import { z } from "zod"; import { displayNameSchema } from "./account"; export const screenshotLimit = 5 * 1024 * 1024; const text = (min: number, max: number) => z .string() .trim() .min(min) .max(max) .refine( (value) => !/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u202a-\u202e\u2066-\u2069]/u.test( value, ), "Remove hidden control characters", ); export const bugReportSchema = z .object({ clientId: z.string().uuid(), title: text(5, 120), description: text(15, 6000), displayName: displayNameSchema.optional(), pagePath: z .string() .max(300) .regex(/^\/(?!\/)[a-zA-Z0-9/_-]*$/) .default("/"), screenshot: z.boolean().default(false), }) .strict(); export const bugMessageSchema = z .object({ clientId: z.string().uuid(), text: text(1, 6000) }) .strict(); export type BugReportRecord = { id: string; client_id: string; owner_id: string | null; access_hash: string; display_name: string; title: string; description: string; page_path: string; state: "draft" | "pending" | "open"; screenshot_expected: boolean; screenshot_path: string | null; linear_asset_url: string | null; linear_identifier: string | null; issue_synced: boolean; glow_requested: boolean; bug_confirmed?: boolean; lease_owner: string | null; lease_until: string | null; retry_at: string | null; created_at: string; updated_at: string; }; export type BugMessageRecord = { id: string; client_id: string | null; report_id: string; kind: "user" | "staff" | "system"; body: string; author_name: string; delivered: boolean; created_at: string; external_id: string | null; }; export type BugReportView = Pick< BugReportRecord, | "id" | "title" | "description" | "page_path" | "state" | "linear_identifier" | "glow_requested" | "created_at" > & { screenshot: string | null; isGuest: boolean; authorAppearance?: import("./profile-appearance").ProfileAppearance; canClaim: boolean; messages: Pick< BugMessageRecord, "id" | "kind" | "body" | "author_name" | "delivered" | "created_at" >[]; }; export type SupportCommand = | { kind: "reply"; text: string } | { kind: "glow"; enabled: boolean }; export function supportCommand(body: string): SupportCommand | null { const reply = /^#player-report-send\s+([\s\S]+)$/.exec(body.trim()); if (reply) { const parsed = text(1, 6000).safeParse(reply[1]); return parsed.success ? { kind: "reply", text: parsed.data } : null; } const glow = /^#player-report-glow (grant|revoke)$/.exec(body.trim()); return glow ? { kind: "glow", enabled: glow[1] === "grant" } : null; } export function escapeSupportMarkdown(value: string) { return value.replace(/[\\`*_{}\[\]()<>#!|~]/g, "\\$&"); }