changed verifier bot to marksbot

This commit is contained in:
onysd 2026-08-04 03:22:46 +03:00
parent 6ade34970c
commit fa5cfaf14d
16 changed files with 302 additions and 30 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -23,8 +23,8 @@
})();
</script>
<script type="module" crossorigin src="/assets/index-65rEwtSD.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CKoIcj6p.css">
<script type="module" crossorigin src="/assets/index-D0qBFBU-.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-EkAGiEK9.css">
</head>
<body>
<div id="root"></div>

View file

@ -0,0 +1,130 @@
import { Check, Loader2, Search, X } from "lucide-react";
import { useEffect, useState } from "react";
import { api, errorMessage } from "../api";
import type { EmojiRow } from "../types";
import { StaticLottie } from "./StaticLottie";
function isAnimated(mime: string): boolean {
const m = mime.toLowerCase();
return m.includes("tgsticker") || m.includes("lottie") || m.includes("json");
}
function EmojiThumb({ row }: { row: EmojiRow }) {
const [failed, setFailed] = useState(!isAnimated(row.MimeType));
useEffect(() => {
setFailed(!isAnimated(row.MimeType));
}, [row.DocumentID, row.MimeType]);
if (failed) {
return <div className="emoji-picker-glyph">{row.Alt || "🙂"}</div>;
}
return (
<StaticLottie
className="emoji-picker-anim"
cacheKey={row.DocumentID}
loader={() => api.emojiAnimation(row.DocumentID)}
onError={() => setFailed(true)}
/>
);
}
// EmojiPicker searches every custom-emoji document already on this
// deployment -- unlike the Emoji admin page's per-pack browsing, this is a
// flat document search with no system/non-system distinction, so a bundled
// icon from a default pack (e.g. Topics' ✅) is just as findable as a
// hand-uploaded one. Lets the caller select a document id by clicking it
// instead of typing one from memory; used wherever a raw document id field
// otherwise has nothing else in the panel to pick from (e.g. a
// bot-verification icon).
export function EmojiPicker({
label,
value,
onChange
}: {
label: string;
value: string;
onChange: (documentID: string) => void;
}) {
const [query, setQuery] = useState("");
const [rows, setRows] = useState<EmojiRow[]>([]);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const selected = rows.find((row) => row.DocumentID === value) ?? null;
async function search() {
setBusy(true);
setError("");
const params = new URLSearchParams({ limit: "24" });
if (query.trim()) params.set("q", query.trim());
try {
const result = await api.emoji(params);
setRows(result.rows ?? []);
} catch (err) {
setError(errorMessage(err));
} finally {
setBusy(false);
}
}
useEffect(() => {
void search();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div className="entity-picker">
<div className="picker-head">
<span>{label}</span>
{value ? (
<button className="link-button" type="button" onClick={() => onChange("")}>
<X size={13} /> {"Clear"}
</button>
) : null}
</div>
{value ? (
<div className="selected-entity">
<Check size={15} />
<div>
<strong>{selected?.Alt || "—"}</strong>
<span className="mono">{value}</span>
</div>
<span>{selected?.SetTitle || "-"}</span>
</div>
) : null}
<div className="picker-search">
<Search size={15} />
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") {
event.preventDefault();
void search();
}
}}
placeholder={"Search document ID or emoji"}
/>
<button className="btn compact-btn" type="button" onClick={search} disabled={busy}>
{busy ? <Loader2 size={14} className="spin" /> : "Search"}
</button>
</div>
{error && <div className="picker-error">{error}</div>}
<div className="picker-results emoji-picker-results">
{rows.map((row) => (
<button
key={row.DocumentID}
className={`picker-row emoji-picker-row ${value === row.DocumentID ? "selected" : ""}`}
type="button"
onClick={() => onChange(row.DocumentID)}
>
<EmojiThumb row={row} />
<span className="mono">{row.DocumentID}</span>
<span>{row.SetTitle || "—"}</span>
</button>
))}
{rows.length === 0 && !busy ? <div className="picker-empty">{"No results"}</div> : null}
</div>
</div>
);
}

View file

@ -18,6 +18,7 @@ import {
import { useEffect, useState, type ReactNode } from "react";
import { api, APIError, errorMessage } from "../api";
import { ActionButton } from "../components/ActionButton";
import { EmojiPicker } from "../components/EmojiPicker";
import { BotPicker } from "../components/EntityPicker";
import { Alert, Badge, EmptyRow, Metric, PageFrame, QueryPanel, SectionHead } from "../components/ui";
import { displayUsername, formatDate } from "../lib/format";
@ -623,17 +624,9 @@ function IconsBlock({
<>
{canManage && (
<section className="section-block">
<SectionHead title={"Add or rename an icon"} text={"The document id has to name a real custom emoji document on this deployment; the Emoji section lists them with their ids. Adding an id that already exists renames it instead of duplicating it."} />
<SectionHead title={"Add or rename an icon"} text={"Search and pick any custom-emoji document already on this deployment (including bundled/system ones). Adding an id that already exists renames it instead of duplicating it."} />
<EmojiPicker label={"Document"} value={documentID} onChange={setDocumentID} />
<div className="bot-create-fields">
<label className="duration-field">
<span>{"Document ID"}</span>
<input
value={documentID}
onChange={(event) => setDocumentID(event.target.value)}
inputMode="numeric"
placeholder="5361371319611781774"
/>
</label>
<label className="duration-field">
<span>{"Name"}</span>
<input

View file

@ -11,9 +11,8 @@ import { StickerSetPreviewModal } from "./StickerSetPreviewModal";
type StickerPageSize = 10 | 20 | 50 | 100 | "all";
// Shared list/manage view for one non-system sticker-set kind ("stickers" or
// "emoji") — system packs (dice, animated emoji, premium/TON gifts, etc.) are
// filtered out server-side and never reach this page; they aren't meant to be
// hand-edited.
// "emoji") — system packs (dice, animated emoji, gifts) aren't shown here,
// they're not hand-edited.
export function StickerSetsPage({ kind }: { kind: "stickers" | "emoji" }) {
const [sets, setSets] = useState<StickerSetRow[]>([]);
const [query, setQuery] = useState("");

View file

@ -317,6 +317,26 @@
text-align: center;
}
.emoji-picker-row {
grid-template-columns: 36px minmax(140px, 1fr) minmax(100px, 1fr);
}
.emoji-picker-glyph {
font-size: 22px;
line-height: 1;
text-align: center;
}
.emoji-picker-anim {
width: 28px;
height: 28px;
}
.emoji-picker-anim canvas {
width: 100% !important;
height: 100% !important;
}
.picker-error {
color: var(--danger);
background: var(--danger-tint);

View file

@ -1223,9 +1223,14 @@ func run(logger *zap.Logger) error {
botverificationapp.WithMaxPerVerifier(cfg.BotVerificationMaxPerVerifier),
botverificationapp.WithLogger(logger.Named("app").Named("botverification")),
)
// @verifierbot files applications with the operator and reports decisions back.
// @marksbot files applications with the operator and reports decisions back.
botsService.SetCustomVerification(botVerificationService)
botVerificationService.SetApplicantNotifier(botsService)
if granted, err := botVerificationService.SeedDefaultVerifier(ctx); err != nil {
return fmt.Errorf("seed default verifier: %w", err)
} else if granted {
logger.Info("default verifier seed complete", zap.Int64("bot_id", domain.VerifierBotUserID))
}
updatesService := updates.NewService(updateStateStore, updateEventStore, updates.WithLogger(logger.Named("app").Named("updates")))
router := rpc.New(rpc.Config{
DC: cfg.DC,