Files
MineGeo/shared/bug-reports.ts

118 lines
3.2 KiB
TypeScript

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" | "done";
closed_at?: string | null;
delete_after?: string | null;
linear_done_synced?: boolean;
cleanup_run_id?: string | null;
cleanup_lease_until?: string | null;
cleanup_error?: string | null;
purge_started_at?: string | null;
status_checked_at?: string | null;
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"
| "closed_at"
| "delete_after"
| "linear_done_synced"
| "cleanup_error"
> & {
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 }
| { kind: "close" };
export function supportCommand(body: string): SupportCommand | null {
if (body.trim() === "#player-report-close") return { kind: "close" };
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, "\\$&");
}