Add contact relay and DMCA workflow

This commit is contained in:
JB
2026-07-25 19:20:43 -04:00
parent e0bf3b05ab
commit 7f9a75021d
11 changed files with 308 additions and 4 deletions
+18
View File
@@ -40,6 +40,24 @@ CREATE TABLE IF NOT EXISTS notifications (
); );
CREATE INDEX IF NOT EXISTS notifications_user_created_idx ON notifications(user_id, created_at DESC); CREATE INDEX IF NOT EXISTS notifications_user_created_idx ON notifications(user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS notifications_user_unread_idx ON notifications(user_id, read_at, created_at DESC); CREATE INDEX IF NOT EXISTS notifications_user_unread_idx ON notifications(user_id, read_at, created_at DESC);
CREATE TABLE IF NOT EXISTS contact_messages (
id TEXT PRIMARY KEY,
sender_user_id TEXT REFERENCES users(id) ON DELETE SET NULL,
sender_name TEXT NOT NULL,
sender_email TEXT NOT NULL,
team TEXT NOT NULL CHECK(team IN ('oversight', 'administrators', 'dmca')),
subject TEXT NOT NULL,
body TEXT NOT NULL,
dmca_json TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS contact_message_recipients (
message_id TEXT NOT NULL REFERENCES contact_messages(id) ON DELETE CASCADE,
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
PRIMARY KEY(message_id, user_id)
);
CREATE INDEX IF NOT EXISTS contact_messages_created_idx ON contact_messages(created_at DESC);
CREATE INDEX IF NOT EXISTS contact_message_recipients_user_idx ON contact_message_recipients(user_id, message_id);
CREATE TABLE IF NOT EXISTS notification_preferences ( CREATE TABLE IF NOT EXISTS notification_preferences (
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
category TEXT NOT NULL CHECK(category IN ('isac', 'contributions', 'comments', 'builds', 'guides', 'system')), category TEXT NOT NULL CHECK(category IN ('isac', 'contributions', 'comments', 'builds', 'guides', 'system')),
+44
View File
@@ -31,6 +31,50 @@ cannot render, `src/error.html` provides a dependency-free SHD fallback surface.
Error controls retain square geometry, focus-visible treatment, pressed motion, Error controls retain square geometry, focus-visible treatment, pressed motion,
plain-language labels, and stacked mobile recovery actions. plain-language labels, and stacked mobile recovery actions.
### Contact relay and legal requests
Public support uses the SHD Contact Relay surface: a broad transmission form
paired with a compact destination guide. Two-to-six destinations use the shared
segmented-choice pattern. Signed-in identity details may be prefilled, but the
public form remains available to people who cannot sign in. Discord community
access is a distinct provider-colored card; it supplements rather than replaces
the durable contact channel.
The Contact hero explicitly resets the global site-header height: semantic page
`header` elements must use content-driven height and spacing rather than inherit
the 76-pixel navigation-shell constraint. Its title, description, and Discord
card align within a minimum 230-pixel desktop band before the form grid begins.
The Contact page itself remains content-height-driven so high-resolution browser
zoom does not multiply a viewport minimum into a large empty region above the
footer.
The application frame is a full-viewport vertical flex stack. The shared footer
uses automatic top margin, so it rests against the bottom edge on short routes
and follows normal document flow after longer content. Individual pages must
not add spacer heights solely to position the footer. The frame compensates for
`--shd-display-scale` when calculating its viewport minimum so high-resolution
browser zoom does not push a short-page footer below the visible edge.
Community access in the shared footer uses a compact Discord-branded action
with the official symbol, provider blue surface, explicit external-link text,
and visible hover, focus, and pressed feedback. Keep provider branding separate
from the orange links reserved for SHD navigation and legal resources. Support
and legal links form a compact horizontal rail that may wrap at intermediate
widths and becomes a two-column touch-friendly grid on narrow mobile screens.
Contact notifications use the mandatory ISAC visual identity because they are
operational inbound messages. The notification carries a concise preview and a
specific action to an authorization-checked full record. Full messages never
appear in URLs. Copyright requests add a yellow legal-detail subpanel, explicit
attestations, electronic signature, and a direct policy link. Do not visually
present a submission as a legal determination.
The DMCA policy reuses the legal-document/index layout. Copyright and takedown
links must remain visible in the global footer alongside Contact Us. On mobile,
contact columns stack, destination segments become full-width 44-pixel rows,
attestations remain readable, and reply/recovery actions occupy the available
width.
## Tokens ## Tokens
The canonical tokens live in `src/app.css` under `:root`. The canonical tokens live in `src/app.css` under `:root`.
+7 -1
View File
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -133,7 +133,15 @@
<footer class="site-footer"> <footer class="site-footer">
<div><a class="brand footer-brand" href="/"><span class="brand-mark"><i></i><b>SHD</b></span><span>SHD<span class="accent">.build</span></span></a><p>Independent loadout intelligence for the agent community.</p></div> <div><a class="brand footer-brand" href="/"><span class="brand-mark"><i></i><b>SHD</b></span><span>SHD<span class="accent">.build</span></span></a><p>Independent loadout intelligence for the agent community.</p></div>
<div class="footer-links"><a href="/terms">TERMS & PRIVACY</a><a href="/terms#cookies">COOKIE POLICY</a><a href="/terms#retention">DATA RETENTION</a></div> <div class="footer-links">
<a class="footer-discord" href="https://discord.gg/dhpd33VwQK" target="_blank" rel="noreferrer">
<img src="/discord-symbol.svg" alt="" aria-hidden="true" />
<span><small>COMMUNITY NETWORK</small>JOIN SHD DISCORD ↗</span>
</a>
<nav class="footer-nav" aria-label="Support and legal">
<a href="/contact">CONTACT US</a><a href="/dmca">DMCA & COPYRIGHT</a><a href="/terms">TERMS & PRIVACY</a><a href="/terms#cookies">COOKIE POLICY</a><a href="/terms#retention">DATA RETENTION</a>
</nav>
</div>
<p class="fan-disclaimer">UNOFFICIAL FAN SITE · NOT AFFILIATED WITH OR ENDORSED BY UBISOFT. TOM CLANCY’S THE DIVISION, THE DIVISION RESURGENCE, UBISOFT, AND RELATED INTELLECTUAL PROPERTY ARE OWNED BY UBISOFT AND/OR ITS LICENSORS.</p> <p class="fan-disclaimer">UNOFFICIAL FAN SITE · NOT AFFILIATED WITH OR ENDORSED BY UBISOFT. TOM CLANCY’S THE DIVISION, THE DIVISION RESURGENCE, UBISOFT, AND RELATED INTELLECTUAL PROPERTY ARE OWNED BY UBISOFT AND/OR ITS LICENSORS.</p>
</footer> </footer>
@@ -0,0 +1,15 @@
import { error } from '@sveltejs/kit';
import { getDatabase } from '$lib/server/database';
import { getSessionUser } from '$lib/server/auth/session';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ cookies, params }) => {
const user = await getSessionUser(cookies);
if (!user) error(401, 'Sign in to view this contact message.');
const database = await getDatabase();
const message = await (await database.prepare(`SELECT contact_messages.* FROM contact_messages
JOIN contact_message_recipients ON contact_message_recipients.message_id=contact_messages.id
WHERE contact_messages.id=? AND contact_message_recipients.user_id=?`)).get(params.id, user.id);
if (!message) error(404, 'Contact message not found.');
return { message: { ...message, dmca: message.dmca_json ? JSON.parse(String(message.dmca_json)) : null } };
};
@@ -0,0 +1,18 @@
<script lang="ts">
let { data } = $props();
const message = $derived(data.message);
</script>
<svelte:head><title>{message.subject} — Contact Relay — SHD.build</title></svelte:head>
<main class="contact-message-page">
<a class="contact-back" href="/account/notifications">← RETURN TO INBOX</a>
<article class="contact-message">
<header><div><span>SHD CONTACT RELAY</span><strong>{message.team === 'dmca' ? 'DMCA / COPYRIGHT' : String(message.team).toUpperCase()}</strong></div><time datetime={message.created_at}>{new Date(message.created_at).toLocaleString()}</time></header>
<div class="contact-message-title"><p class="eyebrow"><span></span> INBOUND TRANSMISSION</p><h1>{message.subject}</h1><p>FROM {message.sender_name} · <a href={`mailto:${message.sender_email}`}>{message.sender_email}</a></p></div>
<section><h2>MESSAGE</h2><p>{message.body}</p></section>
{#if message.dmca}
<section class="contact-dmca-record"><h2>DMCA NOTICE DETAILS</h2><dl><div><dt>COPYRIGHTED WORK</dt><dd>{message.dmca.work}</dd></div><div><dt>MATERIAL LOCATION</dt><dd>{message.dmca.locations}</dd></div><div><dt>CLAIMANT CONTACT</dt><dd>{message.dmca.contact}</dd></div><div><dt>ELECTRONIC SIGNATURE</dt><dd>{message.dmca.signature}</dd></div><div><dt>ATTESTATIONS</dt><dd>Good-faith authorization statement confirmed · Accuracy and authority statement confirmed under penalty of perjury</dd></div></dl></section>
{/if}
<footer><a class="primary" href={`mailto:${message.sender_email}?subject=${encodeURIComponent(`Re: ${message.subject}`)}`}>REPLY BY EMAIL →</a></footer>
</article>
</main>
@@ -34,6 +34,7 @@
if (notification.category === 'contributions' && /(approved|deployed)/.test(text)) return { label: 'VIEW PUBLISHED DATA', href: notification.link }; if (notification.category === 'contributions' && /(approved|deployed)/.test(text)) return { label: 'VIEW PUBLISHED DATA', href: notification.link };
if (notification.category === 'guides') return { label: 'OPEN GUIDE', href: notification.link }; if (notification.category === 'guides') return { label: 'OPEN GUIDE', href: notification.link };
if (notification.category === 'builds') return { label: 'OPEN BUILD', href: notification.link }; if (notification.category === 'builds') return { label: 'OPEN BUILD', href: notification.link };
if (notification.source === 'SHD CONTACT RELAY') return { label: notification.title.startsWith('DMCA NOTICE') ? 'REVIEW COPYRIGHT REQUEST' : 'OPEN CONTACT MESSAGE', href: notification.link };
if (notification.link === '/account') return { label: 'OPEN AGENT PROFILE', href: notification.link }; if (notification.link === '/account') return { label: 'OPEN AGENT PROFILE', href: notification.link };
return { label: 'OPEN RELATED PAGE', href: notification.link }; return { label: 'OPEN RELATED PAGE', href: notification.link };
}; };
+107
View File
@@ -0,0 +1,107 @@
import { fail } from '@sveltejs/kit';
import { randomUUID } from 'node:crypto';
import { getDatabase } from '$lib/server/database';
import { getSessionUser } from '$lib/server/auth/session';
import { createNotification } from '$lib/server/notifications';
import type { Actions, PageServerLoad } from './$types';
const submissions = new Map<string, number[]>();
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
function clean(value: FormDataEntryValue | null, maximum: number) {
return String(value ?? '').normalize('NFKC').trim().slice(0, maximum);
}
function allowSubmission(key: string) {
const cutoff = Date.now() - 60 * 60 * 1000;
const recent = (submissions.get(key) ?? []).filter((time) => time > cutoff);
if (recent.length >= 3) return false;
recent.push(Date.now());
submissions.set(key, recent);
return true;
}
async function recipients(team: string) {
const database = await getDatabase();
const admin = team === 'administrators' || team === 'dmca';
return (await database.prepare(`SELECT DISTINCT users.id FROM users
LEFT JOIN user_roles ON user_roles.user_id=users.id
LEFT JOIN roles ON roles.id=user_roles.role_id
LEFT JOIN role_permissions ON role_permissions.role_id=roles.id
LEFT JOIN permissions role_permission ON role_permission.id=role_permissions.permission_id
LEFT JOIN user_permissions ON user_permissions.user_id=users.id
LEFT JOIN permissions direct_permission ON direct_permission.id=user_permissions.permission_id
WHERE NOT EXISTS (SELECT 1 FROM deleted_accounts WHERE deleted_accounts.user_id=users.id)
AND (${admin
? "users.role='admin' OR role_permission.name='system.admin' OR direct_permission.name='system.admin'"
: "users.role='admin' OR roles.id IN ('role-moderator','role-data-approver') OR role_permission.name IN ('system.admin','data.approve','users.moderate','avatars.review') OR direct_permission.name IN ('system.admin','data.approve','users.moderate','avatars.review')"})`))
.all();
}
export const load: PageServerLoad = async ({ cookies, url }) => {
const user = await getSessionUser(cookies);
const initialTeam = url.searchParams.get('team') === 'dmca' ? 'dmca' : 'oversight';
if (!user) return { sender: null, initialTeam };
const database = await getDatabase();
const email = await (await database.prepare('SELECT email FROM user_emails WHERE user_id=?')).get(user.id);
return { sender: { name: user.displayName, email: typeof email?.email === 'string' ? email.email : '' }, initialTeam };
};
export const actions: Actions = {
send: async ({ request, cookies, getClientAddress }) => {
const form = await request.formData();
if (clean(form.get('website'), 200)) return { contactSent: true };
const user = await getSessionUser(cookies);
let address = 'unknown';
try { address = getClientAddress(); } catch { /* Development adapters may not expose a client address. */ }
if (!allowSubmission(user ? `user:${user.id}` : `ip:${address}`)) {
return fail(429, { contactError: 'This contact channel has received several recent transmissions. Try again later.' });
}
const team = clean(form.get('team'), 30);
const senderName = clean(form.get('senderName'), 100);
const senderEmail = clean(form.get('senderEmail'), 254).toLocaleLowerCase();
const subject = clean(form.get('subject'), 140);
const body = clean(form.get('body'), 8000);
if (!['oversight', 'administrators', 'dmca'].includes(team)) return fail(400, { contactError: 'Choose a valid destination team.' });
if (senderName.length < 2 || !emailPattern.test(senderEmail)) return fail(400, { contactError: 'Provide your name and a valid reply email address.' });
if (subject.length < 3 || body.length < 10) return fail(400, { contactError: 'Provide a subject and a message of at least 10 characters.' });
let dmca: Record<string, string | boolean> | null = null;
if (team === 'dmca') {
dmca = {
work: clean(form.get('copyrightedWork'), 4000),
locations: clean(form.get('contentLocations'), 4000),
contact: clean(form.get('claimantContact'), 2000),
signature: clean(form.get('signature'), 200),
goodFaith: form.get('goodFaith') === '1',
accurate: form.get('accurate') === '1'
};
if (String(dmca.work).length < 5 || String(dmca.locations).length < 5 || String(dmca.contact).length < 10 || String(dmca.signature).length < 2 || !dmca.goodFaith || !dmca.accurate) {
return fail(400, { contactError: 'DMCA notices require the work, material location, complete contact information, electronic signature, and both required statements.' });
}
}
const targetUsers = await recipients(team);
if (!targetUsers.length) return fail(503, { contactError: 'No authorized recipient is currently assigned to this channel. Please use the Discord community link for assistance.' });
const database = await getDatabase();
const messageId = randomUUID();
const createdAt = new Date().toISOString();
await (await database.prepare(`INSERT INTO contact_messages(id,sender_user_id,sender_name,sender_email,team,subject,body,dmca_json,created_at)
VALUES(?,?,?,?,?,?,?,?,?)`)).run(messageId, user?.id ?? null, senderName, senderEmail, team, subject, body, dmca ? JSON.stringify(dmca) : null, createdAt);
for (const recipient of targetUsers) {
const userId = String(recipient.id);
await (await database.prepare('INSERT INTO contact_message_recipients(message_id,user_id) VALUES(?,?)')).run(messageId, userId);
await createNotification({
userId,
category: 'isac',
source: 'SHD CONTACT RELAY',
title: `${team === 'dmca' ? 'DMCA NOTICE' : team === 'oversight' ? 'OVERSIGHT CONTACT' : 'ADMIN CONTACT'} · ${subject}`,
body: `${senderName} (${senderEmail}) submitted a new ${team === 'dmca' ? 'copyright request' : 'contact message'}.\n\n${body.slice(0, 1200)}`,
link: `/account/contact/${messageId}`,
mandatory: true
});
}
return { contactSent: true, contactToken: messageId };
}
};
+66
View File
@@ -0,0 +1,66 @@
<script lang="ts">
import PendingForm from '$lib/components/PendingForm.svelte';
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte';
let { data, form } = $props();
let team = $derived(data.initialTeam);
const teams = [
{ value: 'oversight', label: 'Oversight' },
{ value: 'administrators', label: 'Administrators' },
{ value: 'dmca', label: 'DMCA / Copyright' }
];
</script>
<svelte:head><title>Contact SHD.build</title><meta name="description" content="Contact SHD.build Oversight, administrators, or submit a copyright request."/></svelte:head>
<main class="contact-page">
<header class="contact-hero">
<div><p class="eyebrow"><span></span> SHD COMMUNICATION NETWORK</p><h1>CONTACT <em>SHD.</em></h1><p>Route a message to the team best equipped to respond. Your transmission will enter the appropriate team’s secure SHD.build inbox.</p></div>
<a class="discord-contact" href="https://discord.gg/dhpd33VwQK" rel="external noreferrer">
<img src="/discord-symbol.svg" alt="" aria-hidden="true" />
<span><small>DISCORD COMMUNITY</small><strong>JOIN THE SHD NETWORK</strong></span>
<b>↗</b>
</a>
</header>
<div class="contact-layout">
<section class="contact-console">
<div class="contact-console-head"><div><span>OUTBOUND TRANSMISSION</span><strong>NEW MESSAGE</strong></div><b>SECURE</b></div>
{#if form?.contactSent}
<div class="contact-success" role="status"><span>✓</span><div><strong>TRANSMISSION RECEIVED</strong><p>Your message has been delivered to the selected team. Keep your reply email available for follow-up.</p></div></div>
{:else}
<PendingForm action="?/send">
{#snippet children(pending)}
<SegmentedChoice legend="DESTINATION TEAM" name="team" options={teams} bind:value={team} required/>
<div class="contact-identity">
<label class="field">YOUR NAME<input name="senderName" value={data.sender?.name ?? ''} maxlength="100" autocomplete="name" required/></label>
<label class="field">REPLY EMAIL<input name="senderEmail" type="email" value={data.sender?.email ?? ''} maxlength="254" autocomplete="email" required/></label>
</div>
<label class="field">SUBJECT<input name="subject" maxlength="140" required/></label>
<label class="field">MESSAGE<textarea name="body" rows="7" maxlength="8000" required></textarea></label>
<label class="contact-honeypot" aria-hidden="true">WEBSITE<input name="website" tabindex="-1" autocomplete="off"/></label>
{#if team === 'dmca'}
<section class="dmca-contact-fields">
<header><div><span>COPYRIGHT CLAIM DETAILS</span><strong>DMCA NOTICE</strong></div><a href="/dmca">VIEW DMCA POLICY →</a></header>
<p>Use this channel only if you own the copyright or are authorized to act for its owner. All fields below are required for a formal notice.</p>
<label class="field">COPYRIGHTED WORK<textarea name="copyrightedWork" rows="3" maxlength="4000" placeholder="Identify the copyrighted work, or provide a representative list…" required></textarea></label>
<label class="field">LOCATION OF MATERIAL<textarea name="contentLocations" rows="3" maxlength="4000" placeholder="Provide each exact SHD.build URL where the material appears…" required></textarea></label>
<label class="field">CLAIMANT CONTACT INFORMATION<textarea name="claimantContact" rows="3" maxlength="2000" placeholder="Legal name, mailing address, telephone number, and email address…" required></textarea></label>
<label class="field">ELECTRONIC SIGNATURE<input name="signature" maxlength="200" placeholder="Type your full legal name" required/></label>
<label class="legal-attestation"><input name="goodFaith" type="checkbox" value="1" required/><span>I have a good-faith belief that the disputed use is not authorized by the copyright owner, its agent, or the law.</span></label>
<label class="legal-attestation"><input name="accurate" type="checkbox" value="1" required/><span>I state under penalty of perjury that this notice is accurate and that I am the copyright owner or authorized to act for the owner.</span></label>
</section>
{/if}
{#if form?.contactError}<p class="error" role="alert">{form.contactError}</p>{/if}
<div class="form-actions"><button class="primary" disabled={pending}>{pending ? 'TRANSMITTING…' : 'SEND MESSAGE →'}</button></div>
{/snippet}
</PendingForm>
{/if}
</section>
<aside class="contact-guidance">
<div><span>01</span><h2>OVERSIGHT</h2><p>Community conduct, moderation decisions, inappropriate user content, avatars, and contribution review.</p></div>
<div><span>02</span><h2>ADMINISTRATORS</h2><p>Account support, access issues, security concerns, technical problems, and site operations.</p></div>
<div class="copyright-guidance"><span>03</span><h2>DMCA & COPYRIGHT</h2><p>Formal copyright takedown notices, counter-notices, and other intellectual-property requests.</p><a href="/dmca">READ POLICY →</a></div>
<small>Do not submit passwords, authentication tokens, private keys, or other secrets through this form.</small>
</aside>
</div>
</main>
+21
View File
@@ -0,0 +1,21 @@
<svelte:head>
<title>DMCA & Copyright Policy — SHD.build</title>
<meta name="description" content="SHD.build copyright, DMCA notice, counter-notice, and repeat-infringer policy."/>
</svelte:head>
<main class="legal-page">
<header class="legal-hero"><p class="eyebrow"><span></span> SHD COPYRIGHT OPERATIONS</p><h1>DMCA & <em>COPYRIGHT.</em></h1><p>SHD.build respects intellectual-property rights and provides a documented channel for copyright owners, authorized agents, and affected users.</p></header>
<div class="legal-layout">
<nav class="legal-index" aria-label="DMCA policy sections">
<a href="#notice">01 · TAKEDOWN NOTICE</a><a href="#processing">02 · PROCESSING</a><a href="#counter">03 · COUNTER-NOTICE</a><a href="#repeat">04 · REPEAT INFRINGERS</a><a href="#misrepresentation">05 · ACCURACY</a><a href="#contact">06 · SUBMIT</a>
</nav>
<article class="legal-document">
<section id="notice"><span>01</span><div><h2>NOTICE OF CLAIMED INFRINGEMENT</h2><p>If you own a copyright or are authorized to act for its owner and believe material on SHD.build infringes that copyright, submit a written notice containing: your physical or electronic signature; identification of the copyrighted work or a representative list; identification and exact location of the material to remove or disable; sufficient contact information, including mailing address, telephone number, and email address; a good-faith statement that the disputed use is not authorized by the owner, its agent, or law; and a statement under penalty of perjury that the notice is accurate and you are authorized to act for the owner.</p><p>Use exact SHD.build URLs whenever possible. A notice that omits required information may delay review while we request clarification.</p></div></section>
<section id="processing"><span>02</span><div><h2>REVIEW & REMOVAL</h2><p>We review sufficiently complete notices and may remove or disable access to identified material expeditiously where appropriate. We may provide the notice, including the claimant’s identity and allegations, to the user who supplied the material so that user can understand the claim and exercise applicable counter-notice rights.</p><p>Submitting a notice does not guarantee removal, constitute a legal ruling, or waive any party’s rights.</p></div></section>
<section id="counter"><span>03</span><div><h2>COUNTER-NOTIFICATION</h2><p>A user who believes material was removed through mistake or misidentification may submit a counter-notice identifying the removed material and its prior location; providing a physical or electronic signature and complete contact information; stating under penalty of perjury a good-faith belief that removal resulted from mistake or misidentification; consenting to the jurisdiction of the appropriate United States federal district court; and agreeing to accept service of process from the original claimant or that claimant’s agent.</p><p>Where applicable, SHD.build may restore material no sooner than 10 and no later than 14 business days after forwarding a compliant counter-notice, unless the original claimant informs us that a court action has been filed seeking to restrain the allegedly infringing activity.</p></div></section>
<section id="repeat"><span>04</span><div><h2>REPEAT-INFRINGER POLICY</h2><p>SHD.build may restrict or terminate accounts of repeat infringers in appropriate circumstances. We consider the nature, credibility, and outcome of notices and counter-notices rather than treating every allegation as a final determination. We may also remove content or restrict accounts when infringement is otherwise established or apparent.</p></div></section>
<section id="misrepresentation"><span>05</span><div><h2>ACCURACY & MISREPRESENTATION</h2><p>Knowingly making a material misrepresentation that content is infringing, or that removed content was removed through mistake or misidentification, may result in liability under 17 U.S.C. § 512(f). Consider fair use, licenses, and other lawful uses before submitting a notice. This policy is operational information and not legal advice.</p><p>For official guidance, consult the <a href="https://www.copyright.gov/512/" rel="external noreferrer">U.S. Copyright Office Section 512 resources</a>.</p></div></section>
<section id="contact"><span>06</span><div><h2>SUBMIT A REQUEST</h2><p>Use the DMCA / Copyright destination on the SHD.build contact form. The form collects the elements needed for an initial takedown notice and routes the complete record to administrators. Counter-notices and follow-up documents may use the same channel.</p><a class="primary legal-primary" href="/contact?team=dmca">OPEN COPYRIGHT REQUEST →</a></div></section>
</article>
</div>
</main>
+2 -2
View File
@@ -23,11 +23,11 @@
<section id="accounts"><span>02</span><div><h2>ACCOUNTS & SECURITY</h2><p>You are responsible for activity performed through your account and for maintaining control of your authentication methods. You must provide accurate information, may not impersonate another person, and may not attempt to access another account. We may restrict accounts to protect users, enforce these terms, or comply with law.</p><p>Available authentication methods may include passkeys, verified email magic links, local username and password credentials, Discord, and other providers added later. Third-party authentication remains subject to that provider’s terms and privacy practices.</p></div></section> <section id="accounts"><span>02</span><div><h2>ACCOUNTS & SECURITY</h2><p>You are responsible for activity performed through your account and for maintaining control of your authentication methods. You must provide accurate information, may not impersonate another person, and may not attempt to access another account. We may restrict accounts to protect users, enforce these terms, or comply with law.</p><p>Available authentication methods may include passkeys, verified email magic links, local username and password credentials, Discord, and other providers added later. Third-party authentication remains subject to that provider’s terms and privacy practices.</p></div></section>
<section id="content"><span>03</span><div><h2>USER CONTENT</h2><p>You retain ownership of original content you submit. You grant SHD.build a non-exclusive, worldwide, royalty-free license to host, reproduce, format, display, and distribute that content as necessary to operate the service. You represent that you have the rights required to post it.</p><p>Builds, guides, and comments may remain available after account deletion under a “Deleted Agent” attribution so community references are not broken. You may not upload unlawful, infringing, malicious, or privacy-invasive content.</p></div></section> <section id="content"><span>03</span><div><h2>USER CONTENT</h2><p>You retain ownership of original content you submit. You grant SHD.build a non-exclusive, worldwide, royalty-free license to host, reproduce, format, display, and distribute that content as necessary to operate the service. You represent that you have the rights required to post it.</p><p>Builds, guides, and comments may remain available after account deletion under a “Deleted Agent” attribution so community references are not broken. You may not upload unlawful, infringing, malicious, or privacy-invasive content.</p></div></section>
<section id="conduct"><span>04</span><div><h2>COMMUNITY CONDUCT</h2><p>Do not harass others; publish illegal or infringing material; distribute malware; evade access restrictions; scrape or overload the service; manipulate ratings; spam; or exploit security vulnerabilities. Community Oversight may remove content, restrict access, or preserve relevant records when reasonably necessary for safety, support, abuse prevention, or legal obligations.</p></div></section> <section id="conduct"><span>04</span><div><h2>COMMUNITY CONDUCT</h2><p>Do not harass others; publish illegal or infringing material; distribute malware; evade access restrictions; scrape or overload the service; manipulate ratings; spam; or exploit security vulnerabilities. Community Oversight may remove content, restrict access, or preserve relevant records when reasonably necessary for safety, support, abuse prevention, or legal obligations.</p></div></section>
<section id="privacy"><span>05</span><div><h2>INFORMATION WE PROCESS</h2><p>Account data may include internal account identifiers, private login username, public display name, password hash, verified email, linked-provider identifiers and profile details, passkey public credentials, avatar, roles, permissions, builds, guides, favorites, ratings, comments, notification messages, read state, and delivery preferences. Passwords are stored only as salted Argon2id hashes. Private passkey material remains on your authenticator.</p><p>For login security and support, events may include the time, success status, authentication provider, IP address, user-agent string, normalized browser, operating system and device type, approximate country/region/city when supplied by the hosting proxy, failure reason, automated risk flags, provider or passkey credential reference, associated session identifier, and session end time or reason. We do not sell this information. It is used for authentication, abuse prevention, troubleshooting, moderation, and account support.</p><p>Browser and email notification delivery is optional except that operational ISAC notices remain available in the in-app inbox. Email frequency is configurable by notification category and defaults to off. OAuth and email providers process information under their own policies.</p></div></section> <section id="privacy"><span>05</span><div><h2>INFORMATION WE PROCESS</h2><p>Account data may include internal account identifiers, private login username, public display name, password hash, verified email, linked-provider identifiers and profile details, passkey public credentials, avatar, roles, permissions, builds, guides, favorites, ratings, comments, notification messages, read state, and delivery preferences. Passwords are stored only as salted Argon2id hashes. Private passkey material remains on your authenticator.</p><p>For login security and support, events may include the time, success status, authentication provider, IP address, user-agent string, normalized browser, operating system and device type, approximate country/region/city when supplied by the hosting proxy, failure reason, automated risk flags, provider or passkey credential reference, associated session identifier, and session end time or reason. We do not sell this information. It is used for authentication, abuse prevention, troubleshooting, moderation, and account support.</p><p>Contact submissions include the sender’s name, reply email, destination, subject, message, account identifier when signed in, and submission time. Copyright requests may additionally include claimant contact details, identified works and URLs, attestations, and electronic signature. These records are shared only with authorized recipients and retained as reasonably necessary to respond, document action, resolve disputes, enforce policy, and meet legal obligations.</p><p>Browser and email notification delivery is optional except that operational ISAC notices remain available in the in-app inbox. Email frequency is configurable by notification category and defaults to off. OAuth and email providers process information under their own policies.</p></div></section>
<section id="cookies"><span>06</span><div><h2>ESSENTIAL COOKIES</h2><p>SHD.build currently uses only first-party cookies that are strictly necessary to provide authentication and protect sign-in flows. We do not currently use analytics, advertising, behavioral-tracking, or personalization cookies. These essential cookies cannot be disabled through a preference control. You may prevent cookies through your browser, but account sign-in will not function correctly.</p><div class="legal-table-wrap"><table class="legal-table"><thead><tr><th>COOKIE</th><th>PURPOSE</th><th>DURATION</th><th>PROTECTION</th></tr></thead><tbody><tr><td><code>shd_session</code></td><td>Keeps you securely signed in and associates requests with your server-side session.</td><td>Up to 30 days, or until sign-out or session invalidation.</td><td>HTTP-only, SameSite=Lax, Secure in production.</td></tr><tr><td><code>shd_discord_oauth_state</code></td><td>Validates a Discord sign-in or linking response and helps prevent request forgery.</td><td>Up to 10 minutes; removed when the callback is processed.</td><td>HTTP-only, SameSite=Lax, Secure in production; limited to the Discord authentication path.</td></tr></tbody></table></div><p>The informational notice remembers its dismissal in browser local storage under <code>shd-essential-cookie-notice</code>. It is not sent with web requests and can be removed through your browser’s site-data controls. OAuth state remains in a short-lived HTTP-only cookie because it must be securely validated by the server and unavailable to browser scripts.</p><p>If non-essential technologies are introduced later, this policy and the site’s consent controls will be updated before those technologies are enabled where consent is required.</p></div></section> <section id="cookies"><span>06</span><div><h2>ESSENTIAL COOKIES</h2><p>SHD.build currently uses only first-party cookies that are strictly necessary to provide authentication and protect sign-in flows. We do not currently use analytics, advertising, behavioral-tracking, or personalization cookies. These essential cookies cannot be disabled through a preference control. You may prevent cookies through your browser, but account sign-in will not function correctly.</p><div class="legal-table-wrap"><table class="legal-table"><thead><tr><th>COOKIE</th><th>PURPOSE</th><th>DURATION</th><th>PROTECTION</th></tr></thead><tbody><tr><td><code>shd_session</code></td><td>Keeps you securely signed in and associates requests with your server-side session.</td><td>Up to 30 days, or until sign-out or session invalidation.</td><td>HTTP-only, SameSite=Lax, Secure in production.</td></tr><tr><td><code>shd_discord_oauth_state</code></td><td>Validates a Discord sign-in or linking response and helps prevent request forgery.</td><td>Up to 10 minutes; removed when the callback is processed.</td><td>HTTP-only, SameSite=Lax, Secure in production; limited to the Discord authentication path.</td></tr></tbody></table></div><p>The informational notice remembers its dismissal in browser local storage under <code>shd-essential-cookie-notice</code>. It is not sent with web requests and can be removed through your browser’s site-data controls. OAuth state remains in a short-lived HTTP-only cookie because it must be securely validated by the server and unavailable to browser scripts.</p><p>If non-essential technologies are introduced later, this policy and the site’s consent controls will be updated before those technologies are enabled where consent is required.</p></div></section>
<section id="retention"><span>07</span><div><h2>RETENTION & DELETION</h2><p>IP addresses, user-agent strings, normalized device information, and credential references in login history are retained for no more than 30 days, then automatically removed from the event. Non-sensitive facts such as event time, provider, outcome, failure category, and whether a session was created may remain for security and support history.</p><p>Deleting your account removes active sessions, credentials, verified email, provider profiles, passkeys, permissions, and uploaded avatar. You may also select immediate purging of retained login-event history during self-deletion. Public contributions may remain attributed to “Deleted Agent.” Backups and records required for fraud prevention, dispute resolution, or law may persist for a limited period where applicable.</p></div></section> <section id="retention"><span>07</span><div><h2>RETENTION & DELETION</h2><p>IP addresses, user-agent strings, normalized device information, and credential references in login history are retained for no more than 30 days, then automatically removed from the event. Non-sensitive facts such as event time, provider, outcome, failure category, and whether a session was created may remain for security and support history.</p><p>Deleting your account removes active sessions, credentials, verified email, provider profiles, passkeys, permissions, and uploaded avatar. You may also select immediate purging of retained login-event history during self-deletion. Public contributions may remain attributed to “Deleted Agent.” Backups and records required for fraud prevention, dispute resolution, or law may persist for a limited period where applicable.</p></div></section>
<section id="third-parties"><span>08</span><div><h2>THIRD-PARTY SERVICES</h2><p>SHD.build may connect to Discord, email delivery services, and future identity providers. Their services are governed by their own terms. Links to other sites do not imply endorsement. You authorize the minimum provider data requested when choosing a linked sign-in method.</p></div></section> <section id="third-parties"><span>08</span><div><h2>THIRD-PARTY SERVICES</h2><p>SHD.build may connect to Discord, email delivery services, and future identity providers. Their services are governed by their own terms. Links to other sites do not imply endorsement. You authorize the minimum provider data requested when choosing a linked sign-in method.</p></div></section>
<section id="disclaimers"><span>09</span><div><h2>FAN-SITE & LEGAL DISCLAIMER</h2><p>SHD.build is an unofficial fan-created service and is not affiliated with, endorsed by, sponsored by, or approved by Ubisoft Entertainment or its affiliates.</p><p>Tom Clancy’s The Division, Tom Clancy’s The Division Resurgence, Ubisoft, and related names, trademarks, logos, characters, artwork, game data, and other intellectual property are owned by Ubisoft and/or its licensors. Their use is for identification, commentary, and community reference. No ownership is claimed.</p><p>The service and community information are provided “as is” and “as available,” without warranties of accuracy, availability, fitness for a particular purpose, or non-infringement to the extent permitted by law. Build data may become outdated after game updates. SHD.build is not responsible for third-party services or decisions made using community content.</p><p>These terms may be updated as the service changes. Material revisions will be identified by a new effective date. Nothing here limits rights that cannot legally be waived.</p><p>For authoritative information about Ubisoft intellectual property, consult Ubisoft’s official <a href="https://www.ubisoft.com/legal/documents/termsofuse/en-US" rel="external">Terms of Use</a> and the official <a href="https://www.ubisoft.com/game/the-division/resurgence" rel="external">The Division Resurgence site</a>.</p></div></section> <section id="disclaimers"><span>09</span><div><h2>FAN-SITE & LEGAL DISCLAIMER</h2><p>SHD.build is an unofficial fan-created service and is not affiliated with, endorsed by, sponsored by, or approved by Ubisoft Entertainment or its affiliates.</p><p>Tom Clancy’s The Division, Tom Clancy’s The Division Resurgence, Ubisoft, and related names, trademarks, logos, characters, artwork, game data, and other intellectual property are owned by Ubisoft and/or its licensors. Their use is for identification, commentary, and community reference. No ownership is claimed.</p><p>The service and community information are provided “as is” and “as available,” without warranties of accuracy, availability, fitness for a particular purpose, or non-infringement to the extent permitted by law. Build data may become outdated after game updates. SHD.build is not responsible for third-party services or decisions made using community content.</p><p>Copyright owners and authorized agents may use our <a href="/dmca">DMCA & Copyright Policy</a> and <a href="/contact?team=dmca">copyright request form</a>. Other questions may be sent through <a href="/contact">Contact Us</a>.</p><p>These terms may be updated as the service changes. Material revisions will be identified by a new effective date. Nothing here limits rights that cannot legally be waived.</p><p>For authoritative information about Ubisoft intellectual property, consult Ubisoft’s official <a href="https://www.ubisoft.com/legal/documents/termsofuse/en-US" rel="external">Terms of Use</a> and the official <a href="https://www.ubisoft.com/game/the-division/resurgence" rel="external">The Division Resurgence site</a>.</p></div></section>
</article> </article>
</div> </div>
</main> </main>