mirror of
https://github.com/moltbot/moltbot.git
synced 2026-04-18 12:14:32 +00:00
fix(plugins): clean tlon lint types
This commit is contained in:
@@ -45,6 +45,20 @@ export type MonitorTlonOpts = {
|
||||
accountId?: string | null;
|
||||
};
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown> | null, key: string): string | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
function readNumber(record: Record<string, unknown> | null, key: string): number | undefined {
|
||||
const value = record?.[key];
|
||||
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
||||
}
|
||||
|
||||
function formatErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
@@ -683,9 +697,10 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
});
|
||||
|
||||
// Firehose handler for all channel messages (/v2)
|
||||
const handleChannelsFirehose = async (event: any) => {
|
||||
const handleChannelsFirehose = async (event: unknown) => {
|
||||
try {
|
||||
const nest = event?.nest;
|
||||
const eventRecord = asRecord(event);
|
||||
const nest = readString(eventRecord, "nest");
|
||||
if (!nest) {
|
||||
return;
|
||||
}
|
||||
@@ -695,27 +710,36 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
return;
|
||||
}
|
||||
|
||||
const response = event?.response;
|
||||
const response = asRecord(eventRecord?.response);
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle post responses (new posts and replies)
|
||||
const essay = response?.post?.["r-post"]?.set?.essay;
|
||||
const memo = response?.post?.["r-post"]?.reply?.["r-reply"]?.set?.memo;
|
||||
const post = asRecord(response.post);
|
||||
const rPost = asRecord(post?.["r-post"]);
|
||||
const set = asRecord(rPost?.set);
|
||||
const reply = asRecord(rPost?.reply);
|
||||
const replyPayload = asRecord(reply?.["r-reply"]);
|
||||
const replySet = asRecord(replyPayload?.set);
|
||||
const essay = asRecord(set?.essay);
|
||||
const memo = asRecord(replySet?.memo);
|
||||
if (!essay && !memo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = memo || essay;
|
||||
const isThreadReply = Boolean(memo);
|
||||
const messageId = isThreadReply ? response?.post?.["r-post"]?.reply?.id : response?.post?.id;
|
||||
const messageId = isThreadReply ? readString(reply, "id") : readString(post, "id");
|
||||
if (!messageId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!processedTracker.mark(messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const senderShip = normalizeShip(content.author ?? "");
|
||||
const senderShip = normalizeShip(readString(content, "author") ?? "");
|
||||
if (!senderShip || senderShip === botShipName) {
|
||||
return;
|
||||
}
|
||||
@@ -728,15 +752,13 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
cacheMessage(nest, {
|
||||
author: senderShip,
|
||||
content: rawText,
|
||||
timestamp: content.sent || Date.now(),
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
id: messageId,
|
||||
});
|
||||
|
||||
// Get thread info early for participation check
|
||||
const seal = isThreadReply
|
||||
? response?.post?.["r-post"]?.reply?.["r-reply"]?.set?.seal
|
||||
: response?.post?.["r-post"]?.set?.seal;
|
||||
const parentId = seal?.["parent-id"] || seal?.parent || null;
|
||||
const seal = isThreadReply ? asRecord(replySet?.seal) : asRecord(set?.seal);
|
||||
const parentId = readString(seal, "parent-id") ?? readString(seal, "parent") ?? null;
|
||||
|
||||
// Check if we should respond:
|
||||
// 1. Direct mention always triggers response
|
||||
@@ -773,7 +795,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
messageId: messageId ?? "",
|
||||
messageText: rawText,
|
||||
messageContent: content.content,
|
||||
timestamp: content.sent || Date.now(),
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
parentId: parentId ?? undefined,
|
||||
isThreadReply,
|
||||
},
|
||||
@@ -806,11 +828,11 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
channelNest: nest,
|
||||
hostShip: parsed?.hostShip,
|
||||
channelName: parsed?.channelName,
|
||||
timestamp: content.sent || Date.now(),
|
||||
timestamp: readNumber(content, "sent") ?? Date.now(),
|
||||
parentId,
|
||||
isThreadReply,
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Error handling channel firehose event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
@@ -819,7 +841,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
// Track which DM invites we've already processed to avoid duplicate accepts
|
||||
const processedDmInvites = new Set<string>();
|
||||
|
||||
const handleChatFirehose = async (event: any) => {
|
||||
const handleChatFirehose = async (event: unknown) => {
|
||||
try {
|
||||
// Handle DM invite lists (arrays)
|
||||
if (Array.isArray(event)) {
|
||||
@@ -874,16 +896,20 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!("whom" in event) || !("response" in event)) {
|
||||
const eventRecord = asRecord(event);
|
||||
if (!eventRecord) {
|
||||
return;
|
||||
}
|
||||
|
||||
const whom = event.whom; // DM partner ship or club ID
|
||||
const messageId = event.id;
|
||||
const response = event.response;
|
||||
const whom = eventRecord.whom; // DM partner ship or club ID
|
||||
const messageId = readString(eventRecord, "id");
|
||||
const response = asRecord(eventRecord.response);
|
||||
if (!messageId || !response) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle add events (new messages)
|
||||
const essay = response?.add?.essay;
|
||||
const essay = asRecord(asRecord(response.add)?.essay);
|
||||
if (!essay) {
|
||||
return;
|
||||
}
|
||||
@@ -892,7 +918,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
return;
|
||||
}
|
||||
|
||||
const authorShip = normalizeShip(essay.author ?? "");
|
||||
const authorShip = normalizeShip(readString(essay, "author") ?? "");
|
||||
const partnerShip = extractDmPartnerShip(whom);
|
||||
const senderShip = partnerShip || authorShip;
|
||||
|
||||
@@ -950,7 +976,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
messageText: resolvedMessageText,
|
||||
messageContent: essay.content,
|
||||
isGroup: false,
|
||||
timestamp: essay.sent || Date.now(),
|
||||
timestamp: readNumber(essay, "sent") ?? Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -967,7 +993,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
messageId: messageId ?? "",
|
||||
messageText,
|
||||
messageContent: essay.content,
|
||||
timestamp: essay.sent || Date.now(),
|
||||
timestamp: readNumber(essay, "sent") ?? Date.now(),
|
||||
},
|
||||
});
|
||||
await queueApprovalRequest(approval);
|
||||
@@ -988,9 +1014,9 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
senderShip,
|
||||
messageContent: essay.content, // Pass raw content for media extraction
|
||||
isGroup: false,
|
||||
timestamp: essay.sent || Date.now(),
|
||||
timestamp: readNumber(essay, "sent") ?? Date.now(),
|
||||
});
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Error handling chat firehose event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
};
|
||||
@@ -1030,20 +1056,23 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
await api.subscribe({
|
||||
app: "contacts",
|
||||
path: "/v1/news",
|
||||
event: (event: any) => {
|
||||
event: (event: unknown) => {
|
||||
try {
|
||||
const eventRecord = asRecord(event);
|
||||
// Look for self profile updates
|
||||
if (event?.self) {
|
||||
const selfUpdate = event.self;
|
||||
if (selfUpdate?.contact?.nickname?.value !== undefined) {
|
||||
const newNickname = selfUpdate.contact.nickname.value || null;
|
||||
if (eventRecord?.self) {
|
||||
const selfUpdate = asRecord(eventRecord.self);
|
||||
const contact = asRecord(selfUpdate?.contact);
|
||||
const nickname = asRecord(contact?.nickname);
|
||||
if (nickname && "value" in nickname) {
|
||||
const newNickname = readString(nickname, "value") ?? null;
|
||||
if (newNickname !== botNickname) {
|
||||
botNickname = newNickname;
|
||||
runtime.log?.(`[tlon] Nickname updated: ${botNickname}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Error handling contacts event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
},
|
||||
@@ -1103,14 +1132,15 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
await api.subscribe({
|
||||
app: "groups",
|
||||
path: "/groups/ui",
|
||||
event: async (event: any) => {
|
||||
event: async (event: unknown) => {
|
||||
try {
|
||||
const eventRecord = asRecord(event);
|
||||
// Handle group/channel join events
|
||||
// Event structure: { group: { flag: "~host/group-name", ... }, channels: { ... } }
|
||||
if (event && typeof event === "object") {
|
||||
if (eventRecord) {
|
||||
// Check for new channels being added to groups
|
||||
if (event.channels && typeof event.channels === "object") {
|
||||
const channels = event.channels as Record<string, any>;
|
||||
const channels = asRecord(eventRecord.channels);
|
||||
if (channels) {
|
||||
for (const [channelNest, _channelData] of Object.entries(channels)) {
|
||||
// Only monitor chat channels
|
||||
if (!channelNest.startsWith("chat/")) {
|
||||
@@ -1156,10 +1186,14 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
|
||||
// Also check for the "join" event structure
|
||||
if (event.join && typeof event.join === "object") {
|
||||
const join = event.join as { group?: string; channels?: string[] };
|
||||
if (join.channels) {
|
||||
for (const channelNest of join.channels) {
|
||||
const join = asRecord(eventRecord.join);
|
||||
if (join) {
|
||||
const joinChannels = Array.isArray(join.channels) ? join.channels : [];
|
||||
if (joinChannels.length > 0) {
|
||||
for (const channelNest of joinChannels) {
|
||||
if (typeof channelNest !== "string") {
|
||||
continue;
|
||||
}
|
||||
if (!channelNest.startsWith("chat/")) {
|
||||
continue;
|
||||
}
|
||||
@@ -1198,7 +1232,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Error handling groups-ui event: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
},
|
||||
@@ -1329,7 +1363,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
void (async () => {
|
||||
try {
|
||||
await processPendingInvites(data as Foreigns);
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(
|
||||
`[tlon] Error handling foreigns event: ${formatErrorMessage(error)}`,
|
||||
);
|
||||
@@ -1383,7 +1417,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Channel refresh error: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
@@ -1409,7 +1443,7 @@ export async function monitorTlonProvider(opts: MonitorTlonOpts = {}): Promise<v
|
||||
} finally {
|
||||
try {
|
||||
await api?.close();
|
||||
} catch (error: any) {
|
||||
} catch (error: unknown) {
|
||||
runtime.error?.(`[tlon] Cleanup error: ${formatErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +181,18 @@ export async function resolveAuthorizedMessageText(params: {
|
||||
return citedContent + rawText;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> | null {
|
||||
return value && typeof value === "object" ? (value as Record<string, unknown>) : null;
|
||||
}
|
||||
|
||||
function readString(record: Record<string, unknown>, key: string): string | undefined {
|
||||
const value = record[key];
|
||||
return typeof value === "string" ? value : undefined;
|
||||
}
|
||||
|
||||
// Helper to recursively extract text from inline content
|
||||
function renderInlineItem(
|
||||
item: any,
|
||||
item: unknown,
|
||||
options?: {
|
||||
linkMode?: "content-or-href" | "href";
|
||||
allowBreak?: boolean;
|
||||
@@ -193,44 +202,52 @@ function renderInlineItem(
|
||||
if (typeof item === "string") {
|
||||
return item;
|
||||
}
|
||||
if (!item || typeof item !== "object") {
|
||||
const record = asRecord(item);
|
||||
if (!record) {
|
||||
return "";
|
||||
}
|
||||
if (item.ship) {
|
||||
return item.ship;
|
||||
const ship = readString(record, "ship");
|
||||
if (ship) {
|
||||
return ship;
|
||||
}
|
||||
if ("sect" in item) {
|
||||
return `@${item.sect || "all"}`;
|
||||
const sect = readString(record, "sect");
|
||||
if (sect !== undefined) {
|
||||
return `@${sect || "all"}`;
|
||||
}
|
||||
if (options?.allowBreak && item.break !== undefined) {
|
||||
if (options?.allowBreak && "break" in record) {
|
||||
return "\n";
|
||||
}
|
||||
if (item["inline-code"]) {
|
||||
return `\`${item["inline-code"]}\``;
|
||||
const inlineCode = readString(record, "inline-code");
|
||||
if (inlineCode) {
|
||||
return `\`${inlineCode}\``;
|
||||
}
|
||||
if (item.code) {
|
||||
return `\`${item.code}\``;
|
||||
const code = readString(record, "code");
|
||||
if (code) {
|
||||
return `\`${code}\``;
|
||||
}
|
||||
if (item.link && item.link.href) {
|
||||
return options?.linkMode === "href" ? item.link.href : item.link.content || item.link.href;
|
||||
const link = asRecord(record.link);
|
||||
const linkHref = link ? readString(link, "href") : undefined;
|
||||
if (linkHref) {
|
||||
const linkContent = readString(link, "content");
|
||||
return options?.linkMode === "href" ? linkHref : linkContent || linkHref;
|
||||
}
|
||||
if (item.bold && Array.isArray(item.bold)) {
|
||||
return `**${extractInlineText(item.bold)}**`;
|
||||
if (Array.isArray(record.bold)) {
|
||||
return `**${extractInlineText(record.bold)}**`;
|
||||
}
|
||||
if (item.italics && Array.isArray(item.italics)) {
|
||||
return `*${extractInlineText(item.italics)}*`;
|
||||
if (Array.isArray(record.italics)) {
|
||||
return `*${extractInlineText(record.italics)}*`;
|
||||
}
|
||||
if (item.strike && Array.isArray(item.strike)) {
|
||||
return `~~${extractInlineText(item.strike)}~~`;
|
||||
if (Array.isArray(record.strike)) {
|
||||
return `~~${extractInlineText(record.strike)}~~`;
|
||||
}
|
||||
if (options?.allowBlockquote && item.blockquote && Array.isArray(item.blockquote)) {
|
||||
return `> ${extractInlineText(item.blockquote)}`;
|
||||
if (options?.allowBlockquote && Array.isArray(record.blockquote)) {
|
||||
return `> ${extractInlineText(record.blockquote)}`;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function extractInlineText(items: any[]): string {
|
||||
return items.map((item: any) => renderInlineItem(item)).join("");
|
||||
function extractInlineText(items: readonly unknown[]): string {
|
||||
return items.map((item) => renderInlineItem(item)).join("");
|
||||
}
|
||||
|
||||
export function extractMessageText(content: unknown): string {
|
||||
@@ -239,11 +256,16 @@ export function extractMessageText(content: unknown): string {
|
||||
}
|
||||
|
||||
return content
|
||||
.map((verse: any) => {
|
||||
.map((verse) => {
|
||||
const verseRecord = asRecord(verse);
|
||||
if (!verseRecord) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Handle inline content (text, ships, links, etc.)
|
||||
if (verse.inline && Array.isArray(verse.inline)) {
|
||||
return verse.inline
|
||||
.map((item: any) =>
|
||||
if (Array.isArray(verseRecord.inline)) {
|
||||
return verseRecord.inline
|
||||
.map((item) =>
|
||||
renderInlineItem(item, {
|
||||
linkMode: "href",
|
||||
allowBreak: true,
|
||||
@@ -254,38 +276,46 @@ export function extractMessageText(content: unknown): string {
|
||||
}
|
||||
|
||||
// Handle block content (images, code blocks, etc.)
|
||||
if (verse.block && typeof verse.block === "object") {
|
||||
const block = verse.block;
|
||||
const block = asRecord(verseRecord.block);
|
||||
if (block) {
|
||||
const image = asRecord(block.image);
|
||||
|
||||
// Image blocks
|
||||
if (block.image && block.image.src) {
|
||||
const alt = block.image.alt ? ` (${block.image.alt})` : "";
|
||||
return `\n${block.image.src}${alt}\n`;
|
||||
if (image) {
|
||||
const imageSrc = readString(image, "src");
|
||||
if (imageSrc) {
|
||||
const altText = readString(image, "alt");
|
||||
const alt = altText ? ` (${altText})` : "";
|
||||
return `\n${imageSrc}${alt}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Code blocks
|
||||
if (block.code && typeof block.code === "object") {
|
||||
const lang = block.code.lang || "";
|
||||
const code = block.code.code || "";
|
||||
const codeBlock = asRecord(block.code);
|
||||
if (codeBlock) {
|
||||
const lang = readString(codeBlock, "lang") ?? "";
|
||||
const code = readString(codeBlock, "code") ?? "";
|
||||
return `\n\`\`\`${lang}\n${code}\n\`\`\`\n`;
|
||||
}
|
||||
|
||||
// Header blocks
|
||||
if (block.header && typeof block.header === "object") {
|
||||
const header = asRecord(block.header);
|
||||
if (header) {
|
||||
const headerContent = Array.isArray(header.content) ? header.content : [];
|
||||
const text =
|
||||
block.header.content
|
||||
?.map((item: any) => (typeof item === "string" ? item : ""))
|
||||
.join("") || "";
|
||||
headerContent.map((item) => (typeof item === "string" ? item : "")).join("") || "";
|
||||
return `\n## ${text}\n`;
|
||||
}
|
||||
|
||||
// Cite/quote blocks - parse the reference structure
|
||||
if (block.cite && typeof block.cite === "object") {
|
||||
const cite = block.cite;
|
||||
const cite = asRecord(block.cite);
|
||||
if (cite) {
|
||||
const chanCite = asRecord(cite.chan);
|
||||
|
||||
// ChanCite - reference to a channel message
|
||||
if (cite.chan && typeof cite.chan === "object") {
|
||||
const { nest, where } = cite.chan;
|
||||
if (chanCite) {
|
||||
const nest = readString(chanCite, "nest");
|
||||
const where = readString(chanCite, "where");
|
||||
// where is typically /msg/~author/timestamp
|
||||
const whereMatch = where?.match(/\/msg\/(~[a-z-]+)\/(.+)/);
|
||||
if (whereMatch) {
|
||||
@@ -296,18 +326,28 @@ export function extractMessageText(content: unknown): string {
|
||||
}
|
||||
|
||||
// GroupCite - reference to a group
|
||||
if (cite.group && typeof cite.group === "string") {
|
||||
return `\n> [ref: group ${cite.group}]\n`;
|
||||
const group = readString(cite, "group");
|
||||
if (group) {
|
||||
return `\n> [ref: group ${group}]\n`;
|
||||
}
|
||||
|
||||
// DeskCite - reference to an app/desk
|
||||
if (cite.desk && typeof cite.desk === "object") {
|
||||
return `\n> [ref: ${cite.desk.flag}]\n`;
|
||||
const desk = asRecord(cite.desk);
|
||||
if (desk) {
|
||||
const flag = readString(desk, "flag");
|
||||
if (flag) {
|
||||
return `\n> [ref: ${flag}]\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// BaitCite - reference with group+graph context
|
||||
if (cite.bait && typeof cite.bait === "object") {
|
||||
return `\n> [ref: ${cite.bait.graph} in ${cite.bait.group}]\n`;
|
||||
const bait = asRecord(cite.bait);
|
||||
if (bait) {
|
||||
const graph = readString(bait, "graph");
|
||||
const groupName = readString(bait, "group");
|
||||
if (graph && groupName) {
|
||||
return `\n> [ref: ${graph} in ${groupName}]\n`;
|
||||
}
|
||||
}
|
||||
|
||||
return `\n> [quoted message]\n`;
|
||||
|
||||
Reference in New Issue
Block a user