From 1af58ce9034f0279ce188f606207ba3b480b2b85 Mon Sep 17 00:00:00 2001 From: SavvinPC Date: Sun, 1 Mar 2026 20:16:52 +0300 Subject: [PATCH 1/5] upd: add fragement-stars-plugin --- plugins/fragment-stars-plugin/dist/index.d.ts | 106 ++++ plugins/fragment-stars-plugin/dist/index.js | 32 ++ .../fragment-stars-plugin/dist/index.js.map | 1 + .../dist/utils/common.d.ts | 10 + .../dist/utils/common.js | 19 + .../dist/utils/common.js.map | 1 + .../dist/utils/fragment-api-service.d.ts | 73 +++ .../dist/utils/fragment-api-service.js | 195 ++++++++ .../dist/utils/fragment-api-service.js.map | 1 + .../dist/utils/order-repository.d.ts | 7 + .../dist/utils/order-repository.js | 142 ++++++ .../dist/utils/order-repository.js.map | 1 + .../dist/utils/order-status.d.ts | 2 + .../dist/utils/order-status.js | 12 + .../dist/utils/order-status.js.map | 1 + .../dist/utils/tool-definitions.d.ts | 103 ++++ .../dist/utils/tool-definitions.js | 376 ++++++++++++++ .../dist/utils/tool-definitions.js.map | 1 + .../dist/utils/types.d.ts | 108 ++++ .../fragment-stars-plugin/dist/utils/types.js | 2 + .../dist/utils/types.js.map | 1 + plugins/fragment-stars-plugin/index.js | 10 + plugins/fragment-stars-plugin/index.ts | 38 ++ .../fragment-stars-plugin/package-lock.json | 471 ++++++++++++++++++ plugins/fragment-stars-plugin/package.json | 23 + plugins/fragment-stars-plugin/tsconfig.json | 16 + plugins/fragment-stars-plugin/utils/common.ts | 31 ++ .../utils/fragment-api-service.ts | 316 ++++++++++++ .../utils/order-repository.ts | 187 +++++++ .../utils/order-status.ts | 19 + .../utils/tool-definitions.ts | 460 +++++++++++++++++ plugins/fragment-stars-plugin/utils/types.ts | 94 ++++ 32 files changed, 2859 insertions(+) create mode 100644 plugins/fragment-stars-plugin/dist/index.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/index.js create mode 100644 plugins/fragment-stars-plugin/dist/index.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/common.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/common.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/common.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map create mode 100644 plugins/fragment-stars-plugin/dist/utils/types.d.ts create mode 100644 plugins/fragment-stars-plugin/dist/utils/types.js create mode 100644 plugins/fragment-stars-plugin/dist/utils/types.js.map create mode 100644 plugins/fragment-stars-plugin/index.js create mode 100644 plugins/fragment-stars-plugin/index.ts create mode 100644 plugins/fragment-stars-plugin/package-lock.json create mode 100644 plugins/fragment-stars-plugin/package.json create mode 100644 plugins/fragment-stars-plugin/tsconfig.json create mode 100644 plugins/fragment-stars-plugin/utils/common.ts create mode 100644 plugins/fragment-stars-plugin/utils/fragment-api-service.ts create mode 100644 plugins/fragment-stars-plugin/utils/order-repository.ts create mode 100644 plugins/fragment-stars-plugin/utils/order-status.ts create mode 100644 plugins/fragment-stars-plugin/utils/tool-definitions.ts create mode 100644 plugins/fragment-stars-plugin/utils/types.ts diff --git a/plugins/fragment-stars-plugin/dist/index.d.ts b/plugins/fragment-stars-plugin/dist/index.d.ts new file mode 100644 index 0000000..a948a29 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/index.d.ts @@ -0,0 +1,106 @@ +import type { PluginContext, PluginManifest, RuntimeSdk } from "./utils/types.js"; +export declare const manifest: PluginManifest; +export declare function migrate(db: RuntimeSdk["db"]): void; +export declare function start(ctx: PluginContext): Promise; +export declare const tools: (sdk: RuntimeSdk) => ({ + name: string; + description: string; + parameters: { + type: string; + properties: { + username: { + type: string; + description: string; + }; + quantity: { + type: string; + description: string; + }; + stars: { + type: string; + description: string; + }; + show_sender: { + type: string; + description: string; + }; + lang: { + type: string; + description: string; + enum: string[]; + }; + ref_id?: undefined; + }; + required: string[]; + }; + execute(params: { + username: string; + quantity?: number; + stars?: number; + show_sender?: boolean; + lang?: "ru" | "en"; + }, context: PluginContext): Promise<{ + success: boolean; + error: string; + data?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + message: string; + force_user_message: boolean; + }; + error?: undefined; + }>; +} | { + name: string; + description: string; + parameters: { + type: string; + properties: { + ref_id: { + type: string; + description: string; + }; + lang: { + type: string; + description: string; + enum: string[]; + }; + username?: undefined; + quantity?: undefined; + stars?: undefined; + show_sender?: undefined; + }; + required: string[]; + }; + execute(params: { + ref_id?: string; + lang?: "ru" | "en"; + }, context: PluginContext): Promise<{ + success: boolean; + error: string; + data?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + fragment_order: Record | null; + message: string; + force_user_message?: undefined; + }; + error?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + message: string; + force_user_message: boolean; + fragment_order?: undefined; + }; + error?: undefined; + }>; +})[]; diff --git a/plugins/fragment-stars-plugin/dist/index.js b/plugins/fragment-stars-plugin/dist/index.js new file mode 100644 index 0000000..8e7c4e5 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/index.js @@ -0,0 +1,32 @@ +import { initSchema } from "./utils/order-repository.js"; +import { createTools } from "./utils/tool-definitions.js"; +export const manifest = { + name: "fragment-stars-plugin", + version: "1.0.0", + author: "d1nckache", + description: "Buy Telegram Stars through TON payment and Fragment API", + sdkVersion: ">=1.0.0", + defaultConfig: { + fragment_api_url: "http://127.0.0.1:8000/api/v1/stars", + fragment_api_timeout_ms: 240000, + payment_ttl_minutes: 15, + }, + secrets: { + fragment_api_token: { + required: true, + description: "Required token for X-Fragment-Api-Token header to Fragment Stars API", + }, + }, +}; +const activeChecks = new Set(); +let runtimeSdk = null; +export function migrate(db) { + initSchema(db); +} +export async function start(ctx) { +} +export const tools = (sdk) => { + runtimeSdk = sdk; + return createTools(sdk, activeChecks); +}; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/index.js.map b/plugins/fragment-stars-plugin/dist/index.js.map new file mode 100644 index 0000000..10a32b9 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG1D,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,WAAW;IACnB,WAAW,EAAE,yDAAyD;IACtE,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE;QACb,gBAAgB,EAAE,oCAAoC;QACtD,uBAAuB,EAAE,MAAM;QAC/B,mBAAmB,EAAE,EAAE;KACxB;IACD,OAAO,EAAE;QACP,kBAAkB,EAAE;YAClB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,sEAAsE;SACpF;KACF;CACF,CAAC;AAEF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;AACvC,IAAI,UAAU,GAAsB,IAAI,CAAC;AAEzC,MAAM,UAAU,OAAO,CAAC,EAAoB;IAC1C,UAAU,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAkB;AAE9C,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAe,EAAE,EAAE;IACvC,UAAU,GAAG,GAAG,CAAC;IACjB,OAAO,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACxC,CAAC,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/common.d.ts b/plugins/fragment-stars-plugin/dist/utils/common.d.ts new file mode 100644 index 0000000..5aa862d --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/common.d.ts @@ -0,0 +1,10 @@ +export declare function toNano(ton: number | string): string; +export declare function createRefId(senderId: string | number): string; +export declare function getConfig(context: { + pluginConfig?: Record; + config?: Record; +} | undefined, key: string, fallback: T): T; +export declare function getPluginConfig(sdk: { + pluginConfig?: Record; +} | undefined, key: string, fallback: T): T; +export declare function sleep(ms: number): Promise; diff --git a/plugins/fragment-stars-plugin/dist/utils/common.js b/plugins/fragment-stars-plugin/dist/utils/common.js new file mode 100644 index 0000000..dbbc129 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/common.js @@ -0,0 +1,19 @@ +const NANO = 1_000_000_000; +export function toNano(ton) { + return String(Math.round(Number(ton) * NANO)); +} +export function createRefId(senderId) { + return `stars-${senderId}-${Date.now()}`; +} +export function getConfig(context, key, fallback) { + const raw = context?.pluginConfig?.[key] ?? context?.config?.[key]; + return (raw === undefined ? fallback : raw); +} +export function getPluginConfig(sdk, key, fallback) { + const raw = sdk?.pluginConfig?.[key]; + return (raw === undefined ? fallback : raw); +} +export function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} +//# sourceMappingURL=common.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/common.js.map b/plugins/fragment-stars-plugin/dist/utils/common.js.map new file mode 100644 index 0000000..e51f245 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/common.js.map @@ -0,0 +1 @@ +{"version":3,"file":"common.js","sourceRoot":"","sources":["../../utils/common.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,aAAa,CAAC;AAE3B,MAAM,UAAU,MAAM,CAAC,GAAoB;IACzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAyB;IACnD,OAAO,SAAS,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,OAAiG,EACjG,GAAW,EACX,QAAW;IAEX,MAAM,GAAG,GAAG,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAM,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAA2D,EAC3D,GAAW,EACX,QAAW;IAEX,MAAM,GAAG,GAAG,GAAG,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAM,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts new file mode 100644 index 0000000..4d3506e --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts @@ -0,0 +1,73 @@ +import type { RuntimeSdk } from "./types.js"; +interface FragmentApiQuotePayload { + username: string; + quantity: number; + show_sender: boolean; +} +export interface FragmentApiPurchaseResult { + ok: boolean; + status: string; + message: string; + purchase_id: string; + ref_id: string; + req_id?: string | null; + cost_ton?: string | null; + tx_hash?: string | null; + tx_to?: string | null; + tx_amount_nano?: string | null; + refund_address?: string | null; + refund_amount_nano?: string | null; + error?: string | null; +} +export interface FragmentApiQuoteResult { + ok: boolean; + message: string; + username: string; + quantity: number; + fragment_cost_ton: string; + pay_amount_ton: string; + pay_amount_nano: string; +} +export interface FragmentApiCreateOrderPayload { + username: string; + quantity: number; + show_sender: boolean; + ref_id: string; + fee_address: string; +} +export interface FragmentApiCreateOrderResult { + ok: boolean; + message: string; + purchase_id: string; + ref_id: string; + pay_to_address: string; + pay_deeplink: string; + fragment_cost_ton: string; + pay_amount_ton: string; + pay_amount_nano: string; +} +export interface FragmentApiProcessOrderPayload { + ref_id: string; + fee_address?: string; +} +export interface FragmentApiProcessOrderResult { + ok: boolean; + status: string; + message: string; + purchase_id: string; + ref_id: string; + req_id?: string | null; + cost_ton?: string | null; + tx_hash?: string | null; + tx_to?: string | null; + tx_amount_nano?: string | null; + refund_address?: string | null; + refund_amount_nano?: string | null; + payment_tx?: string | null; + payment_from?: string | null; + error?: string | null; +} +export declare function executeFragmentQuote(sdk: RuntimeSdk, payload: FragmentApiQuotePayload): Promise; +export declare function executeFragmentCreateOrder(sdk: RuntimeSdk, payload: FragmentApiCreateOrderPayload): Promise; +export declare function executeFragmentProcessOrder(sdk: RuntimeSdk, payload: FragmentApiProcessOrderPayload): Promise; +export {}; diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js new file mode 100644 index 0000000..9dc2463 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js @@ -0,0 +1,195 @@ +import { getPluginConfig } from "./common.js"; +function getStarsBaseUrlFromSdk(sdk) { + const raw = String(getPluginConfig(sdk, "fragment_api_url", "http://127.0.0.1:8000/api/v1/stars")); + const trimmed = raw.replace(/\/$/, ""); + if (trimmed.endsWith("/purchase")) + return trimmed.slice(0, -"/purchase".length); + if (trimmed.endsWith("/quote")) + return trimmed.slice(0, -"/quote".length); + return trimmed; +} +function getApiTimeoutMsFromSdk(sdk) { + return Number(getPluginConfig(sdk, "fragment_api_timeout_ms", 240000)); +} +function requireApiTokenFromSdk(sdk) { + const rawSecret = sdk.secrets?.get("fragment_api_token"); + const tokenFromSecrets = rawSecret ? String(rawSecret).trim() : ""; + if (tokenFromSecrets) { + return tokenFromSecrets; + } + const rawConfig = sdk.pluginConfig?.fragment_api_token; + const tokenFromConfig = typeof rawConfig === "string" ? rawConfig.trim() : ""; + if (tokenFromConfig) { + return tokenFromConfig; + } + throw new Error("fragment_api_token is required to call Fragment API (set plugin secret or plugin config)"); +} +function tokenHint(token) { + if (!token) + return "none"; + const trimmed = token.trim(); + if (!trimmed) + return "empty"; + const prefix = trimmed.slice(0, 2); + const suffix = trimmed.slice(-2); + return `${prefix}…${suffix} (len=${trimmed.length})`; +} +function tokenSource(sdk) { + if (sdk.secrets?.has("fragment_api_token")) + return "secrets"; + if (sdk.pluginConfig?.fragment_api_token !== undefined) + return "pluginConfig"; + return "none"; +} +export async function executeFragmentQuote(sdk, payload) { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/quote`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + console.log("Fragment API request ->", { + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + payload, + }); + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + const rawText = await response.text(); + console.log("Fragment API response <-", { url: apiUrl, status: response.status, body: rawText }); + let parsed = {}; + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } + catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn("quote failed", JSON.stringify({ + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, null, 0)); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + return parsed; + } + catch (error) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } + finally { + clearTimeout(timeout); + } +} +export async function executeFragmentCreateOrder(sdk, payload) { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + const rawText = await response.text(); + let parsed = {}; + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } + catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn("order create failed", JSON.stringify({ + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, null, 0)); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + return parsed; + } + catch (error) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } + finally { + clearTimeout(timeout); + } +} +export async function executeFragmentProcessOrder(sdk, payload) { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders/process`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + const rawText = await response.text(); + let parsed = {}; + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } + catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn("order process failed", JSON.stringify({ + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, null, 0)); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + return parsed; + } + catch (error) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } + finally { + clearTimeout(timeout); + } +} +//# sourceMappingURL=fragment-api-service.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map new file mode 100644 index 0000000..6ca6db9 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fragment-api-service.js","sourceRoot":"","sources":["../../utils/fragment-api-service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts b/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts new file mode 100644 index 0000000..ce493f3 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts @@ -0,0 +1,7 @@ +import type { CheckingOrderRow, Database, OrderRecord } from "./types.js"; +export declare function initSchema(db: Database): void; +export declare function getOrderByRef(db: Database, refId: string): OrderRecord | null; +export declare function getLatestActiveOrderForUser(db: Database, chatId: string, senderId: string): OrderRecord | null; +export declare function upsertOrder(db: Database, order: OrderRecord): void; +export declare function updateOrderStatus(db: Database, refId: string, status: OrderRecord["status"], updates?: Partial): OrderRecord | null; +export declare function listCheckingOrders(db: Database): CheckingOrderRow[]; diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.js b/plugins/fragment-stars-plugin/dist/utils/order-repository.js new file mode 100644 index 0000000..ed3e19f --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-repository.js @@ -0,0 +1,142 @@ +export function initSchema(db) { + db.exec(` + CREATE TABLE IF NOT EXISTS used_transactions ( + tx_hash TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + amount REAL NOT NULL, + game_type TEXT NOT NULL, + used_at INTEGER NOT NULL + ) + `); + db.exec(` + CREATE TABLE IF NOT EXISTS stars_orders ( + ref_id TEXT PRIMARY KEY, + chat_id TEXT NOT NULL, + sender_id TEXT NOT NULL, + username TEXT NOT NULL, + quantity INTEGER NOT NULL, + base_amount_ton REAL NOT NULL DEFAULT 0, + amount_ton REAL NOT NULL, + lang TEXT, + refund_address TEXT, + refund_amount_nano TEXT, + platform_fee_percent REAL NOT NULL DEFAULT 0, + fragment_fee_percent REAL NOT NULL DEFAULT 0, + show_sender INTEGER NOT NULL, + status TEXT NOT NULL, + payment_tx TEXT, + payment_from TEXT, + fragment_order_json TEXT, + error TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + `); + ensureColumn(db, "stars_orders", "base_amount_ton", "REAL NOT NULL DEFAULT 0"); + ensureColumn(db, "stars_orders", "lang", "TEXT"); + ensureColumn(db, "stars_orders", "refund_address", "TEXT"); + ensureColumn(db, "stars_orders", "refund_amount_nano", "TEXT"); + ensureColumn(db, "stars_orders", "platform_fee_percent", "REAL NOT NULL DEFAULT 0"); + ensureColumn(db, "stars_orders", "fragment_fee_percent", "REAL NOT NULL DEFAULT 0"); +} +function ensureColumn(db, tableName, columnName, columnSpec) { + const columns = db.prepare(`PRAGMA table_info(${tableName})`).all(); + const exists = columns.some((c) => c.name === columnName); + if (!exists) { + db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSpec}`); + } +} +function mapOrderRow(row) { + if (!row) { + return null; + } + return { + refId: row.ref_id, + chatId: row.chat_id, + senderId: row.sender_id, + username: row.username, + quantity: Number(row.quantity), + baseAmountTon: Number(row.base_amount_ton), + amountTon: Number(row.amount_ton), + lang: (row.lang === "en" ? "en" : row.lang === "ru" ? "ru" : null) || undefined, + refundAddress: row.refund_address || null, + refundAmountNano: row.refund_amount_nano || null, + platformFeePercent: Number(row.platform_fee_percent), + fragmentFeePercent: Number(row.fragment_fee_percent), + show_sender: Boolean(row.show_sender), + status: row.status, + paymentTx: row.payment_tx || null, + paymentFrom: row.payment_from || null, + fragmentOrder: row.fragment_order_json ? JSON.parse(row.fragment_order_json) : null, + error: row.error || null, + createdAt: row.created_at, + updatedAt: row.updated_at, + }; +} +export function getOrderByRef(db, refId) { + const row = db.prepare("SELECT * FROM stars_orders WHERE ref_id = ?").get(refId); + return mapOrderRow(row); +} +export function getLatestActiveOrderForUser(db, chatId, senderId) { + const row = db + .prepare(` + SELECT * + FROM stars_orders + WHERE chat_id = ? + AND sender_id = ? + AND status IN ('pending', 'checking', 'paid') + ORDER BY updated_at DESC + LIMIT 1 + `) + .get(String(chatId), String(senderId)); + return mapOrderRow(row); +} +export function upsertOrder(db, order) { + const now = new Date().toISOString(); + db.prepare(` + INSERT INTO stars_orders ( + ref_id, chat_id, sender_id, username, quantity, base_amount_ton, amount_ton, + lang, + refund_address, refund_amount_nano, + platform_fee_percent, fragment_fee_percent, show_sender, status, + payment_tx, payment_from, fragment_order_json, error, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(ref_id) DO UPDATE SET + chat_id = excluded.chat_id, + sender_id = excluded.sender_id, + username = excluded.username, + quantity = excluded.quantity, + base_amount_ton = excluded.base_amount_ton, + amount_ton = excluded.amount_ton, + lang = excluded.lang, + refund_address = excluded.refund_address, + refund_amount_nano = excluded.refund_amount_nano, + platform_fee_percent = excluded.platform_fee_percent, + fragment_fee_percent = excluded.fragment_fee_percent, + show_sender = excluded.show_sender, + status = excluded.status, + payment_tx = excluded.payment_tx, + payment_from = excluded.payment_from, + fragment_order_json = excluded.fragment_order_json, + error = excluded.error, + updated_at = excluded.updated_at + `).run(order.refId, String(order.chatId), String(order.senderId), order.username, Number(order.quantity), Number(order.baseAmountTon || order.amountTon), Number(order.amountTon), order.lang || null, order.refundAddress || null, order.refundAmountNano || null, Number(order.platformFeePercent || 0), Number(order.fragmentFeePercent || 0), order.show_sender ? 1 : 0, order.status, order.paymentTx || null, order.paymentFrom || null, order.fragmentOrder ? JSON.stringify(order.fragmentOrder) : null, order.error || null, order.createdAt || now, now); +} +export function updateOrderStatus(db, refId, status, updates = {}) { + const current = getOrderByRef(db, refId); + if (!current) { + return null; + } + const next = { + ...current, + ...updates, + status, + updatedAt: new Date().toISOString(), + }; + upsertOrder(db, next); + return next; +} +export function listCheckingOrders(db) { + return db.prepare("SELECT ref_id, chat_id FROM stars_orders WHERE status = 'checking'").all(); +} +//# sourceMappingURL=order-repository.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map b/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map new file mode 100644 index 0000000..7e930db --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map @@ -0,0 +1 @@ +{"version":3,"file":"order-repository.js","sourceRoot":"","sources":["../../utils/order-repository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts b/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts new file mode 100644 index 0000000..44f6dc2 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts @@ -0,0 +1,2 @@ +import type { RuntimeSdk } from "./types.js"; +export declare function setOrderStatus(sdk: RuntimeSdk, refId: string, status: string, extra?: Record, ttlMs?: number): void; diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.js b/plugins/fragment-stars-plugin/dist/utils/order-status.js new file mode 100644 index 0000000..81331fe --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-status.js @@ -0,0 +1,12 @@ +export function setOrderStatus(sdk, refId, status, extra = {}, ttlMs = 24 * 60 * 60 * 1000) { + const current = sdk.storage.get(`order:${refId}`) || {}; + const next = { + ...current, + ...extra, + refId, + status, + updatedAt: new Date().toISOString(), + }; + sdk.storage.set(`order:${refId}`, next, { ttl: ttlMs }); +} +//# sourceMappingURL=order-status.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.js.map b/plugins/fragment-stars-plugin/dist/utils/order-status.js.map new file mode 100644 index 0000000..6db1752 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/order-status.js.map @@ -0,0 +1 @@ +{"version":3,"file":"order-status.js","sourceRoot":"","sources":["../../utils/order-status.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAC5B,GAAe,EACf,KAAa,EACb,MAAc,EACd,QAAiC,EAAE,EACnC,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IAE3B,MAAM,OAAO,GAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAoC,IAAI,EAAE,CAAC;IAC5F,MAAM,IAAI,GAAG;QACX,GAAG,OAAO;QACV,GAAG,KAAK;QACR,KAAK;QACL,MAAM;QACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1D,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts new file mode 100644 index 0000000..9797ab6 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts @@ -0,0 +1,103 @@ +import type { PluginContext, RuntimeSdk } from "./types.js"; +export declare function createTools(sdk: RuntimeSdk, activeChecks: Set): ({ + name: string; + description: string; + parameters: { + type: string; + properties: { + username: { + type: string; + description: string; + }; + quantity: { + type: string; + description: string; + }; + stars: { + type: string; + description: string; + }; + show_sender: { + type: string; + description: string; + }; + lang: { + type: string; + description: string; + enum: string[]; + }; + ref_id?: undefined; + }; + required: string[]; + }; + execute(params: { + username: string; + quantity?: number; + stars?: number; + show_sender?: boolean; + lang?: "ru" | "en"; + }, context: PluginContext): Promise<{ + success: boolean; + error: string; + data?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + message: string; + force_user_message: boolean; + }; + error?: undefined; + }>; +} | { + name: string; + description: string; + parameters: { + type: string; + properties: { + ref_id: { + type: string; + description: string; + }; + lang: { + type: string; + description: string; + enum: string[]; + }; + username?: undefined; + quantity?: undefined; + stars?: undefined; + show_sender?: undefined; + }; + required: string[]; + }; + execute(params: { + ref_id?: string; + lang?: "ru" | "en"; + }, context: PluginContext): Promise<{ + success: boolean; + error: string; + data?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + fragment_order: Record | null; + message: string; + force_user_message?: undefined; + }; + error?: undefined; + } | { + success: boolean; + data: { + ref_id: string; + status: string; + message: string; + force_user_message: boolean; + fragment_order?: undefined; + }; + error?: undefined; + }>; +})[]; diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js new file mode 100644 index 0000000..1b60e1a --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js @@ -0,0 +1,376 @@ +import { createRefId, getPluginConfig, sleep } from "./common.js"; +import { setOrderStatus } from "./order-status.js"; +import { getLatestActiveOrderForUser, getOrderByRef as loadOrderByRef, updateOrderStatus as setDbOrderStatus, upsertOrder as saveOrder, } from "./order-repository.js"; +import { executeFragmentCreateOrder, executeFragmentProcessOrder } from "./fragment-api-service.js"; +function roundTon(value) { + return Number(Number(value).toFixed(9)); +} +function resolveLang(sdk, lang) { + const explicit = typeof lang === "string" ? lang.trim().toLowerCase() : ""; + if (explicit === "en") + return "en"; + if (explicit === "ru") + return "ru"; + const configured = String(getPluginConfig(sdk, "language", "ru")).trim().toLowerCase(); + return configured === "en" ? "en" : "ru"; +} +function formatFinalResultMessage(lang, refId, result) { + return lang === "en" + ? `Payment confirmed. Order sent to Fragment; wait for Stars delivery.\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id || "-")}\n` + + `tx_hash: ${String(result?.tx_hash || "-")}` + : `Платёж подтверждён, заказ отправлен в Fragment, ожидайте получение звёзд\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id || "-")}\n` + + `tx_hash: ${String(result?.tx_hash || "-")}`; +} +async function pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang) { + const feeAddress = sdk.ton.getAddress(); + const startedAt = Date.now(); + const maxDurationMs = 15 * 60_000; + const pollIntervalMs = 5_000; + const progressUpdateEveryMs = 30_000; + let lastProgressAt = 0; + const updateText = async (text) => { + if (messageId && sdk.telegram.editMessage) { + await sdk.telegram.editMessage(chatId, messageId, text); + } + else { + await sdk.telegram.sendMessage(chatId, text); + } + }; + try { + while (Date.now() - startedAt < maxDurationMs) { + let result; + try { + console.log("Fragment processOrder ->", { ref_id: refId, fee_address: feeAddress || undefined }); + result = await executeFragmentProcessOrder(sdk, { + ref_id: refId, + fee_address: feeAddress || undefined, + }); + console.log("Fragment processOrder <-", { ref_id: refId, result }); + } + catch { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText(lang === "en" + ? `Payment check service is temporarily unavailable. Retrying...\nref_id: ${refId}` + : `Сервис проверки оплаты временно недоступен. Продолжаю попытки...\nref_id: ${refId}`); + } + await sleep(pollIntervalMs); + continue; + } + if (result?.ok) { + await updateText(formatFinalResultMessage(lang, refId, result)); + return; + } + const status = String(result?.status || "awaiting_payment"); + if (status === "awaiting_payment") { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText(lang === "en" ? `Checking payment for order ${refId}...` : `Проверяю оплату по заказу ${refId}...`); + } + await sleep(pollIntervalMs); + continue; + } + const errorText = String(result?.error || result?.message || "unknown error"); + await updateText(lang === "en" ? `Failed to process order ${refId}: ${errorText}` : `Не удалось обработать заказ ${refId}: ${errorText}`); + return; + } + await updateText(lang === "en" + ? `Payment for order ${refId} was not found within 15 minutes.\n` + + `If you paid — wait a bit and then send: "check payment ${refId}".` + : `Оплата по заказу ${refId} не найдена за 15 минут.\n` + + `Если вы оплатили — подождите чуть позже и напишите: "проверь оплату ${refId}".`); + } + finally { + activeChecks.delete(refId); + } +} +export function createTools(sdk, activeChecks) { + return [ + { + name: "fragment_stars_create_payment", + description: "Шаг 1/2. Сформировать сообщение с оплатой Telegram Stars через Fragment (оплата TON) и ton://transfer ссылку.\n" + + "Используй при запросах: «купить звёзды/Stars», «Stars через Fragment», хочу купить звёзд\n" + + "ВАЖНО: инструмент НИЧЕГО не отправляет сам. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + parameters: { + type: "object", + properties: { + username: { type: "string", description: "Telegram username without @ (кому покупаем звёзды)" }, + quantity: { type: "number", description: "Сколько звёзд купить (минимум 50)" }, + stars: { type: "number", description: "Алиас для quantity" }, + show_sender: { type: "boolean", description: "Показывать отправителя в Fragment (по умолчанию false)" }, + lang: { + type: "string", + description: "ОПРЕДЕЛИ ЯЗЫК ПОЛЬЗОВАТЕЛЯ. Если он пишет на русском — 'ru', если на английском — 'en'.", + enum: ["ru", "en"], + }, + }, + required: ["username", "lang"], + }, + async execute(params, context) { + const rawQuantity = params.quantity ?? params.stars; + if (rawQuantity === undefined || rawQuantity === null) { + return { + success: false, + error: "quantity is required (you can also pass it as stars)", + }; + } + const quantity = Number(rawQuantity); + if (!Number.isFinite(quantity) || quantity <= 0) { + return { success: false, error: "quantity must be a positive number" }; + } + if (quantity < 50) { + return { + success: false, + error: resolveLang(sdk, params.lang) === "en" + ? "Stars amount must be at least 50" + : "Количество звёзд должно быть не меньше 50", + }; + } + const refId = createRefId(String(context.senderId ?? "unknown")); + const feeAddress = sdk.ton.getAddress(); + if (!feeAddress) { + return { + success: false, + error: resolveLang(sdk, params.lang) === "en" + ? "TON wallet address is not available in this runtime" + : "Адрес TON кошелька недоступен в этом окружении", + }; + } + let orderCreate; + try { + console.log("Fragment createOrder ->", { + payload: { + username: String(params.username).replace(/^@/, ""), + quantity, + show_sender: Boolean(params.show_sender), + ref_id: refId, + fee_address: feeAddress, + }, + }); + orderCreate = await executeFragmentCreateOrder(sdk, { + username: String(params.username).replace(/^@/, ""), + quantity, + show_sender: Boolean(params.show_sender), + ref_id: refId, + fee_address: feeAddress, + }); + console.log("Fragment createOrder <-", { ref_id: refId, result: orderCreate }); + } + catch { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: resolveLang(sdk, params.lang) === "en" + ? `Payment service is temporarily unavailable (order creation failed). Try again in 1–2 minutes.\n` + + `If it keeps failing — contact the administrator.\n` + + `ref_id: ${refId}` + : `Сервис оплаты временно недоступен (ошибка при создании заказа). Попробуйте ещё раз через 1–2 минуты.\n` + + `Если ошибка повторяется — напишите администратору.\n` + + `ref_id: ${refId}`, + force_user_message: true, + }, + }; + } + if (!orderCreate.ok) { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: resolveLang(sdk, params.lang) === "en" + ? `Failed to create order: ${orderCreate.message || "unknown error"}` + : `Не удалось создать заказ: ${orderCreate.message || "unknown error"}`, + force_user_message: true, + }, + }; + } + const baseAmountTon = roundTon(Number(orderCreate.fragment_cost_ton)); + const amountTon = roundTon(Number(orderCreate.pay_amount_ton)); + const amountNano = String(orderCreate.pay_amount_nano || "").trim(); + if (!amountNano || !/^\d+$/.test(amountNano)) { + return { success: false, error: "Invalid pay_amount_nano from API" }; + } + const payToAddress = "UQDFOnNC_cgSJqbpH_k9hH8OqkuxvBeO5LUlE_x8wsQitGVJ"; + console.log("Generated payment details", { ref_id: refId, amountTon, amountNano, payToAddress, deepLinkFromApi: orderCreate.pay_deeplink }); + const deepLinkRawFromApi = String(orderCreate.pay_deeplink || "").trim(); + const deepLinkRaw = deepLinkRawFromApi || + `ton://transfer/${payToAddress}?amount=${amountNano}&text=${encodeURIComponent(refId)}`; + console.log("Resolved deep link", { ref_id: refId, deepLinkRaw }); + if (!deepLinkRaw) { + return { success: false, error: "Invalid pay_deeplink from API" }; + } + const lang = params.lang; + const paymentDetailsRu = `\nДетали платежа\n` + + `Куда (адрес): \`${payToAddress}\`\n` + + `Сумма: ${amountTon} TON\n` + + `Комментарий (memo): \`${refId}\`\n`; + const paymentDetailsEn = `\nPayment details\n` + + `To (address): \`${payToAddress}\`\n` + + `Amount: ${amountTon} TON\n` + + `Comment (memo): \`${refId}\`\n`; + console.log("Final payment details", { ref_id: refId, paymentDetailsRu, paymentDetailsEn }); + const order = { + refId, + chatId: String(context.chatId), + senderId: String(context.senderId), + username: String(params.username).replace(/^@/, ""), + quantity, + baseAmountTon, + amountTon, + lang, + refundAddress: null, + refundAmountNano: null, + platformFeePercent: 1, + fragmentFeePercent: 0, + show_sender: Boolean(params.show_sender), + status: "pending", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + saveOrder(sdk.db, order); + const ttlMs = Number(getPluginConfig(sdk, "payment_ttl_minutes", 15)) * 60_000; + setOrderStatus(sdk, refId, "pending", order, ttlMs); + const deepLink = deepLinkRaw; + const labels = lang === "en" ? { + header: "📦 *Order: Telegram Stars*", + account: "👤 *Account:*", + quantity: "⭐️ *Quantity:*", + detailsHeader: "💳 *Payment details:*", + address: "Address:", + amount: "Amount :", + memo: "Memo :", + action: "🔗 Open payment link" + } : { + header: "📦 *Заказ: Telegram Stars*", + account: "👤 *Аккаунт:*", + quantity: "⭐️ *Количество:*", + detailsHeader: "💳 *Реквизиты для оплаты:*", + address: "Адрес :", + amount: "Сумма :", + memo: "Memo :", + action: "🔗 Открыть ссылку на оплату" + }; + const text = ` + ${labels.header} + ━━━━━━━━━━━━━━━━━━━━ + ${labels.account} @${order.username} + ${labels.quantity} ${quantity} + + ${labels.detailsHeader} + \`${labels.address}\` \`${payToAddress}\` + \`${labels.amount}\` \`${amountTon} TON\` + \`${labels.memo}\` \`${refId}\` + + ${labels.action} + `.trim(); + return { + success: true, + data: { + ref_id: refId, + status: "pending", + message: text, + force_user_message: true, + }, + }; + }, + }, + { + name: "fragment_stars_confirm_payment", + description: "Шаг 2/2. Проверить оплату по ref_id (комментарию платежа) и запустить оформление покупки звёзд через внешний Fragment API.\n" + + "Используй, когда пользователь пишет: «проверь оплату », «я оплатил», «я отправил». 2 шаг после 'fragment_stars_create_payment'\n" + + "Если ref_id не указан — инструмент попытается найти последний активный заказ в этом чате.\n" + + "ВАЖНО: не вызывай ton_my_transactions. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + parameters: { + type: "object", + properties: { + ref_id: { type: "string", description: "ref_id из шага 1 (можно не указывать, если пользователь просто «я оплатил»)" }, + lang: { + type: "string", + description: "Language for the message: ru | en (default: order.lang or plugin config language)", + enum: ["ru", "en"], + }, + }, + required: ["lang"], + }, + async execute(params, context) { + const explicitRefId = typeof params.ref_id === "string" ? params.ref_id.trim() : ""; + const inferredOrder = !explicitRefId && context.chatId && context.senderId + ? getLatestActiveOrderForUser(sdk.db, String(context.chatId), String(context.senderId)) + : null; + const refId = explicitRefId || inferredOrder?.refId || ""; + if (!refId) { + return { + success: false, + error: resolveLang(sdk, params.lang) === "en" + ? 'ref_id is required. Send: "check payment " (ref_id is shown in the payment message).' + : 'ref_id is required. Send: "проверь оплату " (ref_id is shown in the payment message).', + }; + } + const order = loadOrderByRef(sdk.db, refId); + if (!order) { + return { + success: false, + error: resolveLang(sdk, params.lang || order?.lang) === "en" + ? `Order not found or expired. Create a new payment link.` + : `Заказ не найден или истёк. Создайте новую ссылку на оплату.`, + }; + } + const lang = resolveLang(sdk, params.lang || order.lang); + if (order.status === "ordered") { + const text = lang === "en" + ? `Order ${refId} is already placed. If Stars haven't arrived yet — wait a couple of minutes.` + : `Заказ ${refId} уже оформлен. Если звёзды ещё не пришли — подождите пару минут.`; + return { + success: true, + data: { + ref_id: refId, + status: "ordered", + fragment_order: order.fragmentOrder || null, + message: text, + }, + }; + } + const feeAddress = sdk.ton.getAddress(); + if (!feeAddress) { + return { + success: false, + error: lang === "en" + ? `TON wallet address is not available in this runtime.` + : `Адрес TON кошелька недоступен в этом окружении.`, + }; + } + if (activeChecks.has(refId) || order.status === "checking") { + const text = lang === "en" + ? `Payment check for order ${refId} is already running. I'll send the result in a separate message.` + : `Проверка оплаты по заказу ${refId} уже идёт. Я пришлю результат отдельным сообщением.`; + return { success: true, data: { ref_id: refId, status: "checking", message: text, force_user_message: true } }; + } + setDbOrderStatus(sdk.db, refId, "checking", { error: null }); + setOrderStatus(sdk, refId, "checking", { error: null }); + activeChecks.add(refId); + const chatId = String(context.chatId); + const startMessage = lang === "en" + ? `Started background payment check for order ${refId} (up to 15 minutes). I'll send the result in a separate message.` + : `Запустил фоновую проверку оплаты по заказу ${refId} (до 15 минут). Пришлю результат отдельным сообщением.`; + let messageId = null; + void pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang); + return { + success: true, + data: { + ref_id: refId, + status: "checking", + message: startMessage, + force_user_message: true, + }, + }; + }, + }, + ]; +} +//# sourceMappingURL=tool-definitions.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map new file mode 100644 index 0000000..98093c8 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map @@ -0,0 +1 @@ +{"version":3,"file":"tool-definitions.js","sourceRoot":"","sources":["../../utils/tool-definitions.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/types.d.ts b/plugins/fragment-stars-plugin/dist/utils/types.d.ts new file mode 100644 index 0000000..fb21e58 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/types.d.ts @@ -0,0 +1,108 @@ +export type OrderStatus = "pending" | "checking" | "paid" | "ordered" | "failed"; +export interface OrderRecord { + refId: string; + chatId: string; + senderId: string; + username: string; + quantity: number; + baseAmountTon: number; + amountTon: number; + lang?: "ru" | "en"; + refundAddress?: string | null; + refundAmountNano?: string | null; + platformFeePercent: number; + fragmentFeePercent: number; + show_sender: boolean; + status: OrderStatus; + paymentTx?: string | null; + paymentFrom?: string | null; + fragmentOrder?: Record | null; + error?: string | null; + createdAt?: string; + updatedAt?: string; +} +export interface CheckingOrderRow { + ref_id: string; + chat_id: string; +} +export interface SqlStatement { + all(...args: unknown[]): any[]; + get(...args: unknown[]): any; + run(...args: unknown[]): void; +} +export interface Database { + exec(sql: string): void; + prepare(sql: string): SqlStatement; +} +export interface RuntimeSdk { + config?: Record; + pluginConfig?: Record; + secrets?: { + get(key: string): string | undefined; + require(key: string): string; + has(key: string): boolean; + }; + db: Database; + storage: { + get(key: string): unknown; + set(key: string, value: unknown, options?: { + ttl?: number; + }): void; + }; + telegram: { + sendMessage(chatId: string, text: string, opts?: unknown): Promise; + editMessage?(chatId: string, messageId: number, text: string, opts?: unknown): Promise; + }; + ton: { + getAddress(): string | null; + getTransactions(address: string, limit?: number): Promise>; + verifyPayment(payload: { + amount: number; + memo: string; + gameType: string; + maxAgeMinutes: number; + }): Promise<{ + verified: boolean; + error?: string; + txHash?: string; + playerWallet?: string; + }>; + sendTON(address: string, amountTon: number, memo: string): Promise<{ + txHash?: string; + hash?: string; + }>; + }; + log: { + info(...args: unknown[]): void; + warn(...args: unknown[]): void; + error(...args: unknown[]): void; + debug(...args: unknown[]): void; + }; +} +export interface PluginContext { + chatId?: string | number; + senderId?: string | number; + config?: Record; + pluginConfig?: Record; + secrets?: Record; +} +export interface PluginManifest { + name: string; + version: string; + author: string; + description: string; + sdkVersion: string; + defaultConfig: Record; + secrets: Record; +} diff --git a/plugins/fragment-stars-plugin/dist/utils/types.js b/plugins/fragment-stars-plugin/dist/utils/types.js new file mode 100644 index 0000000..718fd38 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/types.js @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/types.js.map b/plugins/fragment-stars-plugin/dist/utils/types.js.map new file mode 100644 index 0000000..6c1e324 --- /dev/null +++ b/plugins/fragment-stars-plugin/dist/utils/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../../utils/types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/index.js b/plugins/fragment-stars-plugin/index.js new file mode 100644 index 0000000..5aa671b --- /dev/null +++ b/plugins/fragment-stars-plugin/index.js @@ -0,0 +1,10 @@ +import * as mod from "./dist/index.js"; + +export const manifest = mod.manifest; +export const tools = mod.tools; +export const migrate = mod.migrate; +export const start = mod.start; +export const stop = mod.stop; +export const onMessage = mod.onMessage; +export const onCallbackQuery = mod.onCallbackQuery; + diff --git a/plugins/fragment-stars-plugin/index.ts b/plugins/fragment-stars-plugin/index.ts new file mode 100644 index 0000000..462ca22 --- /dev/null +++ b/plugins/fragment-stars-plugin/index.ts @@ -0,0 +1,38 @@ +import { initSchema } from "./utils/order-repository.js"; +import { createTools } from "./utils/tool-definitions.js"; +import type { PluginContext, PluginManifest, RuntimeSdk } from "./utils/types.js"; + +export const manifest: PluginManifest = { + name: "fragment-stars-plugin", + version: "1.0.0", + author: "d1nckache", + description: "Buy Telegram Stars through TON payment and Fragment API", + sdkVersion: ">=1.0.0", + defaultConfig: { + fragment_api_url: "http://127.0.0.1:8000/api/v1/stars", + fragment_api_timeout_ms: 240000, + payment_ttl_minutes: 15, + }, + secrets: { + fragment_api_token: { + required: true, + description: "Required token for X-Fragment-Api-Token header to Fragment Stars API", + }, + }, +}; + +const activeChecks = new Set(); +let runtimeSdk: RuntimeSdk | null = null; + +export function migrate(db: RuntimeSdk["db"]): void { + initSchema(db); +} + +export async function start(ctx: PluginContext): Promise { + +} + +export const tools = (sdk: RuntimeSdk) => { + runtimeSdk = sdk; + return createTools(sdk, activeChecks); +}; diff --git a/plugins/fragment-stars-plugin/package-lock.json b/plugins/fragment-stars-plugin/package-lock.json new file mode 100644 index 0000000..9d40822 --- /dev/null +++ b/plugins/fragment-stars-plugin/package-lock.json @@ -0,0 +1,471 @@ +{ + "name": "fragment-stars-plugin", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "fragment-stars-plugin", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@ton/core": "^0.63.1", + "@ton/crypto": "^3.3.0", + "@ton/ton": "^16.2.2", + "buffer": "^6.0.3" + }, + "devDependencies": { + "@types/node": "^25.3.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@ton/core": { + "version": "0.63.1", + "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.63.1.tgz", + "integrity": "sha512-hDWMjlKzc18W2E4OeV3hUP8ohRJNHPD4Wd1+AQJj8zshZyCRT0usrvnExgbNUTo/vntDqCGMzgYWbXxyaA+L4g==", + "license": "MIT", + "peerDependencies": { + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@ton/crypto": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz", + "integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==", + "license": "MIT", + "dependencies": { + "@ton/crypto-primitives": "2.1.0", + "jssha": "3.2.0", + "tweetnacl": "1.0.3" + } + }, + "node_modules/@ton/crypto-primitives": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz", + "integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==", + "license": "MIT", + "dependencies": { + "jssha": "3.2.0" + } + }, + "node_modules/@ton/ton": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-16.2.2.tgz", + "integrity": "sha512-yEOw4IW3gpRZxJAcILMI4dQ1d5/eAAbD2VU/Iwc6z7f2jt1mLDWVED8yn2vLNucQfZr+1eaqYHLztYVFZ7PKmw==", + "license": "MIT", + "dependencies": { + "axios": "^1.6.7", + "dataloader": "^2.0.0", + "zod": "^3.21.4" + }, + "peerDependencies": { + "@ton/core": ">=0.63.0 <1.0.0", + "@ton/crypto": ">=3.2.0" + } + }, + "node_modules/@ton/ton/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@types/node": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", + "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/jssha": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz", + "integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/plugins/fragment-stars-plugin/package.json b/plugins/fragment-stars-plugin/package.json new file mode 100644 index 0000000..c16f994 --- /dev/null +++ b/plugins/fragment-stars-plugin/package.json @@ -0,0 +1,23 @@ +{ + "type": "module", + "dependencies": { + "@ton/core": "^0.63.1", + "@ton/crypto": "^3.3.0", + "@ton/ton": "^16.2.2", + "buffer": "^6.0.3" + }, + "name": "fragment-stars-plugin", + "version": "1.0.0", + "main": "index.js", + "devDependencies": { + "@types/node": "^25.3.0", + "typescript": "^5.9.3" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "d1nckache", + "license": "ISC", + "description": "" +} diff --git a/plugins/fragment-stars-plugin/tsconfig.json b/plugins/fragment-stars-plugin/tsconfig.json new file mode 100644 index 0000000..03c9030 --- /dev/null +++ b/plugins/fragment-stars-plugin/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "declaration": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": ".", + "types": ["node"] + }, + "include": ["index.ts", "utils/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/plugins/fragment-stars-plugin/utils/common.ts b/plugins/fragment-stars-plugin/utils/common.ts new file mode 100644 index 0000000..01ed0ea --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/common.ts @@ -0,0 +1,31 @@ +const NANO = 1_000_000_000; + +export function toNano(ton: number | string): string { + return String(Math.round(Number(ton) * NANO)); +} + +export function createRefId(senderId: string | number): string { + return `stars-${senderId}-${Date.now()}`; +} + +export function getConfig( + context: { pluginConfig?: Record; config?: Record } | undefined, + key: string, + fallback: T, +): T { + const raw = context?.pluginConfig?.[key] ?? context?.config?.[key]; + return (raw === undefined ? fallback : raw) as T; +} + +export function getPluginConfig( + sdk: { pluginConfig?: Record } | undefined, + key: string, + fallback: T, +): T { + const raw = sdk?.pluginConfig?.[key]; + return (raw === undefined ? fallback : raw) as T; +} + +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/plugins/fragment-stars-plugin/utils/fragment-api-service.ts b/plugins/fragment-stars-plugin/utils/fragment-api-service.ts new file mode 100644 index 0000000..9430b00 --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/fragment-api-service.ts @@ -0,0 +1,316 @@ +import { getPluginConfig } from "./common.js"; +import type { RuntimeSdk } from "./types.js"; + +interface FragmentApiQuotePayload { + username: string; + quantity: number; + show_sender: boolean; +} + +export interface FragmentApiPurchaseResult { + ok: boolean; + status: string; + message: string; + purchase_id: string; + ref_id: string; + req_id?: string | null; + cost_ton?: string | null; + tx_hash?: string | null; + tx_to?: string | null; + tx_amount_nano?: string | null; + refund_address?: string | null; + refund_amount_nano?: string | null; + error?: string | null; +} + +export interface FragmentApiQuoteResult { + ok: boolean; + message: string; + username: string; + quantity: number; + fragment_cost_ton: string; + pay_amount_ton: string; + pay_amount_nano: string; +} + +export interface FragmentApiCreateOrderPayload { + username: string; + quantity: number; + show_sender: boolean; + ref_id: string; + fee_address: string; +} + +export interface FragmentApiCreateOrderResult { + ok: boolean; + message: string; + purchase_id: string; + ref_id: string; + pay_to_address: string; + pay_deeplink: string; + fragment_cost_ton: string; + pay_amount_ton: string; + pay_amount_nano: string; +} + +export interface FragmentApiProcessOrderPayload { + ref_id: string; + fee_address?: string; +} + +export interface FragmentApiProcessOrderResult { + ok: boolean; + status: string; + message: string; + purchase_id: string; + ref_id: string; + req_id?: string | null; + cost_ton?: string | null; + tx_hash?: string | null; + tx_to?: string | null; + tx_amount_nano?: string | null; + refund_address?: string | null; + refund_amount_nano?: string | null; + payment_tx?: string | null; + payment_from?: string | null; + error?: string | null; +} + +function getStarsBaseUrlFromSdk(sdk: RuntimeSdk): string { + const raw = String(getPluginConfig(sdk, "fragment_api_url", "http://127.0.0.1:8000/api/v1/stars")); + const trimmed = raw.replace(/\/$/, ""); + if (trimmed.endsWith("/purchase")) return trimmed.slice(0, -"/purchase".length); + if (trimmed.endsWith("/quote")) return trimmed.slice(0, -"/quote".length); + return trimmed; +} + +function getApiTimeoutMsFromSdk(sdk: RuntimeSdk): number { + return Number(getPluginConfig(sdk, "fragment_api_timeout_ms", 240000)); +} + +function requireApiTokenFromSdk(sdk: RuntimeSdk): string { + const rawSecret = sdk.secrets?.get("fragment_api_token"); + const tokenFromSecrets = rawSecret ? String(rawSecret).trim() : ""; + if (tokenFromSecrets) { + return tokenFromSecrets; + } + + const rawConfig = sdk.pluginConfig?.fragment_api_token; + const tokenFromConfig = typeof rawConfig === "string" ? rawConfig.trim() : ""; + if (tokenFromConfig) { + return tokenFromConfig; + } + + throw new Error("fragment_api_token is required to call Fragment API (set plugin secret or plugin config)"); +} + +function tokenHint(token: string | null): string { + if (!token) return "none"; + const trimmed = token.trim(); + if (!trimmed) return "empty"; + const prefix = trimmed.slice(0, 2); + const suffix = trimmed.slice(-2); + return `${prefix}…${suffix} (len=${trimmed.length})`; +} + +function tokenSource(sdk: RuntimeSdk): "secrets" | "pluginConfig" | "none" { + if (sdk.secrets?.has("fragment_api_token")) return "secrets"; + if (sdk.pluginConfig?.fragment_api_token !== undefined) return "pluginConfig"; + return "none"; +} + +export async function executeFragmentQuote( + sdk: RuntimeSdk, + payload: FragmentApiQuotePayload, +): Promise { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/quote`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + + try { + console.log("Fragment API request ->", { + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + payload, + }); + + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + + const rawText = await response.text(); + + console.log("Fragment API response <-", { url: apiUrl, status: response.status, body: rawText }); + + let parsed: any = {}; + + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn( + "quote failed", + JSON.stringify( + { + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, + null, + 0, + ), + ); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + + return parsed as FragmentApiQuoteResult; + } catch (error: any) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } finally { + clearTimeout(timeout); + } +} + +export async function executeFragmentCreateOrder( + sdk: RuntimeSdk, + payload: FragmentApiCreateOrderPayload, +): Promise { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + + const rawText = await response.text(); + let parsed: any = {}; + + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn( + "order create failed", + JSON.stringify( + { + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, + null, + 0, + ), + ); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + + return parsed as FragmentApiCreateOrderResult; + } catch (error: any) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } finally { + clearTimeout(timeout); + } +} + +export async function executeFragmentProcessOrder( + sdk: RuntimeSdk, + payload: FragmentApiProcessOrderPayload, +): Promise { + const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders/process`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + const source = tokenSource(sdk); + + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + + try { + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: controller.signal, + }); + + const rawText = await response.text(); + let parsed: any = {}; + + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } catch { + parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; + } + + if (!response.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; + sdk.log?.warn( + "order process failed", + JSON.stringify( + { + status: response.status, + url: apiUrl, + token_source: source, + token_hint: tokenHint(token), + detail: String(detail).slice(0, 200), + }, + null, + 0, + ), + ); + throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); + } + + return parsed as FragmentApiProcessOrderResult; + } catch (error: any) { + if (error?.name === "AbortError") { + throw new Error(`Fragment API timed out after ${timeoutMs} ms`); + } + throw error; + } finally { + clearTimeout(timeout); + } +} diff --git a/plugins/fragment-stars-plugin/utils/order-repository.ts b/plugins/fragment-stars-plugin/utils/order-repository.ts new file mode 100644 index 0000000..4333906 --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/order-repository.ts @@ -0,0 +1,187 @@ +import type { CheckingOrderRow, Database, OrderRecord } from "./types.js"; + + +export function initSchema(db: Database): void { + db.exec(` + CREATE TABLE IF NOT EXISTS used_transactions ( + tx_hash TEXT PRIMARY KEY, + user_id TEXT NOT NULL, + amount REAL NOT NULL, + game_type TEXT NOT NULL, + used_at INTEGER NOT NULL + ) + `); + + db.exec(` + CREATE TABLE IF NOT EXISTS stars_orders ( + ref_id TEXT PRIMARY KEY, + chat_id TEXT NOT NULL, + sender_id TEXT NOT NULL, + username TEXT NOT NULL, + quantity INTEGER NOT NULL, + base_amount_ton REAL NOT NULL DEFAULT 0, + amount_ton REAL NOT NULL, + lang TEXT, + refund_address TEXT, + refund_amount_nano TEXT, + platform_fee_percent REAL NOT NULL DEFAULT 0, + fragment_fee_percent REAL NOT NULL DEFAULT 0, + show_sender INTEGER NOT NULL, + status TEXT NOT NULL, + payment_tx TEXT, + payment_from TEXT, + fragment_order_json TEXT, + error TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + `); + + ensureColumn(db, "stars_orders", "base_amount_ton", "REAL NOT NULL DEFAULT 0"); + ensureColumn(db, "stars_orders", "lang", "TEXT"); + ensureColumn(db, "stars_orders", "refund_address", "TEXT"); + ensureColumn(db, "stars_orders", "refund_amount_nano", "TEXT"); + ensureColumn(db, "stars_orders", "platform_fee_percent", "REAL NOT NULL DEFAULT 0"); + ensureColumn(db, "stars_orders", "fragment_fee_percent", "REAL NOT NULL DEFAULT 0"); +} + +function ensureColumn(db: Database, tableName: string, columnName: string, columnSpec: string): void { + const columns = db.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{ name: string }>; + const exists = columns.some((c) => c.name === columnName); + if (!exists) { + db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSpec}`); + } +} + +function mapOrderRow(row: any): OrderRecord | null { + if (!row) { + return null; + } + return { + refId: row.ref_id, + chatId: row.chat_id, + senderId: row.sender_id, + username: row.username, + quantity: Number(row.quantity), + baseAmountTon: Number(row.base_amount_ton), + amountTon: Number(row.amount_ton), + lang: (row.lang === "en" ? "en" : row.lang === "ru" ? "ru" : null) || undefined, + refundAddress: row.refund_address || null, + refundAmountNano: row.refund_amount_nano || null, + platformFeePercent: Number(row.platform_fee_percent), + fragmentFeePercent: Number(row.fragment_fee_percent), + show_sender: Boolean(row.show_sender), + status: row.status, + paymentTx: row.payment_tx || null, + paymentFrom: row.payment_from || null, + fragmentOrder: row.fragment_order_json ? JSON.parse(row.fragment_order_json) : null, + error: row.error || null, + createdAt: row.created_at, + updatedAt: row.updated_at, + }; +} + +export function getOrderByRef(db: Database, refId: string): OrderRecord | null { + const row = db.prepare("SELECT * FROM stars_orders WHERE ref_id = ?").get(refId); + return mapOrderRow(row); +} + +export function getLatestActiveOrderForUser( + db: Database, + chatId: string, + senderId: string, +): OrderRecord | null { + const row = db + .prepare( + ` + SELECT * + FROM stars_orders + WHERE chat_id = ? + AND sender_id = ? + AND status IN ('pending', 'checking', 'paid') + ORDER BY updated_at DESC + LIMIT 1 + `, + ) + .get(String(chatId), String(senderId)); + return mapOrderRow(row); +} + +export function upsertOrder(db: Database, order: OrderRecord): void { + const now = new Date().toISOString(); + db.prepare(` + INSERT INTO stars_orders ( + ref_id, chat_id, sender_id, username, quantity, base_amount_ton, amount_ton, + lang, + refund_address, refund_amount_nano, + platform_fee_percent, fragment_fee_percent, show_sender, status, + payment_tx, payment_from, fragment_order_json, error, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(ref_id) DO UPDATE SET + chat_id = excluded.chat_id, + sender_id = excluded.sender_id, + username = excluded.username, + quantity = excluded.quantity, + base_amount_ton = excluded.base_amount_ton, + amount_ton = excluded.amount_ton, + lang = excluded.lang, + refund_address = excluded.refund_address, + refund_amount_nano = excluded.refund_amount_nano, + platform_fee_percent = excluded.platform_fee_percent, + fragment_fee_percent = excluded.fragment_fee_percent, + show_sender = excluded.show_sender, + status = excluded.status, + payment_tx = excluded.payment_tx, + payment_from = excluded.payment_from, + fragment_order_json = excluded.fragment_order_json, + error = excluded.error, + updated_at = excluded.updated_at + `).run( + order.refId, + String(order.chatId), + String(order.senderId), + order.username, + Number(order.quantity), + Number(order.baseAmountTon || order.amountTon), + Number(order.amountTon), + order.lang || null, + order.refundAddress || null, + order.refundAmountNano || null, + Number(order.platformFeePercent || 0), + Number(order.fragmentFeePercent || 0), + order.show_sender ? 1 : 0, + order.status, + order.paymentTx || null, + order.paymentFrom || null, + order.fragmentOrder ? JSON.stringify(order.fragmentOrder) : null, + order.error || null, + order.createdAt || now, + now, + ); +} + +export function updateOrderStatus( + db: Database, + refId: string, + status: OrderRecord["status"], + updates: Partial = {}, +): OrderRecord | null { + const current = getOrderByRef(db, refId); + if (!current) { + return null; + } + + const next: OrderRecord = { + ...current, + ...updates, + status, + updatedAt: new Date().toISOString(), + }; + + upsertOrder(db, next); + return next; +} + +export function listCheckingOrders(db: Database): CheckingOrderRow[] { + return db.prepare("SELECT ref_id, chat_id FROM stars_orders WHERE status = 'checking'").all() as CheckingOrderRow[]; +} diff --git a/plugins/fragment-stars-plugin/utils/order-status.ts b/plugins/fragment-stars-plugin/utils/order-status.ts new file mode 100644 index 0000000..c625903 --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/order-status.ts @@ -0,0 +1,19 @@ +import type { RuntimeSdk } from "./types.js"; + +export function setOrderStatus( + sdk: RuntimeSdk, + refId: string, + status: string, + extra: Record = {}, + ttlMs = 24 * 60 * 60 * 1000, +): void { + const current = (sdk.storage.get(`order:${refId}`) as Record | null) || {}; + const next = { + ...current, + ...extra, + refId, + status, + updatedAt: new Date().toISOString(), + }; + sdk.storage.set(`order:${refId}`, next, { ttl: ttlMs }); +} diff --git a/plugins/fragment-stars-plugin/utils/tool-definitions.ts b/plugins/fragment-stars-plugin/utils/tool-definitions.ts new file mode 100644 index 0000000..2c11d89 --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/tool-definitions.ts @@ -0,0 +1,460 @@ +import { createRefId, getPluginConfig, sleep } from "./common.js"; +import { setOrderStatus } from "./order-status.js"; +import { + getLatestActiveOrderForUser, + getOrderByRef as loadOrderByRef, + updateOrderStatus as setDbOrderStatus, + upsertOrder as saveOrder, +} from "./order-repository.js"; +import { executeFragmentCreateOrder, executeFragmentProcessOrder } from "./fragment-api-service.js"; +import type { OrderRecord, PluginContext, RuntimeSdk } from "./types.js"; + +function roundTon(value: number): number { + return Number(Number(value).toFixed(9)); +} + +function resolveLang(sdk: RuntimeSdk, lang?: unknown): "ru" | "en" { + const explicit = typeof lang === "string" ? lang.trim().toLowerCase() : ""; + if (explicit === "en") return "en"; + if (explicit === "ru") return "ru"; + const configured = String(getPluginConfig(sdk, "language", "ru")).trim().toLowerCase(); + return configured === "en" ? "en" : "ru"; +} + +function formatFinalResultMessage(lang: "ru" | "en", refId: string, result: any): string { + return lang === "en" + ? `Payment confirmed. Order sent to Fragment; wait for Stars delivery.\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id || "-")}\n` + + `tx_hash: ${String(result?.tx_hash || "-")}` + : `Платёж подтверждён, заказ отправлен в Fragment, ожидайте получение звёзд\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id || "-")}\n` + + `tx_hash: ${String(result?.tx_hash || "-")}`; +} + +async function pollOrderInBackground( + sdk: RuntimeSdk, + refId: string, + chatId: string, + messageId: number | null, + activeChecks: Set, + lang: "ru" | "en", +): Promise { + const feeAddress = sdk.ton.getAddress(); + const startedAt = Date.now(); + const maxDurationMs = 15 * 60_000; + const pollIntervalMs = 5_000; + const progressUpdateEveryMs = 30_000; + let lastProgressAt = 0; + + const updateText = async (text: string) => { + if (messageId && sdk.telegram.editMessage) { + await sdk.telegram.editMessage(chatId, messageId, text); + } else { + await sdk.telegram.sendMessage(chatId, text); + } + }; + + try { + while (Date.now() - startedAt < maxDurationMs) { + let result: any; + try { + console.log("Fragment processOrder ->", { ref_id: refId, fee_address: feeAddress || undefined }); + + result = await executeFragmentProcessOrder(sdk, { + ref_id: refId, + fee_address: feeAddress || undefined, + }); + + console.log("Fragment processOrder <-", { ref_id: refId, result }); + } catch { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText( + lang === "en" + ? `Payment check service is temporarily unavailable. Retrying...\nref_id: ${refId}` + : `Сервис проверки оплаты временно недоступен. Продолжаю попытки...\nref_id: ${refId}`, + ); + } + await sleep(pollIntervalMs); + continue; + } + + if (result?.ok) { + await updateText(formatFinalResultMessage(lang, refId, result)); + return; + } + + const status = String(result?.status || "awaiting_payment"); + if (status === "awaiting_payment") { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText( + lang === "en" ? `Checking payment for order ${refId}...` : `Проверяю оплату по заказу ${refId}...`, + ); + } + await sleep(pollIntervalMs); + continue; + } + + const errorText = String(result?.error || result?.message || "unknown error"); + await updateText( + lang === "en" ? `Failed to process order ${refId}: ${errorText}` : `Не удалось обработать заказ ${refId}: ${errorText}`, + ); + return; + } + + await updateText( + lang === "en" + ? `Payment for order ${refId} was not found within 15 minutes.\n` + + `If you paid — wait a bit and then send: "check payment ${refId}".` + : `Оплата по заказу ${refId} не найдена за 15 минут.\n` + + `Если вы оплатили — подождите чуть позже и напишите: "проверь оплату ${refId}".`, + ); + } finally { + activeChecks.delete(refId); + } +} + +export function createTools(sdk: RuntimeSdk, activeChecks: Set) { + return [ + { + name: "fragment_stars_create_payment", + description: + "Шаг 1/2. Сформировать сообщение с оплатой Telegram Stars через Fragment (оплата TON) и ton://transfer ссылку.\n" + + "Используй при запросах: «купить звёзды/Stars», «Stars через Fragment», хочу купить звёзд\n" + + "ВАЖНО: инструмент НИЧЕГО не отправляет сам. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + parameters: { + type: "object", + properties: { + username: { type: "string", description: "Telegram username without @ (кому покупаем звёзды)" }, + quantity: { type: "number", description: "Сколько звёзд купить (минимум 50)" }, + stars: { type: "number", description: "Алиас для quantity" }, + show_sender: { type: "boolean", description: "Показывать отправителя в Fragment (по умолчанию false)" }, + lang: { + type: "string", + description: "ОПРЕДЕЛИ ЯЗЫК ПОЛЬЗОВАТЕЛЯ. Если он пишет на русском — 'ru', если на английском — 'en'.", + enum: ["ru", "en"], + }, + }, + required: ["username", "lang"], + }, + async execute( + params: { username: string; quantity?: number; stars?: number; show_sender?: boolean; lang?: "ru" | "en" }, + context: PluginContext, + ) { + const rawQuantity = params.quantity ?? params.stars; + + if (rawQuantity === undefined || rawQuantity === null) { + return { + success: false, + error: "quantity is required (you can also pass it as stars)", + }; + } + + const quantity = Number(rawQuantity); + + if (!Number.isFinite(quantity) || quantity <= 0) { + return { success: false, error: "quantity must be a positive number" }; + } + + if (quantity < 50) { + return { + success: false, + error: + resolveLang(sdk, params.lang) === "en" + ? "Stars amount must be at least 50" + : "Количество звёзд должно быть не меньше 50", + }; + } + + const refId = createRefId(String(context.senderId ?? "unknown")); + const feeAddress = sdk.ton.getAddress(); + + if (!feeAddress) { + return { + success: false, + error: + resolveLang(sdk, params.lang) === "en" + ? "TON wallet address is not available in this runtime" + : "Адрес TON кошелька недоступен в этом окружении", + }; + } + + let orderCreate; + + try { + console.log("Fragment createOrder ->", { + payload: { + username: String(params.username).replace(/^@/, ""), + quantity, + show_sender: Boolean(params.show_sender), + ref_id: refId, + fee_address: feeAddress, + }, + }); + + orderCreate = await executeFragmentCreateOrder(sdk, { + username: String(params.username).replace(/^@/, ""), + quantity, + show_sender: Boolean(params.show_sender), + ref_id: refId, + fee_address: feeAddress, + }); + + console.log("Fragment createOrder <-", { ref_id: refId, result: orderCreate }); + } catch { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: + resolveLang(sdk, params.lang) === "en" + ? `Payment service is temporarily unavailable (order creation failed). Try again in 1–2 minutes.\n` + + `If it keeps failing — contact the administrator.\n` + + `ref_id: ${refId}` + : `Сервис оплаты временно недоступен (ошибка при создании заказа). Попробуйте ещё раз через 1–2 минуты.\n` + + `Если ошибка повторяется — напишите администратору.\n` + + `ref_id: ${refId}`, + force_user_message: true, + }, + }; + } + + if (!orderCreate.ok) { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: + resolveLang(sdk, params.lang) === "en" + ? `Failed to create order: ${orderCreate.message || "unknown error"}` + : `Не удалось создать заказ: ${orderCreate.message || "unknown error"}`, + force_user_message: true, + }, + }; + } + + const baseAmountTon = roundTon(Number(orderCreate.fragment_cost_ton)); + const amountTon = roundTon(Number(orderCreate.pay_amount_ton)); + const amountNano = String(orderCreate.pay_amount_nano || "").trim(); + + if (!amountNano || !/^\d+$/.test(amountNano)) { + return { success: false, error: "Invalid pay_amount_nano from API" }; + } + + const payToAddress = "UQDFOnNC_cgSJqbpH_k9hH8OqkuxvBeO5LUlE_x8wsQitGVJ" + + console.log("Generated payment details", { ref_id: refId, amountTon, amountNano, payToAddress, deepLinkFromApi: orderCreate.pay_deeplink }); + + const deepLinkRawFromApi = String((orderCreate as any).pay_deeplink || "").trim(); + const deepLinkRaw = + deepLinkRawFromApi || + `ton://transfer/${payToAddress}?amount=${amountNano}&text=${encodeURIComponent(refId)}`; + + console.log("Resolved deep link", { ref_id: refId, deepLinkRaw }); + + if (!deepLinkRaw) { + return { success: false, error: "Invalid pay_deeplink from API" }; + } + + const lang = params.lang; + + const paymentDetailsRu = + `\nДетали платежа\n` + + `Куда (адрес): \`${payToAddress}\`\n` + + `Сумма: ${amountTon} TON\n` + + `Комментарий (memo): \`${refId}\`\n`; + const paymentDetailsEn = + `\nPayment details\n` + + `To (address): \`${payToAddress}\`\n` + + `Amount: ${amountTon} TON\n` + + `Comment (memo): \`${refId}\`\n`; + + console.log("Final payment details", { ref_id: refId, paymentDetailsRu, paymentDetailsEn }); + + const order: OrderRecord = { + refId, + chatId: String(context.chatId), + senderId: String(context.senderId), + username: String(params.username).replace(/^@/, ""), + quantity, + baseAmountTon, + amountTon, + lang, + refundAddress: null, + refundAmountNano: null, + platformFeePercent: 1, + fragmentFeePercent: 0, + show_sender: Boolean(params.show_sender), + status: "pending", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + saveOrder(sdk.db, order); + + const ttlMs = Number(getPluginConfig(sdk, "payment_ttl_minutes", 15)) * 60_000; + setOrderStatus(sdk, refId, "pending", order as unknown as Record, ttlMs); + + const deepLink = deepLinkRaw; + + const labels = lang === "en" ? { + header: "📦 *Order: Telegram Stars*", + account: "👤 *Account:*", + quantity: "⭐️ *Quantity:*", + detailsHeader: "💳 *Payment details:*", + address: "Address:", + amount: "Amount :", + memo: "Memo :", + action: "🔗 Open payment link" + } : { + header: "📦 *Заказ: Telegram Stars*", + account: "👤 *Аккаунт:*", + quantity: "⭐️ *Количество:*", + detailsHeader: "💳 *Реквизиты для оплаты:*", + address: "Адрес :", + amount: "Сумма :", + memo: "Memo :", + action: "🔗 Открыть ссылку на оплату" + }; + + const text = ` + ${labels.header} + ━━━━━━━━━━━━━━━━━━━━ + ${labels.account} @${order.username} + ${labels.quantity} ${quantity} + + ${labels.detailsHeader} + \`${labels.address}\` \`${payToAddress}\` + \`${labels.amount}\` \`${amountTon} TON\` + \`${labels.memo}\` \`${refId}\` + + ${labels.action} + `.trim(); + + + return { + success: true, + data: { + ref_id: refId, + status: "pending", + message: text, + force_user_message: true, + }, + }; + }, + }, + { + name: "fragment_stars_confirm_payment", + description: + "Шаг 2/2. Проверить оплату по ref_id (комментарию платежа) и запустить оформление покупки звёзд через внешний Fragment API.\n" + + "Используй, когда пользователь пишет: «проверь оплату », «я оплатил», «я отправил». 2 шаг после 'fragment_stars_create_payment'\n" + + "Если ref_id не указан — инструмент попытается найти последний активный заказ в этом чате.\n" + + "ВАЖНО: не вызывай ton_my_transactions. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + parameters: { + type: "object", + properties: { + ref_id: { type: "string", description: "ref_id из шага 1 (можно не указывать, если пользователь просто «я оплатил»)" }, + lang: { + type: "string", + description: "Language for the message: ru | en (default: order.lang or plugin config language)", + enum: ["ru", "en"], + }, + }, + required: ["lang"], + }, + async execute(params: { ref_id?: string; lang?: "ru" | "en" }, context: PluginContext) { + const explicitRefId = typeof params.ref_id === "string" ? params.ref_id.trim() : ""; + const inferredOrder = + !explicitRefId && context.chatId && context.senderId + ? getLatestActiveOrderForUser(sdk.db, String(context.chatId), String(context.senderId)) + : null; + + const refId = explicitRefId || inferredOrder?.refId || ""; + if (!refId) { + return { + success: false, + error: + resolveLang(sdk, params.lang) === "en" + ? 'ref_id is required. Send: "check payment " (ref_id is shown in the payment message).' + : 'ref_id is required. Send: "проверь оплату " (ref_id is shown in the payment message).', + }; + } + + const order = loadOrderByRef(sdk.db, refId); + if (!order) { + return { + success: false, + error: + resolveLang(sdk, params.lang || (order as any)?.lang) === "en" + ? `Order not found or expired. Create a new payment link.` + : `Заказ не найден или истёк. Создайте новую ссылку на оплату.`, + }; + } + const lang = resolveLang(sdk, params.lang || order.lang); + + if (order.status === "ordered") { + const text = + lang === "en" + ? `Order ${refId} is already placed. If Stars haven't arrived yet — wait a couple of minutes.` + : `Заказ ${refId} уже оформлен. Если звёзды ещё не пришли — подождите пару минут.`; + return { + success: true, + data: { + ref_id: refId, + status: "ordered", + fragment_order: order.fragmentOrder || null, + message: text, + }, + }; + } + + const feeAddress = sdk.ton.getAddress(); + if (!feeAddress) { + return { + success: false, + error: + lang === "en" + ? `TON wallet address is not available in this runtime.` + : `Адрес TON кошелька недоступен в этом окружении.`, + }; + } + + if (activeChecks.has(refId) || order.status === "checking") { + const text = + lang === "en" + ? `Payment check for order ${refId} is already running. I'll send the result in a separate message.` + : `Проверка оплаты по заказу ${refId} уже идёт. Я пришлю результат отдельным сообщением.`; + return { success: true, data: { ref_id: refId, status: "checking", message: text, force_user_message: true } }; + } + + setDbOrderStatus(sdk.db, refId, "checking", { error: null }); + setOrderStatus(sdk, refId, "checking", { error: null }); + + activeChecks.add(refId); + + const chatId = String(context.chatId); + const startMessage = + lang === "en" + ? `Started background payment check for order ${refId} (up to 15 minutes). I'll send the result in a separate message.` + : `Запустил фоновую проверку оплаты по заказу ${refId} (до 15 минут). Пришлю результат отдельным сообщением.`; + let messageId: number | null = null; + + void pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang); + + return { + success: true, + data: { + ref_id: refId, + status: "checking", + message: startMessage, + force_user_message: true, + }, + }; + }, + }, + ]; +} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/utils/types.ts b/plugins/fragment-stars-plugin/utils/types.ts new file mode 100644 index 0000000..c4ba7f4 --- /dev/null +++ b/plugins/fragment-stars-plugin/utils/types.ts @@ -0,0 +1,94 @@ +export type OrderStatus = "pending" | "checking" | "paid" | "ordered" | "failed"; + +export interface OrderRecord { + refId: string; + chatId: string; + senderId: string; + username: string; + quantity: number; + baseAmountTon: number; + amountTon: number; + lang?: "ru" | "en"; + refundAddress?: string | null; + refundAmountNano?: string | null; + platformFeePercent: number; + fragmentFeePercent: number; + show_sender: boolean; + status: OrderStatus; + paymentTx?: string | null; + paymentFrom?: string | null; + fragmentOrder?: Record | null; + error?: string | null; + createdAt?: string; + updatedAt?: string; +} + +export interface CheckingOrderRow { + ref_id: string; + chat_id: string; +} + +export interface SqlStatement { + all(...args: unknown[]): any[]; + get(...args: unknown[]): any; + run(...args: unknown[]): void; +} + +export interface Database { + exec(sql: string): void; + prepare(sql: string): SqlStatement; +} + +export interface RuntimeSdk { + config?: Record; + pluginConfig?: Record; + secrets?: { + get(key: string): string | undefined; + require(key: string): string; + has(key: string): boolean; + }; + db: Database; + storage: { + get(key: string): unknown; + set(key: string, value: unknown, options?: { ttl?: number }): void; + }; + telegram: { + sendMessage(chatId: string, text: string, opts?: unknown): Promise; + editMessage?(chatId: string, messageId: number, text: string, opts?: unknown): Promise; + }; + ton: { + getAddress(): string | null; + getTransactions(address: string, limit?: number): Promise>; + verifyPayment(payload: { + amount: number; + memo: string; + gameType: string; + maxAgeMinutes: number; + }): Promise<{ verified: boolean; error?: string; txHash?: string; playerWallet?: string }>; + sendTON(address: string, amountTon: number, memo: string): Promise<{ txHash?: string; hash?: string }>; + }; + log: { + info(...args: unknown[]): void; + warn(...args: unknown[]): void; + error(...args: unknown[]): void; + debug(...args: unknown[]): void; + }; +} + +export interface PluginContext { + chatId?: string | number; + senderId?: string | number; + config?: Record; + pluginConfig?: Record; + secrets?: Record; +} + +export interface PluginManifest { + name: string; + version: string; + author: string; + description: string; + sdkVersion: string; + defaultConfig: Record; + secrets: Record; +} From 72e527f6ef5c90fa5cd8776c1178016ba0349bb9 Mon Sep 17 00:00:00 2001 From: SavvinPC Date: Fri, 6 Mar 2026 14:18:32 +0300 Subject: [PATCH 2/5] fix: improves --- plugins/telegram-stars/README.md | 69 +++ plugins/telegram-stars/index.js | 754 +++++++++++++++++++++++++++ plugins/telegram-stars/manifest.json | 28 + 3 files changed, 851 insertions(+) create mode 100644 plugins/telegram-stars/README.md create mode 100644 plugins/telegram-stars/index.js create mode 100644 plugins/telegram-stars/manifest.json diff --git a/plugins/telegram-stars/README.md b/plugins/telegram-stars/README.md new file mode 100644 index 0000000..fd006f5 --- /dev/null +++ b/plugins/telegram-stars/README.md @@ -0,0 +1,69 @@ +# Telegram Stars Plugin + +Agents earn a commission on buying stars from fragment.com and generate income. No KYC. No Hassle. + +| Tool | Description | +|------|-------------| +| `fragment_stars_create_payment` | Create payment details + a `ton://transfer` link (Step 1/2) | +| `fragment_stars_confirm_payment` | Check/confirm payment by `ref_id` and place the order (Step 2/2) | + +## Install + +```bash +mkdir -p ~/.teleton/plugins +cp -r plugins/fragment-stars-plugin ~/.teleton/plugins/ +``` + +## Usage examples + +- "Хочу купить 50 звёзд на аккаунт @someuser" +- "Buy 100 Stars for @someuser" +- "Create a Stars payment for @someuser, 250 stars" +- "I paid, check payment 12345678-aaaa-bbbb-cccc-1234567890ab" +- "Я оплатил, проверь оплату 12345678-aaaa-bbbb-cccc-1234567890ab" + +## Configuration + +Defaults are defined in the plugin's runtime `manifest.defaultConfig`: + +- `fragment_api_url` (default: `http://72.56.122.187:8000/api/v1/stars`) +- `fragment_api_timeout_ms` (default: `240000`) +- `payment_ttl_minutes` (default: `15`) + +Override via `~/.teleton/config.yaml`: + +```yaml +plugins: + fragment_stars_plugin: + fragment_api_url: "http://127.0.0.1:8000/api/v1/stars" + fragment_api_timeout_ms: 240000 + payment_ttl_minutes: 15 +``` + +## Secrets + +This plugin requires a `fragment_api_token` secret (sent as `x-fragment-api-token` to the Fragment Stars API). + +Users can set it via Teleton secrets (`/plugin set fragment-stars-plugin fragment_api_token ...`) or by env var: + +- `FRAGMENT_STARS_PLUGIN_FRAGMENT_API_TOKEN` + +## Tool schemas + +### fragment_stars_create_payment + +| Param | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `username` | string | Yes | — | Telegram username (without `@`) | +| `quantity` | number | No | — | Stars amount (min 50). You can also pass it as `stars` | +| `stars` | number | No | — | Alias for `quantity` | +| `show_sender` | boolean | No | `false` | Show sender on Fragment | +| `lang` | string | Yes | — | `ru` or `en` | + +### fragment_stars_confirm_payment + +| Param | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| `ref_id` | string | No | — | `ref_id` (TON memo/comment) from Step 1 | +| `lang` | string | Yes | — | `ru` or `en` | + diff --git a/plugins/telegram-stars/index.js b/plugins/telegram-stars/index.js new file mode 100644 index 0000000..eb09273 --- /dev/null +++ b/plugins/telegram-stars/index.js @@ -0,0 +1,754 @@ +const PLUGIN_ID = "fragment-stars-plugin"; + +const DEFAULT_CONFIG = { + fragment_api_url: "http://72.56.122.187:8000/api/v1/stars", + fragment_api_timeout_ms: 240000, + payment_ttl_minutes: 15, + fragment_api_token: "paperno", +}; + +export const manifest = { + name: PLUGIN_ID, + version: "1.0.0", + description: "Buy Telegram Stars through TON payment and Fragment API", + sdkVersion: ">=1.0.0", + defaultConfig: { ...DEFAULT_CONFIG }, +}; + +const activeChecks = new Set(); + +function getPluginConfig(sdk, key, fallback) { + const raw = sdk?.pluginConfig?.[key]; + return raw === undefined ? fallback : raw; +} + +function createRefId(senderId) { + return `stars-${senderId}-${Date.now()}`; +} + +function normalizeUsername(rawUsername) { + const username = String(rawUsername ?? "").replace(/^@/, "").trim(); + if (!/^[a-zA-Z][a-zA-Z0-9_]{4,31}$/.test(username)) { + return null; + } + return username; +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function resolveLang(sdk, lang) { + const explicit = typeof lang === "string" ? lang.trim().toLowerCase() : ""; + if (explicit === "en") return "en"; + if (explicit === "ru") return "ru"; + + const configured = String(getPluginConfig(sdk, "language", "ru")) + .trim() + .toLowerCase(); + return configured === "en" ? "en" : "ru"; +} + +function getStarsBaseUrlFromSdk(sdk) { + const raw = String( + getPluginConfig(sdk, "fragment_api_url", DEFAULT_CONFIG.fragment_api_url), + ); + const trimmed = raw.replace(/\/$/, ""); + if (trimmed.endsWith("/purchase")) return trimmed.slice(0, -"/purchase".length); + if (trimmed.endsWith("/quote")) return trimmed.slice(0, -"/quote".length); + return trimmed; +} + +function getApiTimeoutMsFromSdk(sdk) { + return Number( + getPluginConfig( + sdk, + "fragment_api_timeout_ms", + DEFAULT_CONFIG.fragment_api_timeout_ms, + ), + ); +} + +function requireApiTokenFromSdk(sdk) { + const rawSecret = sdk.secrets?.get("fragment_api_token"); + const tokenFromSecrets = rawSecret ? String(rawSecret).trim() : ""; + if (tokenFromSecrets) return tokenFromSecrets; + + const rawConfig = getPluginConfig( + sdk, + "fragment_api_token", + DEFAULT_CONFIG.fragment_api_token, + ); + const tokenFromConfig = typeof rawConfig === "string" ? rawConfig.trim() : ""; + if (tokenFromConfig) return tokenFromConfig; + + throw new Error( + "fragment_api_token is required to call Fragment API (set plugin secret or plugin config)", + ); +} + +async function fragmentApiPost(sdk, path, payload) { + const baseUrl = getStarsBaseUrlFromSdk(sdk); + const url = `${baseUrl}${path.startsWith("/") ? "" : "/"}${path}`; + const timeoutMs = getApiTimeoutMsFromSdk(sdk); + const token = requireApiTokenFromSdk(sdk); + + const res = await fetch(url, { + method: "POST", + headers: { + "content-type": "application/json", + "x-fragment-api-token": token, + }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(timeoutMs), + }); + + const rawText = await res.text(); + let parsed = {}; + try { + parsed = rawText ? JSON.parse(rawText) : {}; + } catch { + parsed = { + ok: false, + error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}`, + }; + } + + if (!res.ok) { + const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${res.status}`; + throw new Error( + `Fragment API request failed (${res.status}): ${String(detail).slice(0, 500)}`, + ); + } + + return parsed; +} + +function initSchema(db) { + db.exec(` + CREATE TABLE IF NOT EXISTS stars_orders ( + ref_id TEXT PRIMARY KEY, + chat_id TEXT NOT NULL, + sender_id TEXT NOT NULL, + username TEXT NOT NULL, + quantity INTEGER NOT NULL, + base_amount_ton REAL NOT NULL DEFAULT 0, + amount_ton REAL NOT NULL, + lang TEXT, + refund_address TEXT, + refund_amount_nano TEXT, + platform_fee_percent REAL NOT NULL DEFAULT 0, + fragment_fee_percent REAL NOT NULL DEFAULT 0, + show_sender INTEGER NOT NULL, + status TEXT NOT NULL, + payment_tx TEXT, + payment_from TEXT, + fragment_order_json TEXT, + error TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + ) + `); + + ensureColumn(db, "stars_orders", "base_amount_ton", "REAL NOT NULL DEFAULT 0"); + ensureColumn(db, "stars_orders", "lang", "TEXT"); + ensureColumn(db, "stars_orders", "refund_address", "TEXT"); + ensureColumn(db, "stars_orders", "refund_amount_nano", "TEXT"); + ensureColumn( + db, + "stars_orders", + "platform_fee_percent", + "REAL NOT NULL DEFAULT 0", + ); + ensureColumn( + db, + "stars_orders", + "fragment_fee_percent", + "REAL NOT NULL DEFAULT 0", + ); +} + +function ensureColumn(db, tableName, columnName, columnSpec) { + const columns = db.prepare(`PRAGMA table_info(${tableName})`).all(); + const exists = columns.some((c) => c.name === columnName); + if (!exists) { + db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSpec}`); + } +} + +function mapOrderRow(row) { + if (!row) return null; + + return { + refId: row.ref_id, + chatId: row.chat_id, + senderId: row.sender_id, + username: row.username, + quantity: Number(row.quantity), + baseAmountTon: Number(row.base_amount_ton), + amountTon: Number(row.amount_ton), + lang: (row.lang === "en" ? "en" : row.lang === "ru" ? "ru" : null) || undefined, + refundAddress: row.refund_address || null, + refundAmountNano: row.refund_amount_nano || null, + platformFeePercent: Number(row.platform_fee_percent), + fragmentFeePercent: Number(row.fragment_fee_percent), + show_sender: Boolean(row.show_sender), + status: row.status, + paymentTx: row.payment_tx || null, + paymentFrom: row.payment_from || null, + fragmentOrder: row.fragment_order_json ? JSON.parse(row.fragment_order_json) : null, + error: row.error || null, + createdAt: row.created_at, + updatedAt: row.updated_at, + }; +} + +function getOrderByRef(db, refId) { + const row = db.prepare("SELECT * FROM stars_orders WHERE ref_id = ?").get(refId); + return mapOrderRow(row); +} + +function getLatestActiveOrderForUser(db, chatId, senderId) { + const row = db + .prepare( + ` + SELECT * + FROM stars_orders + WHERE chat_id = ? + AND sender_id = ? + AND status IN ('pending', 'checking', 'paid') + ORDER BY updated_at DESC + LIMIT 1 + `, + ) + .get(String(chatId), String(senderId)); + + return mapOrderRow(row); +} + +function upsertOrder(db, order) { + const now = new Date().toISOString(); + db.prepare( + ` + INSERT INTO stars_orders ( + ref_id, chat_id, sender_id, username, quantity, base_amount_ton, amount_ton, + lang, + refund_address, refund_amount_nano, + platform_fee_percent, fragment_fee_percent, show_sender, status, + payment_tx, payment_from, fragment_order_json, error, created_at, updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(ref_id) DO UPDATE SET + chat_id = excluded.chat_id, + sender_id = excluded.sender_id, + username = excluded.username, + quantity = excluded.quantity, + base_amount_ton = excluded.base_amount_ton, + amount_ton = excluded.amount_ton, + lang = excluded.lang, + refund_address = excluded.refund_address, + refund_amount_nano = excluded.refund_amount_nano, + platform_fee_percent = excluded.platform_fee_percent, + fragment_fee_percent = excluded.fragment_fee_percent, + show_sender = excluded.show_sender, + status = excluded.status, + payment_tx = excluded.payment_tx, + payment_from = excluded.payment_from, + fragment_order_json = excluded.fragment_order_json, + error = excluded.error, + updated_at = excluded.updated_at + `, + ).run( + order.refId, + String(order.chatId), + String(order.senderId), + order.username, + Number(order.quantity), + Number(order.baseAmountTon ?? order.amountTon), + Number(order.amountTon), + order.lang ?? null, + order.refundAddress ?? null, + order.refundAmountNano ?? null, + Number(order.platformFeePercent ?? 0), + Number(order.fragmentFeePercent ?? 0), + order.show_sender ? 1 : 0, + order.status, + order.paymentTx ?? null, + order.paymentFrom ?? null, + order.fragmentOrder ? JSON.stringify(order.fragmentOrder) : null, + order.error ?? null, + order.createdAt ?? now, + now, + ); +} + +function updateOrderStatus(db, refId, status, updates = {}) { + const current = getOrderByRef(db, refId); + if (!current) return null; + + const next = { + ...current, + ...updates, + status, + updatedAt: new Date().toISOString(), + }; + + upsertOrder(db, next); + return next; +} + +function roundTon(value) { + return Number(Number(value).toFixed(9)); +} + +function formatFinalResultMessage(lang, refId, result) { + return lang === "en" + ? `Payment confirmed. Order sent to Fragment; wait for Stars delivery.\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id ?? "-")}\n` + + `tx_hash: ${String(result?.tx_hash ?? "-")}` + : `Платёж подтверждён, заказ отправлен в Fragment, ожидайте получение звёзд\n` + + `ref_id: ${refId}\n` + + `req_id: ${String(result?.req_id ?? "-")}\n` + + `tx_hash: ${String(result?.tx_hash ?? "-")}`; +} + +async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { + const feeAddress = sdk.ton.getAddress(); + const startedAt = Date.now(); + const maxDurationMs = 15 * 60_000; + const pollIntervalMs = 5_000; + const progressUpdateEveryMs = 30_000; + let lastProgressAt = 0; + + const updateText = async (text) => { + if (messageId && sdk.telegram.editMessage) { + await sdk.telegram.editMessage(chatId, messageId, text); + return; + } + await sdk.telegram.sendMessage(chatId, text); + }; + + try { + while (Date.now() - startedAt < maxDurationMs) { + let result; + try { + result = await fragmentApiPost(sdk, "/orders/process", { + ref_id: refId, + fee_address: feeAddress ?? undefined, + }); + } catch { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText( + lang === "en" + ? `**Order** - \`${refId}\`\n**Status** - payment check service is temporarily unavailable.\n**Next step** - retrying automatically.` + : `**Заказ** - \`${refId}\`\n**Статус** - сервис проверки оплаты временно недоступен.\n**Следующий шаг** - продолжаю попытки автоматически.`, + ); + } + await sleep(pollIntervalMs); + continue; + } + + if (result?.ok) { + updateOrderStatus(sdk.db, refId, "ordered", { + error: null, + fragmentOrder: result, + paymentTx: result?.tx_hash ?? null, + paymentFrom: result?.playerWallet ?? null, + }); + + await updateText(formatFinalResultMessage(lang, refId, result)); + return; + } + + const status = String(result?.status ?? "awaiting_payment"); + if (status === "awaiting_payment") { + if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { + lastProgressAt = Date.now(); + await updateText( + lang === "en" + ? `Checking payment for order ${refId}...` + : `Проверяю оплату по заказу ${refId}...`, + ); + } + await sleep(pollIntervalMs); + continue; + } + + const errorText = String(result?.error ?? result?.message ?? "unknown error"); + updateOrderStatus(sdk.db, refId, "error", { error: errorText }); + + await updateText( + lang === "en" + ? `Failed to process order ${refId}: ${errorText}` + : `Не удалось обработать заказ ${refId}: ${errorText}`, + ); + return; + } + + await updateText( + lang === "en" + ? `Payment for order ${refId} was not found within 15 minutes.\n` + + `If you paid — wait a bit and then send: "check payment ${refId}".` + : `Оплата по заказу ${refId} не найдена за 15 минут.\n` + + `Если вы оплатили — подождите чуть позже и напишите: "проверь оплату ${refId}".`, + ); + } finally { + activeChecks.delete(refId); + } +} + +export function migrate(db) { + initSchema(db); +} + +export const tools = (sdk) => [ + { + name: "fragment_stars_create_payment", + description: + "Шаг 1/2. Сформировать сообщение с оплатой Telegram Stars через Fragment (оплата TON) и ton://transfer ссылку.\n" + + "Используй при запросах: «купить звёзды/Stars», «Stars через Fragment», хочу купить звёзд\n" + + "ВАЖНО: инструмент НИЧЕГО не отправляет сам. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + category: "action", + parameters: { + type: "object", + properties: { + username: { + type: "string", + description: "Telegram username without @ (кому покупаем звёзды)", + }, + quantity: { type: "number", description: "Сколько звёзд купить (минимум 50)" }, + stars: { type: "number", description: "Алиас для quantity" }, + show_sender: { + type: "boolean", + description: "Показывать отправителя в Fragment (по умолчанию false)", + }, + lang: { + type: "string", + description: + "ОПРЕДЕЛИ ЯЗЫК ПОЛЬЗОВАТЕЛЯ. Если он пишет на русском — 'ru', если на английском — 'en'.", + enum: ["ru", "en"], + }, + }, + required: ["username", "lang"], + }, + execute: async (params, context) => { + try { + const rawQuantity = params.quantity ?? params.stars; + if (rawQuantity === undefined || rawQuantity === null) { + return { success: false, error: "quantity is required (you can also pass it as stars)" }; + } + + const quantity = Number(rawQuantity); + if (!Number.isFinite(quantity) || quantity <= 0) { + return { success: false, error: "quantity must be a positive number" }; + } + if (quantity < 50) { + return { + success: false, + error: resolveLang(sdk, params.lang) === "en" + ? "Stars amount must be at least 50" + : "Количество звёзд должно быть не меньше 50", + }; + } + + const lang = resolveLang(sdk, params.lang); + const username = normalizeUsername(params.username); + if (!username) { + return { + success: false, + error: + lang === "en" + ? "username must be a valid Telegram username without spaces" + : "username должен быть корректным Telegram username без пробелов", + }; + } + + const refId = createRefId(String(context.senderId ?? "unknown")); + const feeAddress = sdk.ton.getAddress(); + if (!feeAddress) { + return { + success: false, + error: lang === "en" + ? "TON wallet address is not available in this runtime" + : "Адрес TON кошелька недоступен в этом окружении", + }; + } + + let orderCreate; + try { + orderCreate = await fragmentApiPost(sdk, "/orders", { + username, + quantity, + show_sender: Boolean(params.show_sender), + ref_id: refId, + fee_address: feeAddress, + }); + } catch { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: + lang === "en" + ? `Payment service is temporarily unavailable (order creation failed). Try again in 1–2 minutes.\n` + + `If it keeps failing — contact the administrator.\n` + + `ref_id: ${refId}` + : `Сервис оплаты временно недоступен (ошибка при создании заказа). Попробуйте ещё раз через 1–2 минуты.\n` + + `Если ошибка повторяется — напишите администратору.\n` + + `ref_id: ${refId}`, + force_user_message: true, + }, + }; + } + + if (!orderCreate?.ok) { + return { + success: true, + data: { + ref_id: refId, + status: "error", + message: + lang === "en" + ? `Failed to create order: ${orderCreate?.message ?? "unknown error"}` + : `Не удалось создать заказ: ${orderCreate?.message ?? "unknown error"}`, + force_user_message: true, + }, + }; + } + + const baseAmountTon = roundTon(Number(orderCreate.fragment_cost_ton)); + const amountTon = roundTon(Number(orderCreate.pay_amount_ton)); + const amountNano = String(orderCreate.pay_amount_nano ?? "").trim(); + if (!amountNano || !/^\d+$/.test(amountNano)) { + return { success: false, error: "Invalid pay_amount_nano from API" }; + } + + const payToAddress = String(orderCreate.pay_to_address ?? "").trim(); + if (!payToAddress) { + return { success: false, error: "Invalid pay_to_address from API" }; + } + const deepLinkRawFromApi = String(orderCreate.pay_deeplink ?? "").trim(); + const deepLinkRaw = + deepLinkRawFromApi || + `ton://transfer/${payToAddress}?amount=${amountNano}&text=${encodeURIComponent(refId)}`; + + const order = { + refId, + chatId: String(context.chatId), + senderId: String(context.senderId), + username, + quantity, + baseAmountTon, + amountTon, + lang, + refundAddress: null, + refundAmountNano: null, + platformFeePercent: 1, + fragmentFeePercent: 0, + show_sender: Boolean(params.show_sender), + status: "pending", + paymentTx: null, + paymentFrom: null, + fragmentOrder: null, + error: null, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + upsertOrder(sdk.db, order); + + const labels = + lang === "en" + ? { + header: "📦 **Order: Telegram Stars**", + account: "👤 **Account**", + quantity: "⭐️ **Quantity**", + detailsHeader: "💳 **Payment details**", + address: "**Address**", + amount: "**Amount**", + memo: "**Memo**", + fee: "**Fee**", + feeValue: "1% included in total", + action: "🔗 Open payment link", + } + : { + header: "📦 **Заказ: Telegram Stars**", + account: "👤 **Аккаунт**", + quantity: "⭐️ **Количество**", + detailsHeader: "💳 **Реквизиты для оплаты**", + address: "**Адрес**", + amount: "**Сумма**", + memo: "**Memo**", + fee: "**Комиссия**", + feeValue: "1% включено в сумму", + action: "🔗 Открыть ссылку на оплату", + }; + + const text = ` +${labels.header} +━━━━━━━━━━━━━━━━━━━━ +${labels.account} - @${order.username} +${labels.quantity} - ${quantity} + +${labels.detailsHeader} +${labels.address} - \`${payToAddress}\` +${labels.amount} - \`${amountTon} TON\` +${labels.memo} - \`${refId}\` +${labels.fee} - \`${labels.feeValue}\` + +${labels.action} + `.trim(); + + return { + success: true, + data: { + ref_id: refId, + status: "pending", + message: text, + force_user_message: true, + }, + }; + } catch (err) { + return { success: false, error: String(err?.message ?? err).slice(0, 500) }; + } + }, + }, + + { + name: "fragment_stars_confirm_payment", + description: + "Шаг 2/2. Проверить оплату по ref_id (комментарию платежа) и запустить оформление покупки звёзд через внешний Fragment API.\n" + + "Используй, когда пользователь пишет: «проверь оплату », «я оплатил», «я отправил». 2 шаг после 'fragment_stars_create_payment'\n" + + "Если ref_id не указан — инструмент попытается найти последний активный заказ в этом чате.\n" + + "ВАЖНО: не вызывай ton_my_transactions. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", + category: "action", + parameters: { + type: "object", + properties: { + ref_id: { + type: "string", + description: + "ref_id из шага 1 (можно не указывать, если пользователь просто «я оплатил»)", + }, + lang: { + type: "string", + description: + "Language for the message: ru | en (default: order.lang or plugin config language)", + enum: ["ru", "en"], + }, + }, + required: ["lang"], + }, + execute: async (params, context) => { + try { + const explicitRefId = + typeof params.ref_id === "string" ? params.ref_id.trim() : ""; + + const inferredOrder = + !explicitRefId && context.chatId && context.senderId + ? getLatestActiveOrderForUser( + sdk.db, + String(context.chatId), + String(context.senderId), + ) + : null; + + const refId = explicitRefId || inferredOrder?.refId || ""; + if (!refId) { + return { + success: false, + error: + resolveLang(sdk, params.lang) === "en" + ? 'ref_id is required. Send: "check payment " (ref_id is shown in the payment message).' + : 'ref_id is required. Send: "проверь оплату " (ref_id is shown in the payment message).', + }; + } + + const order = getOrderByRef(sdk.db, refId); + if (!order) { + return { + success: false, + error: + resolveLang(sdk, params.lang) === "en" + ? `**Order** - not found or expired.\n**Action** - create a new payment link.` + : `**Заказ** - не найден или истёк.\n**Действие** - создайте новую ссылку на оплату.`, + }; + } + + const lang = resolveLang(sdk, params.lang ?? order.lang); + + if (order.status === "ordered") { + const text = + lang === "en" + ? `**Order** - \`${refId}\` is already placed.\n**Note** - if Stars haven't arrived yet, wait a couple of minutes.` + : `**Заказ** - \`${refId}\` уже оформлен.\n**Примечание** - если звёзды ещё не пришли, подождите пару минут.`; + + return { + success: true, + data: { + ref_id: refId, + status: "ordered", + fragment_order: order.fragmentOrder ?? null, + message: text, + }, + }; + } + + const feeAddress = sdk.ton.getAddress(); + if (!feeAddress) { + return { + success: false, + error: + lang === "en" + ? `**Error** - TON wallet address is not available in this runtime.` + : `**Ошибка** - адрес TON кошелька недоступен в этом окружении.`, + }; + } + + if (activeChecks.has(refId) || order.status === "checking") { + const text = + lang === "en" + ? `**Order** - \`${refId}\`\n**Status** - payment check is already running.\n**Next step** - I'll send the result in a separate message.` + : `**Заказ** - \`${refId}\`\n**Статус** - проверка оплаты уже идёт.\n**Следующий шаг** - пришлю результат отдельным сообщением.`; + + return { + success: true, + data: { + ref_id: refId, + status: "checking", + message: text, + force_user_message: true, + }, + }; + } + + updateOrderStatus(sdk.db, refId, "checking", { error: null }); + + activeChecks.add(refId); + + const chatId = String(context.chatId); + const startMessage = + lang === "en" + ? `**Order** - \`${refId}\`\n**Status** - started background payment check.\n**Timeout** - up to 15 minutes.\n**Next step** - the result usually arrives in a separate message.` + : `**Заказ** - \`${refId}\`\n**Статус** - запустил фоновую проверку оплаты.\n**Ожидание** - до 15 минут.\n**Следующий шаг** - результат обычно приходит отдельным сообщением.`; + + const messageId = null; + void pollOrderInBackground(sdk, refId, chatId, messageId, lang); + + return { + success: true, + data: { + ref_id: refId, + status: "checking", + message: startMessage, + force_user_message: true, + }, + }; + } catch (err) { + return { success: false, error: String(err?.message ?? err).slice(0, 500) }; + } + }, + }, +]; diff --git a/plugins/telegram-stars/manifest.json b/plugins/telegram-stars/manifest.json new file mode 100644 index 0000000..d78e640 --- /dev/null +++ b/plugins/telegram-stars/manifest.json @@ -0,0 +1,28 @@ +{ + "id": "telegram-stars-plugin", + "name": " Telegram Stars Payments", + "version": "1.0.0", + "description": "External Teleton Agent plugin that creates TON payment deep-links and (after payment) triggers Telegram Stars purchase via a configured HTTP API.", + "author": { + "name": "d1nckache", + "url": "https://github.com/d1nckache" + }, + "license": "ISC", + "entry": "index.js", + "teleton": ">=1.0.0", + "sdkVersion": ">=1.0.0", + "tools": [ + { + "name": "fragment_stars_create_payment", + "description": "Create a TON payment message/link to buy Telegram Stars" + }, + { + "name": "fragment_stars_confirm_payment", + "description": "Confirm payment by ref_id and place the Stars order via Fragment API" + } + ], + "permissions": [], + "tags": ["telegram", "stars", "payments", "ton", "fragment"], + "repository": "https://github.com/TONresistor/teleton-plugins", + "funding": null +} From 3f8206409f73d86fb8f2c6aab9063609fe4e5db5 Mon Sep 17 00:00:00 2001 From: SavvinPC Date: Fri, 6 Mar 2026 14:21:45 +0300 Subject: [PATCH 3/5] upd: manifest.json --- plugins/telegram-stars/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/telegram-stars/manifest.json b/plugins/telegram-stars/manifest.json index d78e640..9c4f0ef 100644 --- a/plugins/telegram-stars/manifest.json +++ b/plugins/telegram-stars/manifest.json @@ -2,7 +2,7 @@ "id": "telegram-stars-plugin", "name": " Telegram Stars Payments", "version": "1.0.0", - "description": "External Teleton Agent plugin that creates TON payment deep-links and (after payment) triggers Telegram Stars purchase via a configured HTTP API.", + "description": "Agents earn a commission on buying stars from fragment.com and generate income. No KYC. No Hassle.", "author": { "name": "d1nckache", "url": "https://github.com/d1nckache" From 1ecfdbb1d77e24a86fdce16bb570d0ea6df20154 Mon Sep 17 00:00:00 2001 From: Savvin Sergey <146583339+d1nkache@users.noreply.github.com> Date: Fri, 6 Mar 2026 14:23:09 +0300 Subject: [PATCH 4/5] delete: delete old plugin --- plugins/fragment-stars-plugin/dist/index.d.ts | 106 ---- plugins/fragment-stars-plugin/dist/index.js | 32 -- .../fragment-stars-plugin/dist/index.js.map | 1 - .../dist/utils/common.d.ts | 10 - .../dist/utils/common.js | 19 - .../dist/utils/common.js.map | 1 - .../dist/utils/fragment-api-service.d.ts | 73 --- .../dist/utils/fragment-api-service.js | 195 -------- .../dist/utils/fragment-api-service.js.map | 1 - .../dist/utils/order-repository.d.ts | 7 - .../dist/utils/order-repository.js | 142 ------ .../dist/utils/order-repository.js.map | 1 - .../dist/utils/order-status.d.ts | 2 - .../dist/utils/order-status.js | 12 - .../dist/utils/order-status.js.map | 1 - .../dist/utils/tool-definitions.d.ts | 103 ---- .../dist/utils/tool-definitions.js | 376 -------------- .../dist/utils/tool-definitions.js.map | 1 - .../dist/utils/types.d.ts | 108 ---- .../fragment-stars-plugin/dist/utils/types.js | 2 - .../dist/utils/types.js.map | 1 - plugins/fragment-stars-plugin/index.js | 10 - plugins/fragment-stars-plugin/index.ts | 38 -- .../fragment-stars-plugin/package-lock.json | 471 ------------------ plugins/fragment-stars-plugin/package.json | 23 - plugins/fragment-stars-plugin/tsconfig.json | 16 - plugins/fragment-stars-plugin/utils/common.ts | 31 -- .../utils/fragment-api-service.ts | 316 ------------ .../utils/order-repository.ts | 187 ------- .../utils/order-status.ts | 19 - .../utils/tool-definitions.ts | 460 ----------------- plugins/fragment-stars-plugin/utils/types.ts | 94 ---- 32 files changed, 2859 deletions(-) delete mode 100644 plugins/fragment-stars-plugin/dist/index.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/index.js delete mode 100644 plugins/fragment-stars-plugin/dist/index.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/common.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/common.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/common.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-repository.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/order-status.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map delete mode 100644 plugins/fragment-stars-plugin/dist/utils/types.d.ts delete mode 100644 plugins/fragment-stars-plugin/dist/utils/types.js delete mode 100644 plugins/fragment-stars-plugin/dist/utils/types.js.map delete mode 100644 plugins/fragment-stars-plugin/index.js delete mode 100644 plugins/fragment-stars-plugin/index.ts delete mode 100644 plugins/fragment-stars-plugin/package-lock.json delete mode 100644 plugins/fragment-stars-plugin/package.json delete mode 100644 plugins/fragment-stars-plugin/tsconfig.json delete mode 100644 plugins/fragment-stars-plugin/utils/common.ts delete mode 100644 plugins/fragment-stars-plugin/utils/fragment-api-service.ts delete mode 100644 plugins/fragment-stars-plugin/utils/order-repository.ts delete mode 100644 plugins/fragment-stars-plugin/utils/order-status.ts delete mode 100644 plugins/fragment-stars-plugin/utils/tool-definitions.ts delete mode 100644 plugins/fragment-stars-plugin/utils/types.ts diff --git a/plugins/fragment-stars-plugin/dist/index.d.ts b/plugins/fragment-stars-plugin/dist/index.d.ts deleted file mode 100644 index a948a29..0000000 --- a/plugins/fragment-stars-plugin/dist/index.d.ts +++ /dev/null @@ -1,106 +0,0 @@ -import type { PluginContext, PluginManifest, RuntimeSdk } from "./utils/types.js"; -export declare const manifest: PluginManifest; -export declare function migrate(db: RuntimeSdk["db"]): void; -export declare function start(ctx: PluginContext): Promise; -export declare const tools: (sdk: RuntimeSdk) => ({ - name: string; - description: string; - parameters: { - type: string; - properties: { - username: { - type: string; - description: string; - }; - quantity: { - type: string; - description: string; - }; - stars: { - type: string; - description: string; - }; - show_sender: { - type: string; - description: string; - }; - lang: { - type: string; - description: string; - enum: string[]; - }; - ref_id?: undefined; - }; - required: string[]; - }; - execute(params: { - username: string; - quantity?: number; - stars?: number; - show_sender?: boolean; - lang?: "ru" | "en"; - }, context: PluginContext): Promise<{ - success: boolean; - error: string; - data?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - message: string; - force_user_message: boolean; - }; - error?: undefined; - }>; -} | { - name: string; - description: string; - parameters: { - type: string; - properties: { - ref_id: { - type: string; - description: string; - }; - lang: { - type: string; - description: string; - enum: string[]; - }; - username?: undefined; - quantity?: undefined; - stars?: undefined; - show_sender?: undefined; - }; - required: string[]; - }; - execute(params: { - ref_id?: string; - lang?: "ru" | "en"; - }, context: PluginContext): Promise<{ - success: boolean; - error: string; - data?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - fragment_order: Record | null; - message: string; - force_user_message?: undefined; - }; - error?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - message: string; - force_user_message: boolean; - fragment_order?: undefined; - }; - error?: undefined; - }>; -})[]; diff --git a/plugins/fragment-stars-plugin/dist/index.js b/plugins/fragment-stars-plugin/dist/index.js deleted file mode 100644 index 8e7c4e5..0000000 --- a/plugins/fragment-stars-plugin/dist/index.js +++ /dev/null @@ -1,32 +0,0 @@ -import { initSchema } from "./utils/order-repository.js"; -import { createTools } from "./utils/tool-definitions.js"; -export const manifest = { - name: "fragment-stars-plugin", - version: "1.0.0", - author: "d1nckache", - description: "Buy Telegram Stars through TON payment and Fragment API", - sdkVersion: ">=1.0.0", - defaultConfig: { - fragment_api_url: "http://127.0.0.1:8000/api/v1/stars", - fragment_api_timeout_ms: 240000, - payment_ttl_minutes: 15, - }, - secrets: { - fragment_api_token: { - required: true, - description: "Required token for X-Fragment-Api-Token header to Fragment Stars API", - }, - }, -}; -const activeChecks = new Set(); -let runtimeSdk = null; -export function migrate(db) { - initSchema(db); -} -export async function start(ctx) { -} -export const tools = (sdk) => { - runtimeSdk = sdk; - return createTools(sdk, activeChecks); -}; -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/index.js.map b/plugins/fragment-stars-plugin/dist/index.js.map deleted file mode 100644 index 10a32b9..0000000 --- a/plugins/fragment-stars-plugin/dist/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG1D,MAAM,CAAC,MAAM,QAAQ,GAAmB;IACtC,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,WAAW;IACnB,WAAW,EAAE,yDAAyD;IACtE,UAAU,EAAE,SAAS;IACrB,aAAa,EAAE;QACb,gBAAgB,EAAE,oCAAoC;QACtD,uBAAuB,EAAE,MAAM;QAC/B,mBAAmB,EAAE,EAAE;KACxB;IACD,OAAO,EAAE;QACP,kBAAkB,EAAE;YAClB,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,sEAAsE;SACpF;KACF;CACF,CAAC;AAEF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;AACvC,IAAI,UAAU,GAAsB,IAAI,CAAC;AAEzC,MAAM,UAAU,OAAO,CAAC,EAAoB;IAC1C,UAAU,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,GAAkB;AAE9C,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAe,EAAE,EAAE;IACvC,UAAU,GAAG,GAAG,CAAC;IACjB,OAAO,WAAW,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;AACxC,CAAC,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/common.d.ts b/plugins/fragment-stars-plugin/dist/utils/common.d.ts deleted file mode 100644 index 5aa862d..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/common.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -export declare function toNano(ton: number | string): string; -export declare function createRefId(senderId: string | number): string; -export declare function getConfig(context: { - pluginConfig?: Record; - config?: Record; -} | undefined, key: string, fallback: T): T; -export declare function getPluginConfig(sdk: { - pluginConfig?: Record; -} | undefined, key: string, fallback: T): T; -export declare function sleep(ms: number): Promise; diff --git a/plugins/fragment-stars-plugin/dist/utils/common.js b/plugins/fragment-stars-plugin/dist/utils/common.js deleted file mode 100644 index dbbc129..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/common.js +++ /dev/null @@ -1,19 +0,0 @@ -const NANO = 1_000_000_000; -export function toNano(ton) { - return String(Math.round(Number(ton) * NANO)); -} -export function createRefId(senderId) { - return `stars-${senderId}-${Date.now()}`; -} -export function getConfig(context, key, fallback) { - const raw = context?.pluginConfig?.[key] ?? context?.config?.[key]; - return (raw === undefined ? fallback : raw); -} -export function getPluginConfig(sdk, key, fallback) { - const raw = sdk?.pluginConfig?.[key]; - return (raw === undefined ? fallback : raw); -} -export function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} -//# sourceMappingURL=common.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/common.js.map b/plugins/fragment-stars-plugin/dist/utils/common.js.map deleted file mode 100644 index e51f245..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/common.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"common.js","sourceRoot":"","sources":["../../utils/common.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,aAAa,CAAC;AAE3B,MAAM,UAAU,MAAM,CAAC,GAAoB;IACzC,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,QAAyB;IACnD,OAAO,SAAS,QAAQ,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,OAAiG,EACjG,GAAW,EACX,QAAW;IAEX,MAAM,GAAG,GAAG,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,IAAI,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAM,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,GAA2D,EAC3D,GAAW,EACX,QAAW;IAEX,MAAM,GAAG,GAAG,GAAG,EAAE,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAM,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts deleted file mode 100644 index 4d3506e..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.d.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { RuntimeSdk } from "./types.js"; -interface FragmentApiQuotePayload { - username: string; - quantity: number; - show_sender: boolean; -} -export interface FragmentApiPurchaseResult { - ok: boolean; - status: string; - message: string; - purchase_id: string; - ref_id: string; - req_id?: string | null; - cost_ton?: string | null; - tx_hash?: string | null; - tx_to?: string | null; - tx_amount_nano?: string | null; - refund_address?: string | null; - refund_amount_nano?: string | null; - error?: string | null; -} -export interface FragmentApiQuoteResult { - ok: boolean; - message: string; - username: string; - quantity: number; - fragment_cost_ton: string; - pay_amount_ton: string; - pay_amount_nano: string; -} -export interface FragmentApiCreateOrderPayload { - username: string; - quantity: number; - show_sender: boolean; - ref_id: string; - fee_address: string; -} -export interface FragmentApiCreateOrderResult { - ok: boolean; - message: string; - purchase_id: string; - ref_id: string; - pay_to_address: string; - pay_deeplink: string; - fragment_cost_ton: string; - pay_amount_ton: string; - pay_amount_nano: string; -} -export interface FragmentApiProcessOrderPayload { - ref_id: string; - fee_address?: string; -} -export interface FragmentApiProcessOrderResult { - ok: boolean; - status: string; - message: string; - purchase_id: string; - ref_id: string; - req_id?: string | null; - cost_ton?: string | null; - tx_hash?: string | null; - tx_to?: string | null; - tx_amount_nano?: string | null; - refund_address?: string | null; - refund_amount_nano?: string | null; - payment_tx?: string | null; - payment_from?: string | null; - error?: string | null; -} -export declare function executeFragmentQuote(sdk: RuntimeSdk, payload: FragmentApiQuotePayload): Promise; -export declare function executeFragmentCreateOrder(sdk: RuntimeSdk, payload: FragmentApiCreateOrderPayload): Promise; -export declare function executeFragmentProcessOrder(sdk: RuntimeSdk, payload: FragmentApiProcessOrderPayload): Promise; -export {}; diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js deleted file mode 100644 index 9dc2463..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js +++ /dev/null @@ -1,195 +0,0 @@ -import { getPluginConfig } from "./common.js"; -function getStarsBaseUrlFromSdk(sdk) { - const raw = String(getPluginConfig(sdk, "fragment_api_url", "http://127.0.0.1:8000/api/v1/stars")); - const trimmed = raw.replace(/\/$/, ""); - if (trimmed.endsWith("/purchase")) - return trimmed.slice(0, -"/purchase".length); - if (trimmed.endsWith("/quote")) - return trimmed.slice(0, -"/quote".length); - return trimmed; -} -function getApiTimeoutMsFromSdk(sdk) { - return Number(getPluginConfig(sdk, "fragment_api_timeout_ms", 240000)); -} -function requireApiTokenFromSdk(sdk) { - const rawSecret = sdk.secrets?.get("fragment_api_token"); - const tokenFromSecrets = rawSecret ? String(rawSecret).trim() : ""; - if (tokenFromSecrets) { - return tokenFromSecrets; - } - const rawConfig = sdk.pluginConfig?.fragment_api_token; - const tokenFromConfig = typeof rawConfig === "string" ? rawConfig.trim() : ""; - if (tokenFromConfig) { - return tokenFromConfig; - } - throw new Error("fragment_api_token is required to call Fragment API (set plugin secret or plugin config)"); -} -function tokenHint(token) { - if (!token) - return "none"; - const trimmed = token.trim(); - if (!trimmed) - return "empty"; - const prefix = trimmed.slice(0, 2); - const suffix = trimmed.slice(-2); - return `${prefix}…${suffix} (len=${trimmed.length})`; -} -function tokenSource(sdk) { - if (sdk.secrets?.has("fragment_api_token")) - return "secrets"; - if (sdk.pluginConfig?.fragment_api_token !== undefined) - return "pluginConfig"; - return "none"; -} -export async function executeFragmentQuote(sdk, payload) { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/quote`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - try { - console.log("Fragment API request ->", { - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - payload, - }); - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - const rawText = await response.text(); - console.log("Fragment API response <-", { url: apiUrl, status: response.status, body: rawText }); - let parsed = {}; - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } - catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn("quote failed", JSON.stringify({ - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, null, 0)); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - return parsed; - } - catch (error) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } - finally { - clearTimeout(timeout); - } -} -export async function executeFragmentCreateOrder(sdk, payload) { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - const rawText = await response.text(); - let parsed = {}; - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } - catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn("order create failed", JSON.stringify({ - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, null, 0)); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - return parsed; - } - catch (error) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } - finally { - clearTimeout(timeout); - } -} -export async function executeFragmentProcessOrder(sdk, payload) { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders/process`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - try { - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - const rawText = await response.text(); - let parsed = {}; - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } - catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn("order process failed", JSON.stringify({ - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, null, 0)); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - return parsed; - } - catch (error) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } - finally { - clearTimeout(timeout); - } -} -//# sourceMappingURL=fragment-api-service.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map b/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map deleted file mode 100644 index 6ca6db9..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/fragment-api-service.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"fragment-api-service.js","sourceRoot":"","sources":["../../utils/fragment-api-service.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts b/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts deleted file mode 100644 index ce493f3..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-repository.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { CheckingOrderRow, Database, OrderRecord } from "./types.js"; -export declare function initSchema(db: Database): void; -export declare function getOrderByRef(db: Database, refId: string): OrderRecord | null; -export declare function getLatestActiveOrderForUser(db: Database, chatId: string, senderId: string): OrderRecord | null; -export declare function upsertOrder(db: Database, order: OrderRecord): void; -export declare function updateOrderStatus(db: Database, refId: string, status: OrderRecord["status"], updates?: Partial): OrderRecord | null; -export declare function listCheckingOrders(db: Database): CheckingOrderRow[]; diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.js b/plugins/fragment-stars-plugin/dist/utils/order-repository.js deleted file mode 100644 index ed3e19f..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-repository.js +++ /dev/null @@ -1,142 +0,0 @@ -export function initSchema(db) { - db.exec(` - CREATE TABLE IF NOT EXISTS used_transactions ( - tx_hash TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - amount REAL NOT NULL, - game_type TEXT NOT NULL, - used_at INTEGER NOT NULL - ) - `); - db.exec(` - CREATE TABLE IF NOT EXISTS stars_orders ( - ref_id TEXT PRIMARY KEY, - chat_id TEXT NOT NULL, - sender_id TEXT NOT NULL, - username TEXT NOT NULL, - quantity INTEGER NOT NULL, - base_amount_ton REAL NOT NULL DEFAULT 0, - amount_ton REAL NOT NULL, - lang TEXT, - refund_address TEXT, - refund_amount_nano TEXT, - platform_fee_percent REAL NOT NULL DEFAULT 0, - fragment_fee_percent REAL NOT NULL DEFAULT 0, - show_sender INTEGER NOT NULL, - status TEXT NOT NULL, - payment_tx TEXT, - payment_from TEXT, - fragment_order_json TEXT, - error TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - `); - ensureColumn(db, "stars_orders", "base_amount_ton", "REAL NOT NULL DEFAULT 0"); - ensureColumn(db, "stars_orders", "lang", "TEXT"); - ensureColumn(db, "stars_orders", "refund_address", "TEXT"); - ensureColumn(db, "stars_orders", "refund_amount_nano", "TEXT"); - ensureColumn(db, "stars_orders", "platform_fee_percent", "REAL NOT NULL DEFAULT 0"); - ensureColumn(db, "stars_orders", "fragment_fee_percent", "REAL NOT NULL DEFAULT 0"); -} -function ensureColumn(db, tableName, columnName, columnSpec) { - const columns = db.prepare(`PRAGMA table_info(${tableName})`).all(); - const exists = columns.some((c) => c.name === columnName); - if (!exists) { - db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSpec}`); - } -} -function mapOrderRow(row) { - if (!row) { - return null; - } - return { - refId: row.ref_id, - chatId: row.chat_id, - senderId: row.sender_id, - username: row.username, - quantity: Number(row.quantity), - baseAmountTon: Number(row.base_amount_ton), - amountTon: Number(row.amount_ton), - lang: (row.lang === "en" ? "en" : row.lang === "ru" ? "ru" : null) || undefined, - refundAddress: row.refund_address || null, - refundAmountNano: row.refund_amount_nano || null, - platformFeePercent: Number(row.platform_fee_percent), - fragmentFeePercent: Number(row.fragment_fee_percent), - show_sender: Boolean(row.show_sender), - status: row.status, - paymentTx: row.payment_tx || null, - paymentFrom: row.payment_from || null, - fragmentOrder: row.fragment_order_json ? JSON.parse(row.fragment_order_json) : null, - error: row.error || null, - createdAt: row.created_at, - updatedAt: row.updated_at, - }; -} -export function getOrderByRef(db, refId) { - const row = db.prepare("SELECT * FROM stars_orders WHERE ref_id = ?").get(refId); - return mapOrderRow(row); -} -export function getLatestActiveOrderForUser(db, chatId, senderId) { - const row = db - .prepare(` - SELECT * - FROM stars_orders - WHERE chat_id = ? - AND sender_id = ? - AND status IN ('pending', 'checking', 'paid') - ORDER BY updated_at DESC - LIMIT 1 - `) - .get(String(chatId), String(senderId)); - return mapOrderRow(row); -} -export function upsertOrder(db, order) { - const now = new Date().toISOString(); - db.prepare(` - INSERT INTO stars_orders ( - ref_id, chat_id, sender_id, username, quantity, base_amount_ton, amount_ton, - lang, - refund_address, refund_amount_nano, - platform_fee_percent, fragment_fee_percent, show_sender, status, - payment_tx, payment_from, fragment_order_json, error, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(ref_id) DO UPDATE SET - chat_id = excluded.chat_id, - sender_id = excluded.sender_id, - username = excluded.username, - quantity = excluded.quantity, - base_amount_ton = excluded.base_amount_ton, - amount_ton = excluded.amount_ton, - lang = excluded.lang, - refund_address = excluded.refund_address, - refund_amount_nano = excluded.refund_amount_nano, - platform_fee_percent = excluded.platform_fee_percent, - fragment_fee_percent = excluded.fragment_fee_percent, - show_sender = excluded.show_sender, - status = excluded.status, - payment_tx = excluded.payment_tx, - payment_from = excluded.payment_from, - fragment_order_json = excluded.fragment_order_json, - error = excluded.error, - updated_at = excluded.updated_at - `).run(order.refId, String(order.chatId), String(order.senderId), order.username, Number(order.quantity), Number(order.baseAmountTon || order.amountTon), Number(order.amountTon), order.lang || null, order.refundAddress || null, order.refundAmountNano || null, Number(order.platformFeePercent || 0), Number(order.fragmentFeePercent || 0), order.show_sender ? 1 : 0, order.status, order.paymentTx || null, order.paymentFrom || null, order.fragmentOrder ? JSON.stringify(order.fragmentOrder) : null, order.error || null, order.createdAt || now, now); -} -export function updateOrderStatus(db, refId, status, updates = {}) { - const current = getOrderByRef(db, refId); - if (!current) { - return null; - } - const next = { - ...current, - ...updates, - status, - updatedAt: new Date().toISOString(), - }; - upsertOrder(db, next); - return next; -} -export function listCheckingOrders(db) { - return db.prepare("SELECT ref_id, chat_id FROM stars_orders WHERE status = 'checking'").all(); -} -//# sourceMappingURL=order-repository.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map b/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map deleted file mode 100644 index 7e930db..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-repository.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"order-repository.js","sourceRoot":"","sources":["../../utils/order-repository.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts b/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts deleted file mode 100644 index 44f6dc2..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-status.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import type { RuntimeSdk } from "./types.js"; -export declare function setOrderStatus(sdk: RuntimeSdk, refId: string, status: string, extra?: Record, ttlMs?: number): void; diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.js b/plugins/fragment-stars-plugin/dist/utils/order-status.js deleted file mode 100644 index 81331fe..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-status.js +++ /dev/null @@ -1,12 +0,0 @@ -export function setOrderStatus(sdk, refId, status, extra = {}, ttlMs = 24 * 60 * 60 * 1000) { - const current = sdk.storage.get(`order:${refId}`) || {}; - const next = { - ...current, - ...extra, - refId, - status, - updatedAt: new Date().toISOString(), - }; - sdk.storage.set(`order:${refId}`, next, { ttl: ttlMs }); -} -//# sourceMappingURL=order-status.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/order-status.js.map b/plugins/fragment-stars-plugin/dist/utils/order-status.js.map deleted file mode 100644 index 6db1752..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/order-status.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"order-status.js","sourceRoot":"","sources":["../../utils/order-status.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,cAAc,CAC5B,GAAe,EACf,KAAa,EACb,MAAc,EACd,QAAiC,EAAE,EACnC,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI;IAE3B,MAAM,OAAO,GAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,CAAoC,IAAI,EAAE,CAAC;IAC5F,MAAM,IAAI,GAAG;QACX,GAAG,OAAO;QACV,GAAG,KAAK;QACR,KAAK;QACL,MAAM;QACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IACF,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1D,CAAC"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts deleted file mode 100644 index 9797ab6..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.d.ts +++ /dev/null @@ -1,103 +0,0 @@ -import type { PluginContext, RuntimeSdk } from "./types.js"; -export declare function createTools(sdk: RuntimeSdk, activeChecks: Set): ({ - name: string; - description: string; - parameters: { - type: string; - properties: { - username: { - type: string; - description: string; - }; - quantity: { - type: string; - description: string; - }; - stars: { - type: string; - description: string; - }; - show_sender: { - type: string; - description: string; - }; - lang: { - type: string; - description: string; - enum: string[]; - }; - ref_id?: undefined; - }; - required: string[]; - }; - execute(params: { - username: string; - quantity?: number; - stars?: number; - show_sender?: boolean; - lang?: "ru" | "en"; - }, context: PluginContext): Promise<{ - success: boolean; - error: string; - data?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - message: string; - force_user_message: boolean; - }; - error?: undefined; - }>; -} | { - name: string; - description: string; - parameters: { - type: string; - properties: { - ref_id: { - type: string; - description: string; - }; - lang: { - type: string; - description: string; - enum: string[]; - }; - username?: undefined; - quantity?: undefined; - stars?: undefined; - show_sender?: undefined; - }; - required: string[]; - }; - execute(params: { - ref_id?: string; - lang?: "ru" | "en"; - }, context: PluginContext): Promise<{ - success: boolean; - error: string; - data?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - fragment_order: Record | null; - message: string; - force_user_message?: undefined; - }; - error?: undefined; - } | { - success: boolean; - data: { - ref_id: string; - status: string; - message: string; - force_user_message: boolean; - fragment_order?: undefined; - }; - error?: undefined; - }>; -})[]; diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js deleted file mode 100644 index 1b60e1a..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js +++ /dev/null @@ -1,376 +0,0 @@ -import { createRefId, getPluginConfig, sleep } from "./common.js"; -import { setOrderStatus } from "./order-status.js"; -import { getLatestActiveOrderForUser, getOrderByRef as loadOrderByRef, updateOrderStatus as setDbOrderStatus, upsertOrder as saveOrder, } from "./order-repository.js"; -import { executeFragmentCreateOrder, executeFragmentProcessOrder } from "./fragment-api-service.js"; -function roundTon(value) { - return Number(Number(value).toFixed(9)); -} -function resolveLang(sdk, lang) { - const explicit = typeof lang === "string" ? lang.trim().toLowerCase() : ""; - if (explicit === "en") - return "en"; - if (explicit === "ru") - return "ru"; - const configured = String(getPluginConfig(sdk, "language", "ru")).trim().toLowerCase(); - return configured === "en" ? "en" : "ru"; -} -function formatFinalResultMessage(lang, refId, result) { - return lang === "en" - ? `Payment confirmed. Order sent to Fragment; wait for Stars delivery.\n` + - `ref_id: ${refId}\n` + - `req_id: ${String(result?.req_id || "-")}\n` + - `tx_hash: ${String(result?.tx_hash || "-")}` - : `Платёж подтверждён, заказ отправлен в Fragment, ожидайте получение звёзд\n` + - `ref_id: ${refId}\n` + - `req_id: ${String(result?.req_id || "-")}\n` + - `tx_hash: ${String(result?.tx_hash || "-")}`; -} -async function pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang) { - const feeAddress = sdk.ton.getAddress(); - const startedAt = Date.now(); - const maxDurationMs = 15 * 60_000; - const pollIntervalMs = 5_000; - const progressUpdateEveryMs = 30_000; - let lastProgressAt = 0; - const updateText = async (text) => { - if (messageId && sdk.telegram.editMessage) { - await sdk.telegram.editMessage(chatId, messageId, text); - } - else { - await sdk.telegram.sendMessage(chatId, text); - } - }; - try { - while (Date.now() - startedAt < maxDurationMs) { - let result; - try { - console.log("Fragment processOrder ->", { ref_id: refId, fee_address: feeAddress || undefined }); - result = await executeFragmentProcessOrder(sdk, { - ref_id: refId, - fee_address: feeAddress || undefined, - }); - console.log("Fragment processOrder <-", { ref_id: refId, result }); - } - catch { - if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { - lastProgressAt = Date.now(); - await updateText(lang === "en" - ? `Payment check service is temporarily unavailable. Retrying...\nref_id: ${refId}` - : `Сервис проверки оплаты временно недоступен. Продолжаю попытки...\nref_id: ${refId}`); - } - await sleep(pollIntervalMs); - continue; - } - if (result?.ok) { - await updateText(formatFinalResultMessage(lang, refId, result)); - return; - } - const status = String(result?.status || "awaiting_payment"); - if (status === "awaiting_payment") { - if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { - lastProgressAt = Date.now(); - await updateText(lang === "en" ? `Checking payment for order ${refId}...` : `Проверяю оплату по заказу ${refId}...`); - } - await sleep(pollIntervalMs); - continue; - } - const errorText = String(result?.error || result?.message || "unknown error"); - await updateText(lang === "en" ? `Failed to process order ${refId}: ${errorText}` : `Не удалось обработать заказ ${refId}: ${errorText}`); - return; - } - await updateText(lang === "en" - ? `Payment for order ${refId} was not found within 15 minutes.\n` + - `If you paid — wait a bit and then send: "check payment ${refId}".` - : `Оплата по заказу ${refId} не найдена за 15 минут.\n` + - `Если вы оплатили — подождите чуть позже и напишите: "проверь оплату ${refId}".`); - } - finally { - activeChecks.delete(refId); - } -} -export function createTools(sdk, activeChecks) { - return [ - { - name: "fragment_stars_create_payment", - description: "Шаг 1/2. Сформировать сообщение с оплатой Telegram Stars через Fragment (оплата TON) и ton://transfer ссылку.\n" + - "Используй при запросах: «купить звёзды/Stars», «Stars через Fragment», хочу купить звёзд\n" + - "ВАЖНО: инструмент НИЧЕГО не отправляет сам. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", - parameters: { - type: "object", - properties: { - username: { type: "string", description: "Telegram username without @ (кому покупаем звёзды)" }, - quantity: { type: "number", description: "Сколько звёзд купить (минимум 50)" }, - stars: { type: "number", description: "Алиас для quantity" }, - show_sender: { type: "boolean", description: "Показывать отправителя в Fragment (по умолчанию false)" }, - lang: { - type: "string", - description: "ОПРЕДЕЛИ ЯЗЫК ПОЛЬЗОВАТЕЛЯ. Если он пишет на русском — 'ru', если на английском — 'en'.", - enum: ["ru", "en"], - }, - }, - required: ["username", "lang"], - }, - async execute(params, context) { - const rawQuantity = params.quantity ?? params.stars; - if (rawQuantity === undefined || rawQuantity === null) { - return { - success: false, - error: "quantity is required (you can also pass it as stars)", - }; - } - const quantity = Number(rawQuantity); - if (!Number.isFinite(quantity) || quantity <= 0) { - return { success: false, error: "quantity must be a positive number" }; - } - if (quantity < 50) { - return { - success: false, - error: resolveLang(sdk, params.lang) === "en" - ? "Stars amount must be at least 50" - : "Количество звёзд должно быть не меньше 50", - }; - } - const refId = createRefId(String(context.senderId ?? "unknown")); - const feeAddress = sdk.ton.getAddress(); - if (!feeAddress) { - return { - success: false, - error: resolveLang(sdk, params.lang) === "en" - ? "TON wallet address is not available in this runtime" - : "Адрес TON кошелька недоступен в этом окружении", - }; - } - let orderCreate; - try { - console.log("Fragment createOrder ->", { - payload: { - username: String(params.username).replace(/^@/, ""), - quantity, - show_sender: Boolean(params.show_sender), - ref_id: refId, - fee_address: feeAddress, - }, - }); - orderCreate = await executeFragmentCreateOrder(sdk, { - username: String(params.username).replace(/^@/, ""), - quantity, - show_sender: Boolean(params.show_sender), - ref_id: refId, - fee_address: feeAddress, - }); - console.log("Fragment createOrder <-", { ref_id: refId, result: orderCreate }); - } - catch { - return { - success: true, - data: { - ref_id: refId, - status: "error", - message: resolveLang(sdk, params.lang) === "en" - ? `Payment service is temporarily unavailable (order creation failed). Try again in 1–2 minutes.\n` + - `If it keeps failing — contact the administrator.\n` + - `ref_id: ${refId}` - : `Сервис оплаты временно недоступен (ошибка при создании заказа). Попробуйте ещё раз через 1–2 минуты.\n` + - `Если ошибка повторяется — напишите администратору.\n` + - `ref_id: ${refId}`, - force_user_message: true, - }, - }; - } - if (!orderCreate.ok) { - return { - success: true, - data: { - ref_id: refId, - status: "error", - message: resolveLang(sdk, params.lang) === "en" - ? `Failed to create order: ${orderCreate.message || "unknown error"}` - : `Не удалось создать заказ: ${orderCreate.message || "unknown error"}`, - force_user_message: true, - }, - }; - } - const baseAmountTon = roundTon(Number(orderCreate.fragment_cost_ton)); - const amountTon = roundTon(Number(orderCreate.pay_amount_ton)); - const amountNano = String(orderCreate.pay_amount_nano || "").trim(); - if (!amountNano || !/^\d+$/.test(amountNano)) { - return { success: false, error: "Invalid pay_amount_nano from API" }; - } - const payToAddress = "UQDFOnNC_cgSJqbpH_k9hH8OqkuxvBeO5LUlE_x8wsQitGVJ"; - console.log("Generated payment details", { ref_id: refId, amountTon, amountNano, payToAddress, deepLinkFromApi: orderCreate.pay_deeplink }); - const deepLinkRawFromApi = String(orderCreate.pay_deeplink || "").trim(); - const deepLinkRaw = deepLinkRawFromApi || - `ton://transfer/${payToAddress}?amount=${amountNano}&text=${encodeURIComponent(refId)}`; - console.log("Resolved deep link", { ref_id: refId, deepLinkRaw }); - if (!deepLinkRaw) { - return { success: false, error: "Invalid pay_deeplink from API" }; - } - const lang = params.lang; - const paymentDetailsRu = `\nДетали платежа\n` + - `Куда (адрес): \`${payToAddress}\`\n` + - `Сумма: ${amountTon} TON\n` + - `Комментарий (memo): \`${refId}\`\n`; - const paymentDetailsEn = `\nPayment details\n` + - `To (address): \`${payToAddress}\`\n` + - `Amount: ${amountTon} TON\n` + - `Comment (memo): \`${refId}\`\n`; - console.log("Final payment details", { ref_id: refId, paymentDetailsRu, paymentDetailsEn }); - const order = { - refId, - chatId: String(context.chatId), - senderId: String(context.senderId), - username: String(params.username).replace(/^@/, ""), - quantity, - baseAmountTon, - amountTon, - lang, - refundAddress: null, - refundAmountNano: null, - platformFeePercent: 1, - fragmentFeePercent: 0, - show_sender: Boolean(params.show_sender), - status: "pending", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - saveOrder(sdk.db, order); - const ttlMs = Number(getPluginConfig(sdk, "payment_ttl_minutes", 15)) * 60_000; - setOrderStatus(sdk, refId, "pending", order, ttlMs); - const deepLink = deepLinkRaw; - const labels = lang === "en" ? { - header: "📦 *Order: Telegram Stars*", - account: "👤 *Account:*", - quantity: "⭐️ *Quantity:*", - detailsHeader: "💳 *Payment details:*", - address: "Address:", - amount: "Amount :", - memo: "Memo :", - action: "🔗 Open payment link" - } : { - header: "📦 *Заказ: Telegram Stars*", - account: "👤 *Аккаунт:*", - quantity: "⭐️ *Количество:*", - detailsHeader: "💳 *Реквизиты для оплаты:*", - address: "Адрес :", - amount: "Сумма :", - memo: "Memo :", - action: "🔗 Открыть ссылку на оплату" - }; - const text = ` - ${labels.header} - ━━━━━━━━━━━━━━━━━━━━ - ${labels.account} @${order.username} - ${labels.quantity} ${quantity} - - ${labels.detailsHeader} - \`${labels.address}\` \`${payToAddress}\` - \`${labels.amount}\` \`${amountTon} TON\` - \`${labels.memo}\` \`${refId}\` - - ${labels.action} - `.trim(); - return { - success: true, - data: { - ref_id: refId, - status: "pending", - message: text, - force_user_message: true, - }, - }; - }, - }, - { - name: "fragment_stars_confirm_payment", - description: "Шаг 2/2. Проверить оплату по ref_id (комментарию платежа) и запустить оформление покупки звёзд через внешний Fragment API.\n" + - "Используй, когда пользователь пишет: «проверь оплату », «я оплатил», «я отправил». 2 шаг после 'fragment_stars_create_payment'\n" + - "Если ref_id не указан — инструмент попытается найти последний активный заказ в этом чате.\n" + - "ВАЖНО: не вызывай ton_my_transactions. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", - parameters: { - type: "object", - properties: { - ref_id: { type: "string", description: "ref_id из шага 1 (можно не указывать, если пользователь просто «я оплатил»)" }, - lang: { - type: "string", - description: "Language for the message: ru | en (default: order.lang or plugin config language)", - enum: ["ru", "en"], - }, - }, - required: ["lang"], - }, - async execute(params, context) { - const explicitRefId = typeof params.ref_id === "string" ? params.ref_id.trim() : ""; - const inferredOrder = !explicitRefId && context.chatId && context.senderId - ? getLatestActiveOrderForUser(sdk.db, String(context.chatId), String(context.senderId)) - : null; - const refId = explicitRefId || inferredOrder?.refId || ""; - if (!refId) { - return { - success: false, - error: resolveLang(sdk, params.lang) === "en" - ? 'ref_id is required. Send: "check payment " (ref_id is shown in the payment message).' - : 'ref_id is required. Send: "проверь оплату " (ref_id is shown in the payment message).', - }; - } - const order = loadOrderByRef(sdk.db, refId); - if (!order) { - return { - success: false, - error: resolveLang(sdk, params.lang || order?.lang) === "en" - ? `Order not found or expired. Create a new payment link.` - : `Заказ не найден или истёк. Создайте новую ссылку на оплату.`, - }; - } - const lang = resolveLang(sdk, params.lang || order.lang); - if (order.status === "ordered") { - const text = lang === "en" - ? `Order ${refId} is already placed. If Stars haven't arrived yet — wait a couple of minutes.` - : `Заказ ${refId} уже оформлен. Если звёзды ещё не пришли — подождите пару минут.`; - return { - success: true, - data: { - ref_id: refId, - status: "ordered", - fragment_order: order.fragmentOrder || null, - message: text, - }, - }; - } - const feeAddress = sdk.ton.getAddress(); - if (!feeAddress) { - return { - success: false, - error: lang === "en" - ? `TON wallet address is not available in this runtime.` - : `Адрес TON кошелька недоступен в этом окружении.`, - }; - } - if (activeChecks.has(refId) || order.status === "checking") { - const text = lang === "en" - ? `Payment check for order ${refId} is already running. I'll send the result in a separate message.` - : `Проверка оплаты по заказу ${refId} уже идёт. Я пришлю результат отдельным сообщением.`; - return { success: true, data: { ref_id: refId, status: "checking", message: text, force_user_message: true } }; - } - setDbOrderStatus(sdk.db, refId, "checking", { error: null }); - setOrderStatus(sdk, refId, "checking", { error: null }); - activeChecks.add(refId); - const chatId = String(context.chatId); - const startMessage = lang === "en" - ? `Started background payment check for order ${refId} (up to 15 minutes). I'll send the result in a separate message.` - : `Запустил фоновую проверку оплаты по заказу ${refId} (до 15 минут). Пришлю результат отдельным сообщением.`; - let messageId = null; - void pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang); - return { - success: true, - data: { - ref_id: refId, - status: "checking", - message: startMessage, - force_user_message: true, - }, - }; - }, - }, - ]; -} -//# sourceMappingURL=tool-definitions.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map b/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map deleted file mode 100644 index 98093c8..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/tool-definitions.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"tool-definitions.js","sourceRoot":"","sources":["../../utils/tool-definitions.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/types.d.ts b/plugins/fragment-stars-plugin/dist/utils/types.d.ts deleted file mode 100644 index fb21e58..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/types.d.ts +++ /dev/null @@ -1,108 +0,0 @@ -export type OrderStatus = "pending" | "checking" | "paid" | "ordered" | "failed"; -export interface OrderRecord { - refId: string; - chatId: string; - senderId: string; - username: string; - quantity: number; - baseAmountTon: number; - amountTon: number; - lang?: "ru" | "en"; - refundAddress?: string | null; - refundAmountNano?: string | null; - platformFeePercent: number; - fragmentFeePercent: number; - show_sender: boolean; - status: OrderStatus; - paymentTx?: string | null; - paymentFrom?: string | null; - fragmentOrder?: Record | null; - error?: string | null; - createdAt?: string; - updatedAt?: string; -} -export interface CheckingOrderRow { - ref_id: string; - chat_id: string; -} -export interface SqlStatement { - all(...args: unknown[]): any[]; - get(...args: unknown[]): any; - run(...args: unknown[]): void; -} -export interface Database { - exec(sql: string): void; - prepare(sql: string): SqlStatement; -} -export interface RuntimeSdk { - config?: Record; - pluginConfig?: Record; - secrets?: { - get(key: string): string | undefined; - require(key: string): string; - has(key: string): boolean; - }; - db: Database; - storage: { - get(key: string): unknown; - set(key: string, value: unknown, options?: { - ttl?: number; - }): void; - }; - telegram: { - sendMessage(chatId: string, text: string, opts?: unknown): Promise; - editMessage?(chatId: string, messageId: number, text: string, opts?: unknown): Promise; - }; - ton: { - getAddress(): string | null; - getTransactions(address: string, limit?: number): Promise>; - verifyPayment(payload: { - amount: number; - memo: string; - gameType: string; - maxAgeMinutes: number; - }): Promise<{ - verified: boolean; - error?: string; - txHash?: string; - playerWallet?: string; - }>; - sendTON(address: string, amountTon: number, memo: string): Promise<{ - txHash?: string; - hash?: string; - }>; - }; - log: { - info(...args: unknown[]): void; - warn(...args: unknown[]): void; - error(...args: unknown[]): void; - debug(...args: unknown[]): void; - }; -} -export interface PluginContext { - chatId?: string | number; - senderId?: string | number; - config?: Record; - pluginConfig?: Record; - secrets?: Record; -} -export interface PluginManifest { - name: string; - version: string; - author: string; - description: string; - sdkVersion: string; - defaultConfig: Record; - secrets: Record; -} diff --git a/plugins/fragment-stars-plugin/dist/utils/types.js b/plugins/fragment-stars-plugin/dist/utils/types.js deleted file mode 100644 index 718fd38..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/types.js +++ /dev/null @@ -1,2 +0,0 @@ -export {}; -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/dist/utils/types.js.map b/plugins/fragment-stars-plugin/dist/utils/types.js.map deleted file mode 100644 index 6c1e324..0000000 --- a/plugins/fragment-stars-plugin/dist/utils/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["../../utils/types.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/index.js b/plugins/fragment-stars-plugin/index.js deleted file mode 100644 index 5aa671b..0000000 --- a/plugins/fragment-stars-plugin/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import * as mod from "./dist/index.js"; - -export const manifest = mod.manifest; -export const tools = mod.tools; -export const migrate = mod.migrate; -export const start = mod.start; -export const stop = mod.stop; -export const onMessage = mod.onMessage; -export const onCallbackQuery = mod.onCallbackQuery; - diff --git a/plugins/fragment-stars-plugin/index.ts b/plugins/fragment-stars-plugin/index.ts deleted file mode 100644 index 462ca22..0000000 --- a/plugins/fragment-stars-plugin/index.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { initSchema } from "./utils/order-repository.js"; -import { createTools } from "./utils/tool-definitions.js"; -import type { PluginContext, PluginManifest, RuntimeSdk } from "./utils/types.js"; - -export const manifest: PluginManifest = { - name: "fragment-stars-plugin", - version: "1.0.0", - author: "d1nckache", - description: "Buy Telegram Stars through TON payment and Fragment API", - sdkVersion: ">=1.0.0", - defaultConfig: { - fragment_api_url: "http://127.0.0.1:8000/api/v1/stars", - fragment_api_timeout_ms: 240000, - payment_ttl_minutes: 15, - }, - secrets: { - fragment_api_token: { - required: true, - description: "Required token for X-Fragment-Api-Token header to Fragment Stars API", - }, - }, -}; - -const activeChecks = new Set(); -let runtimeSdk: RuntimeSdk | null = null; - -export function migrate(db: RuntimeSdk["db"]): void { - initSchema(db); -} - -export async function start(ctx: PluginContext): Promise { - -} - -export const tools = (sdk: RuntimeSdk) => { - runtimeSdk = sdk; - return createTools(sdk, activeChecks); -}; diff --git a/plugins/fragment-stars-plugin/package-lock.json b/plugins/fragment-stars-plugin/package-lock.json deleted file mode 100644 index 9d40822..0000000 --- a/plugins/fragment-stars-plugin/package-lock.json +++ /dev/null @@ -1,471 +0,0 @@ -{ - "name": "fragment-stars-plugin", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "fragment-stars-plugin", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@ton/core": "^0.63.1", - "@ton/crypto": "^3.3.0", - "@ton/ton": "^16.2.2", - "buffer": "^6.0.3" - }, - "devDependencies": { - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - } - }, - "node_modules/@ton/core": { - "version": "0.63.1", - "resolved": "https://registry.npmjs.org/@ton/core/-/core-0.63.1.tgz", - "integrity": "sha512-hDWMjlKzc18W2E4OeV3hUP8ohRJNHPD4Wd1+AQJj8zshZyCRT0usrvnExgbNUTo/vntDqCGMzgYWbXxyaA+L4g==", - "license": "MIT", - "peerDependencies": { - "@ton/crypto": ">=3.2.0" - } - }, - "node_modules/@ton/crypto": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@ton/crypto/-/crypto-3.3.0.tgz", - "integrity": "sha512-/A6CYGgA/H36OZ9BbTaGerKtzWp50rg67ZCH2oIjV1NcrBaCK9Z343M+CxedvM7Haf3f/Ee9EhxyeTp0GKMUpA==", - "license": "MIT", - "dependencies": { - "@ton/crypto-primitives": "2.1.0", - "jssha": "3.2.0", - "tweetnacl": "1.0.3" - } - }, - "node_modules/@ton/crypto-primitives": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@ton/crypto-primitives/-/crypto-primitives-2.1.0.tgz", - "integrity": "sha512-PQesoyPgqyI6vzYtCXw4/ZzevePc4VGcJtFwf08v10OevVJHVfW238KBdpj1kEDQkxWLeuNHEpTECNFKnP6tow==", - "license": "MIT", - "dependencies": { - "jssha": "3.2.0" - } - }, - "node_modules/@ton/ton": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/@ton/ton/-/ton-16.2.2.tgz", - "integrity": "sha512-yEOw4IW3gpRZxJAcILMI4dQ1d5/eAAbD2VU/Iwc6z7f2jt1mLDWVED8yn2vLNucQfZr+1eaqYHLztYVFZ7PKmw==", - "license": "MIT", - "dependencies": { - "axios": "^1.6.7", - "dataloader": "^2.0.0", - "zod": "^3.21.4" - }, - "peerDependencies": { - "@ton/core": ">=0.63.0 <1.0.0", - "@ton/crypto": ">=3.2.0" - } - }, - "node_modules/@ton/ton/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@types/node": { - "version": "25.3.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", - "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", - "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/dataloader": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", - "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", - "license": "MIT" - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/jssha": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.2.0.tgz", - "integrity": "sha512-QuruyBENDWdN4tZwJbQq7/eAK85FqrI4oDbXjy5IBhYD+2pTJyBUWZe8ctWaCkrV0gy6AaelgOZZBMeswEa/6Q==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "license": "Unlicense" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - } - } -} diff --git a/plugins/fragment-stars-plugin/package.json b/plugins/fragment-stars-plugin/package.json deleted file mode 100644 index c16f994..0000000 --- a/plugins/fragment-stars-plugin/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "type": "module", - "dependencies": { - "@ton/core": "^0.63.1", - "@ton/crypto": "^3.3.0", - "@ton/ton": "^16.2.2", - "buffer": "^6.0.3" - }, - "name": "fragment-stars-plugin", - "version": "1.0.0", - "main": "index.js", - "devDependencies": { - "@types/node": "^25.3.0", - "typescript": "^5.9.3" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "keywords": [], - "author": "d1nckache", - "license": "ISC", - "description": "" -} diff --git a/plugins/fragment-stars-plugin/tsconfig.json b/plugins/fragment-stars-plugin/tsconfig.json deleted file mode 100644 index 03c9030..0000000 --- a/plugins/fragment-stars-plugin/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "strict": true, - "skipLibCheck": true, - "declaration": true, - "sourceMap": true, - "outDir": "dist", - "rootDir": ".", - "types": ["node"] - }, - "include": ["index.ts", "utils/**/*.ts"], - "exclude": ["node_modules", "dist"] -} diff --git a/plugins/fragment-stars-plugin/utils/common.ts b/plugins/fragment-stars-plugin/utils/common.ts deleted file mode 100644 index 01ed0ea..0000000 --- a/plugins/fragment-stars-plugin/utils/common.ts +++ /dev/null @@ -1,31 +0,0 @@ -const NANO = 1_000_000_000; - -export function toNano(ton: number | string): string { - return String(Math.round(Number(ton) * NANO)); -} - -export function createRefId(senderId: string | number): string { - return `stars-${senderId}-${Date.now()}`; -} - -export function getConfig( - context: { pluginConfig?: Record; config?: Record } | undefined, - key: string, - fallback: T, -): T { - const raw = context?.pluginConfig?.[key] ?? context?.config?.[key]; - return (raw === undefined ? fallback : raw) as T; -} - -export function getPluginConfig( - sdk: { pluginConfig?: Record } | undefined, - key: string, - fallback: T, -): T { - const raw = sdk?.pluginConfig?.[key]; - return (raw === undefined ? fallback : raw) as T; -} - -export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/plugins/fragment-stars-plugin/utils/fragment-api-service.ts b/plugins/fragment-stars-plugin/utils/fragment-api-service.ts deleted file mode 100644 index 9430b00..0000000 --- a/plugins/fragment-stars-plugin/utils/fragment-api-service.ts +++ /dev/null @@ -1,316 +0,0 @@ -import { getPluginConfig } from "./common.js"; -import type { RuntimeSdk } from "./types.js"; - -interface FragmentApiQuotePayload { - username: string; - quantity: number; - show_sender: boolean; -} - -export interface FragmentApiPurchaseResult { - ok: boolean; - status: string; - message: string; - purchase_id: string; - ref_id: string; - req_id?: string | null; - cost_ton?: string | null; - tx_hash?: string | null; - tx_to?: string | null; - tx_amount_nano?: string | null; - refund_address?: string | null; - refund_amount_nano?: string | null; - error?: string | null; -} - -export interface FragmentApiQuoteResult { - ok: boolean; - message: string; - username: string; - quantity: number; - fragment_cost_ton: string; - pay_amount_ton: string; - pay_amount_nano: string; -} - -export interface FragmentApiCreateOrderPayload { - username: string; - quantity: number; - show_sender: boolean; - ref_id: string; - fee_address: string; -} - -export interface FragmentApiCreateOrderResult { - ok: boolean; - message: string; - purchase_id: string; - ref_id: string; - pay_to_address: string; - pay_deeplink: string; - fragment_cost_ton: string; - pay_amount_ton: string; - pay_amount_nano: string; -} - -export interface FragmentApiProcessOrderPayload { - ref_id: string; - fee_address?: string; -} - -export interface FragmentApiProcessOrderResult { - ok: boolean; - status: string; - message: string; - purchase_id: string; - ref_id: string; - req_id?: string | null; - cost_ton?: string | null; - tx_hash?: string | null; - tx_to?: string | null; - tx_amount_nano?: string | null; - refund_address?: string | null; - refund_amount_nano?: string | null; - payment_tx?: string | null; - payment_from?: string | null; - error?: string | null; -} - -function getStarsBaseUrlFromSdk(sdk: RuntimeSdk): string { - const raw = String(getPluginConfig(sdk, "fragment_api_url", "http://127.0.0.1:8000/api/v1/stars")); - const trimmed = raw.replace(/\/$/, ""); - if (trimmed.endsWith("/purchase")) return trimmed.slice(0, -"/purchase".length); - if (trimmed.endsWith("/quote")) return trimmed.slice(0, -"/quote".length); - return trimmed; -} - -function getApiTimeoutMsFromSdk(sdk: RuntimeSdk): number { - return Number(getPluginConfig(sdk, "fragment_api_timeout_ms", 240000)); -} - -function requireApiTokenFromSdk(sdk: RuntimeSdk): string { - const rawSecret = sdk.secrets?.get("fragment_api_token"); - const tokenFromSecrets = rawSecret ? String(rawSecret).trim() : ""; - if (tokenFromSecrets) { - return tokenFromSecrets; - } - - const rawConfig = sdk.pluginConfig?.fragment_api_token; - const tokenFromConfig = typeof rawConfig === "string" ? rawConfig.trim() : ""; - if (tokenFromConfig) { - return tokenFromConfig; - } - - throw new Error("fragment_api_token is required to call Fragment API (set plugin secret or plugin config)"); -} - -function tokenHint(token: string | null): string { - if (!token) return "none"; - const trimmed = token.trim(); - if (!trimmed) return "empty"; - const prefix = trimmed.slice(0, 2); - const suffix = trimmed.slice(-2); - return `${prefix}…${suffix} (len=${trimmed.length})`; -} - -function tokenSource(sdk: RuntimeSdk): "secrets" | "pluginConfig" | "none" { - if (sdk.secrets?.has("fragment_api_token")) return "secrets"; - if (sdk.pluginConfig?.fragment_api_token !== undefined) return "pluginConfig"; - return "none"; -} - -export async function executeFragmentQuote( - sdk: RuntimeSdk, - payload: FragmentApiQuotePayload, -): Promise { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/quote`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - - try { - console.log("Fragment API request ->", { - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - payload, - }); - - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - - const rawText = await response.text(); - - console.log("Fragment API response <-", { url: apiUrl, status: response.status, body: rawText }); - - let parsed: any = {}; - - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn( - "quote failed", - JSON.stringify( - { - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, - null, - 0, - ), - ); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - - return parsed as FragmentApiQuoteResult; - } catch (error: any) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } finally { - clearTimeout(timeout); - } -} - -export async function executeFragmentCreateOrder( - sdk: RuntimeSdk, - payload: FragmentApiCreateOrderPayload, -): Promise { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - - try { - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - - const rawText = await response.text(); - let parsed: any = {}; - - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn( - "order create failed", - JSON.stringify( - { - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, - null, - 0, - ), - ); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - - return parsed as FragmentApiCreateOrderResult; - } catch (error: any) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } finally { - clearTimeout(timeout); - } -} - -export async function executeFragmentProcessOrder( - sdk: RuntimeSdk, - payload: FragmentApiProcessOrderPayload, -): Promise { - const apiUrl = `${getStarsBaseUrlFromSdk(sdk)}/orders/process`; - const timeoutMs = getApiTimeoutMsFromSdk(sdk); - const token = requireApiTokenFromSdk(sdk); - const source = tokenSource(sdk); - - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), timeoutMs); - - try { - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "content-type": "application/json", - "x-fragment-api-token": token, - }, - body: JSON.stringify(payload), - signal: controller.signal, - }); - - const rawText = await response.text(); - let parsed: any = {}; - - try { - parsed = rawText ? JSON.parse(rawText) : {}; - } catch { - parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}` }; - } - - if (!response.ok) { - const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${response.status}`; - sdk.log?.warn( - "order process failed", - JSON.stringify( - { - status: response.status, - url: apiUrl, - token_source: source, - token_hint: tokenHint(token), - detail: String(detail).slice(0, 200), - }, - null, - 0, - ), - ); - throw new Error(`Fragment API request failed (${response.status}): ${String(detail)}`); - } - - return parsed as FragmentApiProcessOrderResult; - } catch (error: any) { - if (error?.name === "AbortError") { - throw new Error(`Fragment API timed out after ${timeoutMs} ms`); - } - throw error; - } finally { - clearTimeout(timeout); - } -} diff --git a/plugins/fragment-stars-plugin/utils/order-repository.ts b/plugins/fragment-stars-plugin/utils/order-repository.ts deleted file mode 100644 index 4333906..0000000 --- a/plugins/fragment-stars-plugin/utils/order-repository.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { CheckingOrderRow, Database, OrderRecord } from "./types.js"; - - -export function initSchema(db: Database): void { - db.exec(` - CREATE TABLE IF NOT EXISTS used_transactions ( - tx_hash TEXT PRIMARY KEY, - user_id TEXT NOT NULL, - amount REAL NOT NULL, - game_type TEXT NOT NULL, - used_at INTEGER NOT NULL - ) - `); - - db.exec(` - CREATE TABLE IF NOT EXISTS stars_orders ( - ref_id TEXT PRIMARY KEY, - chat_id TEXT NOT NULL, - sender_id TEXT NOT NULL, - username TEXT NOT NULL, - quantity INTEGER NOT NULL, - base_amount_ton REAL NOT NULL DEFAULT 0, - amount_ton REAL NOT NULL, - lang TEXT, - refund_address TEXT, - refund_amount_nano TEXT, - platform_fee_percent REAL NOT NULL DEFAULT 0, - fragment_fee_percent REAL NOT NULL DEFAULT 0, - show_sender INTEGER NOT NULL, - status TEXT NOT NULL, - payment_tx TEXT, - payment_from TEXT, - fragment_order_json TEXT, - error TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL - ) - `); - - ensureColumn(db, "stars_orders", "base_amount_ton", "REAL NOT NULL DEFAULT 0"); - ensureColumn(db, "stars_orders", "lang", "TEXT"); - ensureColumn(db, "stars_orders", "refund_address", "TEXT"); - ensureColumn(db, "stars_orders", "refund_amount_nano", "TEXT"); - ensureColumn(db, "stars_orders", "platform_fee_percent", "REAL NOT NULL DEFAULT 0"); - ensureColumn(db, "stars_orders", "fragment_fee_percent", "REAL NOT NULL DEFAULT 0"); -} - -function ensureColumn(db: Database, tableName: string, columnName: string, columnSpec: string): void { - const columns = db.prepare(`PRAGMA table_info(${tableName})`).all() as Array<{ name: string }>; - const exists = columns.some((c) => c.name === columnName); - if (!exists) { - db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnSpec}`); - } -} - -function mapOrderRow(row: any): OrderRecord | null { - if (!row) { - return null; - } - return { - refId: row.ref_id, - chatId: row.chat_id, - senderId: row.sender_id, - username: row.username, - quantity: Number(row.quantity), - baseAmountTon: Number(row.base_amount_ton), - amountTon: Number(row.amount_ton), - lang: (row.lang === "en" ? "en" : row.lang === "ru" ? "ru" : null) || undefined, - refundAddress: row.refund_address || null, - refundAmountNano: row.refund_amount_nano || null, - platformFeePercent: Number(row.platform_fee_percent), - fragmentFeePercent: Number(row.fragment_fee_percent), - show_sender: Boolean(row.show_sender), - status: row.status, - paymentTx: row.payment_tx || null, - paymentFrom: row.payment_from || null, - fragmentOrder: row.fragment_order_json ? JSON.parse(row.fragment_order_json) : null, - error: row.error || null, - createdAt: row.created_at, - updatedAt: row.updated_at, - }; -} - -export function getOrderByRef(db: Database, refId: string): OrderRecord | null { - const row = db.prepare("SELECT * FROM stars_orders WHERE ref_id = ?").get(refId); - return mapOrderRow(row); -} - -export function getLatestActiveOrderForUser( - db: Database, - chatId: string, - senderId: string, -): OrderRecord | null { - const row = db - .prepare( - ` - SELECT * - FROM stars_orders - WHERE chat_id = ? - AND sender_id = ? - AND status IN ('pending', 'checking', 'paid') - ORDER BY updated_at DESC - LIMIT 1 - `, - ) - .get(String(chatId), String(senderId)); - return mapOrderRow(row); -} - -export function upsertOrder(db: Database, order: OrderRecord): void { - const now = new Date().toISOString(); - db.prepare(` - INSERT INTO stars_orders ( - ref_id, chat_id, sender_id, username, quantity, base_amount_ton, amount_ton, - lang, - refund_address, refund_amount_nano, - platform_fee_percent, fragment_fee_percent, show_sender, status, - payment_tx, payment_from, fragment_order_json, error, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(ref_id) DO UPDATE SET - chat_id = excluded.chat_id, - sender_id = excluded.sender_id, - username = excluded.username, - quantity = excluded.quantity, - base_amount_ton = excluded.base_amount_ton, - amount_ton = excluded.amount_ton, - lang = excluded.lang, - refund_address = excluded.refund_address, - refund_amount_nano = excluded.refund_amount_nano, - platform_fee_percent = excluded.platform_fee_percent, - fragment_fee_percent = excluded.fragment_fee_percent, - show_sender = excluded.show_sender, - status = excluded.status, - payment_tx = excluded.payment_tx, - payment_from = excluded.payment_from, - fragment_order_json = excluded.fragment_order_json, - error = excluded.error, - updated_at = excluded.updated_at - `).run( - order.refId, - String(order.chatId), - String(order.senderId), - order.username, - Number(order.quantity), - Number(order.baseAmountTon || order.amountTon), - Number(order.amountTon), - order.lang || null, - order.refundAddress || null, - order.refundAmountNano || null, - Number(order.platformFeePercent || 0), - Number(order.fragmentFeePercent || 0), - order.show_sender ? 1 : 0, - order.status, - order.paymentTx || null, - order.paymentFrom || null, - order.fragmentOrder ? JSON.stringify(order.fragmentOrder) : null, - order.error || null, - order.createdAt || now, - now, - ); -} - -export function updateOrderStatus( - db: Database, - refId: string, - status: OrderRecord["status"], - updates: Partial = {}, -): OrderRecord | null { - const current = getOrderByRef(db, refId); - if (!current) { - return null; - } - - const next: OrderRecord = { - ...current, - ...updates, - status, - updatedAt: new Date().toISOString(), - }; - - upsertOrder(db, next); - return next; -} - -export function listCheckingOrders(db: Database): CheckingOrderRow[] { - return db.prepare("SELECT ref_id, chat_id FROM stars_orders WHERE status = 'checking'").all() as CheckingOrderRow[]; -} diff --git a/plugins/fragment-stars-plugin/utils/order-status.ts b/plugins/fragment-stars-plugin/utils/order-status.ts deleted file mode 100644 index c625903..0000000 --- a/plugins/fragment-stars-plugin/utils/order-status.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { RuntimeSdk } from "./types.js"; - -export function setOrderStatus( - sdk: RuntimeSdk, - refId: string, - status: string, - extra: Record = {}, - ttlMs = 24 * 60 * 60 * 1000, -): void { - const current = (sdk.storage.get(`order:${refId}`) as Record | null) || {}; - const next = { - ...current, - ...extra, - refId, - status, - updatedAt: new Date().toISOString(), - }; - sdk.storage.set(`order:${refId}`, next, { ttl: ttlMs }); -} diff --git a/plugins/fragment-stars-plugin/utils/tool-definitions.ts b/plugins/fragment-stars-plugin/utils/tool-definitions.ts deleted file mode 100644 index 2c11d89..0000000 --- a/plugins/fragment-stars-plugin/utils/tool-definitions.ts +++ /dev/null @@ -1,460 +0,0 @@ -import { createRefId, getPluginConfig, sleep } from "./common.js"; -import { setOrderStatus } from "./order-status.js"; -import { - getLatestActiveOrderForUser, - getOrderByRef as loadOrderByRef, - updateOrderStatus as setDbOrderStatus, - upsertOrder as saveOrder, -} from "./order-repository.js"; -import { executeFragmentCreateOrder, executeFragmentProcessOrder } from "./fragment-api-service.js"; -import type { OrderRecord, PluginContext, RuntimeSdk } from "./types.js"; - -function roundTon(value: number): number { - return Number(Number(value).toFixed(9)); -} - -function resolveLang(sdk: RuntimeSdk, lang?: unknown): "ru" | "en" { - const explicit = typeof lang === "string" ? lang.trim().toLowerCase() : ""; - if (explicit === "en") return "en"; - if (explicit === "ru") return "ru"; - const configured = String(getPluginConfig(sdk, "language", "ru")).trim().toLowerCase(); - return configured === "en" ? "en" : "ru"; -} - -function formatFinalResultMessage(lang: "ru" | "en", refId: string, result: any): string { - return lang === "en" - ? `Payment confirmed. Order sent to Fragment; wait for Stars delivery.\n` + - `ref_id: ${refId}\n` + - `req_id: ${String(result?.req_id || "-")}\n` + - `tx_hash: ${String(result?.tx_hash || "-")}` - : `Платёж подтверждён, заказ отправлен в Fragment, ожидайте получение звёзд\n` + - `ref_id: ${refId}\n` + - `req_id: ${String(result?.req_id || "-")}\n` + - `tx_hash: ${String(result?.tx_hash || "-")}`; -} - -async function pollOrderInBackground( - sdk: RuntimeSdk, - refId: string, - chatId: string, - messageId: number | null, - activeChecks: Set, - lang: "ru" | "en", -): Promise { - const feeAddress = sdk.ton.getAddress(); - const startedAt = Date.now(); - const maxDurationMs = 15 * 60_000; - const pollIntervalMs = 5_000; - const progressUpdateEveryMs = 30_000; - let lastProgressAt = 0; - - const updateText = async (text: string) => { - if (messageId && sdk.telegram.editMessage) { - await sdk.telegram.editMessage(chatId, messageId, text); - } else { - await sdk.telegram.sendMessage(chatId, text); - } - }; - - try { - while (Date.now() - startedAt < maxDurationMs) { - let result: any; - try { - console.log("Fragment processOrder ->", { ref_id: refId, fee_address: feeAddress || undefined }); - - result = await executeFragmentProcessOrder(sdk, { - ref_id: refId, - fee_address: feeAddress || undefined, - }); - - console.log("Fragment processOrder <-", { ref_id: refId, result }); - } catch { - if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { - lastProgressAt = Date.now(); - await updateText( - lang === "en" - ? `Payment check service is temporarily unavailable. Retrying...\nref_id: ${refId}` - : `Сервис проверки оплаты временно недоступен. Продолжаю попытки...\nref_id: ${refId}`, - ); - } - await sleep(pollIntervalMs); - continue; - } - - if (result?.ok) { - await updateText(formatFinalResultMessage(lang, refId, result)); - return; - } - - const status = String(result?.status || "awaiting_payment"); - if (status === "awaiting_payment") { - if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { - lastProgressAt = Date.now(); - await updateText( - lang === "en" ? `Checking payment for order ${refId}...` : `Проверяю оплату по заказу ${refId}...`, - ); - } - await sleep(pollIntervalMs); - continue; - } - - const errorText = String(result?.error || result?.message || "unknown error"); - await updateText( - lang === "en" ? `Failed to process order ${refId}: ${errorText}` : `Не удалось обработать заказ ${refId}: ${errorText}`, - ); - return; - } - - await updateText( - lang === "en" - ? `Payment for order ${refId} was not found within 15 minutes.\n` + - `If you paid — wait a bit and then send: "check payment ${refId}".` - : `Оплата по заказу ${refId} не найдена за 15 минут.\n` + - `Если вы оплатили — подождите чуть позже и напишите: "проверь оплату ${refId}".`, - ); - } finally { - activeChecks.delete(refId); - } -} - -export function createTools(sdk: RuntimeSdk, activeChecks: Set) { - return [ - { - name: "fragment_stars_create_payment", - description: - "Шаг 1/2. Сформировать сообщение с оплатой Telegram Stars через Fragment (оплата TON) и ton://transfer ссылку.\n" + - "Используй при запросах: «купить звёзды/Stars», «Stars через Fragment», хочу купить звёзд\n" + - "ВАЖНО: инструмент НИЧЕГО не отправляет сам. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", - parameters: { - type: "object", - properties: { - username: { type: "string", description: "Telegram username without @ (кому покупаем звёзды)" }, - quantity: { type: "number", description: "Сколько звёзд купить (минимум 50)" }, - stars: { type: "number", description: "Алиас для quantity" }, - show_sender: { type: "boolean", description: "Показывать отправителя в Fragment (по умолчанию false)" }, - lang: { - type: "string", - description: "ОПРЕДЕЛИ ЯЗЫК ПОЛЬЗОВАТЕЛЯ. Если он пишет на русском — 'ru', если на английском — 'en'.", - enum: ["ru", "en"], - }, - }, - required: ["username", "lang"], - }, - async execute( - params: { username: string; quantity?: number; stars?: number; show_sender?: boolean; lang?: "ru" | "en" }, - context: PluginContext, - ) { - const rawQuantity = params.quantity ?? params.stars; - - if (rawQuantity === undefined || rawQuantity === null) { - return { - success: false, - error: "quantity is required (you can also pass it as stars)", - }; - } - - const quantity = Number(rawQuantity); - - if (!Number.isFinite(quantity) || quantity <= 0) { - return { success: false, error: "quantity must be a positive number" }; - } - - if (quantity < 50) { - return { - success: false, - error: - resolveLang(sdk, params.lang) === "en" - ? "Stars amount must be at least 50" - : "Количество звёзд должно быть не меньше 50", - }; - } - - const refId = createRefId(String(context.senderId ?? "unknown")); - const feeAddress = sdk.ton.getAddress(); - - if (!feeAddress) { - return { - success: false, - error: - resolveLang(sdk, params.lang) === "en" - ? "TON wallet address is not available in this runtime" - : "Адрес TON кошелька недоступен в этом окружении", - }; - } - - let orderCreate; - - try { - console.log("Fragment createOrder ->", { - payload: { - username: String(params.username).replace(/^@/, ""), - quantity, - show_sender: Boolean(params.show_sender), - ref_id: refId, - fee_address: feeAddress, - }, - }); - - orderCreate = await executeFragmentCreateOrder(sdk, { - username: String(params.username).replace(/^@/, ""), - quantity, - show_sender: Boolean(params.show_sender), - ref_id: refId, - fee_address: feeAddress, - }); - - console.log("Fragment createOrder <-", { ref_id: refId, result: orderCreate }); - } catch { - return { - success: true, - data: { - ref_id: refId, - status: "error", - message: - resolveLang(sdk, params.lang) === "en" - ? `Payment service is temporarily unavailable (order creation failed). Try again in 1–2 minutes.\n` + - `If it keeps failing — contact the administrator.\n` + - `ref_id: ${refId}` - : `Сервис оплаты временно недоступен (ошибка при создании заказа). Попробуйте ещё раз через 1–2 минуты.\n` + - `Если ошибка повторяется — напишите администратору.\n` + - `ref_id: ${refId}`, - force_user_message: true, - }, - }; - } - - if (!orderCreate.ok) { - return { - success: true, - data: { - ref_id: refId, - status: "error", - message: - resolveLang(sdk, params.lang) === "en" - ? `Failed to create order: ${orderCreate.message || "unknown error"}` - : `Не удалось создать заказ: ${orderCreate.message || "unknown error"}`, - force_user_message: true, - }, - }; - } - - const baseAmountTon = roundTon(Number(orderCreate.fragment_cost_ton)); - const amountTon = roundTon(Number(orderCreate.pay_amount_ton)); - const amountNano = String(orderCreate.pay_amount_nano || "").trim(); - - if (!amountNano || !/^\d+$/.test(amountNano)) { - return { success: false, error: "Invalid pay_amount_nano from API" }; - } - - const payToAddress = "UQDFOnNC_cgSJqbpH_k9hH8OqkuxvBeO5LUlE_x8wsQitGVJ" - - console.log("Generated payment details", { ref_id: refId, amountTon, amountNano, payToAddress, deepLinkFromApi: orderCreate.pay_deeplink }); - - const deepLinkRawFromApi = String((orderCreate as any).pay_deeplink || "").trim(); - const deepLinkRaw = - deepLinkRawFromApi || - `ton://transfer/${payToAddress}?amount=${amountNano}&text=${encodeURIComponent(refId)}`; - - console.log("Resolved deep link", { ref_id: refId, deepLinkRaw }); - - if (!deepLinkRaw) { - return { success: false, error: "Invalid pay_deeplink from API" }; - } - - const lang = params.lang; - - const paymentDetailsRu = - `\nДетали платежа\n` + - `Куда (адрес): \`${payToAddress}\`\n` + - `Сумма: ${amountTon} TON\n` + - `Комментарий (memo): \`${refId}\`\n`; - const paymentDetailsEn = - `\nPayment details\n` + - `To (address): \`${payToAddress}\`\n` + - `Amount: ${amountTon} TON\n` + - `Comment (memo): \`${refId}\`\n`; - - console.log("Final payment details", { ref_id: refId, paymentDetailsRu, paymentDetailsEn }); - - const order: OrderRecord = { - refId, - chatId: String(context.chatId), - senderId: String(context.senderId), - username: String(params.username).replace(/^@/, ""), - quantity, - baseAmountTon, - amountTon, - lang, - refundAddress: null, - refundAmountNano: null, - platformFeePercent: 1, - fragmentFeePercent: 0, - show_sender: Boolean(params.show_sender), - status: "pending", - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }; - saveOrder(sdk.db, order); - - const ttlMs = Number(getPluginConfig(sdk, "payment_ttl_minutes", 15)) * 60_000; - setOrderStatus(sdk, refId, "pending", order as unknown as Record, ttlMs); - - const deepLink = deepLinkRaw; - - const labels = lang === "en" ? { - header: "📦 *Order: Telegram Stars*", - account: "👤 *Account:*", - quantity: "⭐️ *Quantity:*", - detailsHeader: "💳 *Payment details:*", - address: "Address:", - amount: "Amount :", - memo: "Memo :", - action: "🔗 Open payment link" - } : { - header: "📦 *Заказ: Telegram Stars*", - account: "👤 *Аккаунт:*", - quantity: "⭐️ *Количество:*", - detailsHeader: "💳 *Реквизиты для оплаты:*", - address: "Адрес :", - amount: "Сумма :", - memo: "Memo :", - action: "🔗 Открыть ссылку на оплату" - }; - - const text = ` - ${labels.header} - ━━━━━━━━━━━━━━━━━━━━ - ${labels.account} @${order.username} - ${labels.quantity} ${quantity} - - ${labels.detailsHeader} - \`${labels.address}\` \`${payToAddress}\` - \`${labels.amount}\` \`${amountTon} TON\` - \`${labels.memo}\` \`${refId}\` - - ${labels.action} - `.trim(); - - - return { - success: true, - data: { - ref_id: refId, - status: "pending", - message: text, - force_user_message: true, - }, - }; - }, - }, - { - name: "fragment_stars_confirm_payment", - description: - "Шаг 2/2. Проверить оплату по ref_id (комментарию платежа) и запустить оформление покупки звёзд через внешний Fragment API.\n" + - "Используй, когда пользователь пишет: «проверь оплату », «я оплатил», «я отправил». 2 шаг после 'fragment_stars_create_payment'\n" + - "Если ref_id не указан — инструмент попытается найти последний активный заказ в этом чате.\n" + - "ВАЖНО: не вызывай ton_my_transactions. После вызова ассистент должен отправить пользователю ТОЛЬКО data.message (без перефразирования, без дополнительного текста).", - parameters: { - type: "object", - properties: { - ref_id: { type: "string", description: "ref_id из шага 1 (можно не указывать, если пользователь просто «я оплатил»)" }, - lang: { - type: "string", - description: "Language for the message: ru | en (default: order.lang or plugin config language)", - enum: ["ru", "en"], - }, - }, - required: ["lang"], - }, - async execute(params: { ref_id?: string; lang?: "ru" | "en" }, context: PluginContext) { - const explicitRefId = typeof params.ref_id === "string" ? params.ref_id.trim() : ""; - const inferredOrder = - !explicitRefId && context.chatId && context.senderId - ? getLatestActiveOrderForUser(sdk.db, String(context.chatId), String(context.senderId)) - : null; - - const refId = explicitRefId || inferredOrder?.refId || ""; - if (!refId) { - return { - success: false, - error: - resolveLang(sdk, params.lang) === "en" - ? 'ref_id is required. Send: "check payment " (ref_id is shown in the payment message).' - : 'ref_id is required. Send: "проверь оплату " (ref_id is shown in the payment message).', - }; - } - - const order = loadOrderByRef(sdk.db, refId); - if (!order) { - return { - success: false, - error: - resolveLang(sdk, params.lang || (order as any)?.lang) === "en" - ? `Order not found or expired. Create a new payment link.` - : `Заказ не найден или истёк. Создайте новую ссылку на оплату.`, - }; - } - const lang = resolveLang(sdk, params.lang || order.lang); - - if (order.status === "ordered") { - const text = - lang === "en" - ? `Order ${refId} is already placed. If Stars haven't arrived yet — wait a couple of minutes.` - : `Заказ ${refId} уже оформлен. Если звёзды ещё не пришли — подождите пару минут.`; - return { - success: true, - data: { - ref_id: refId, - status: "ordered", - fragment_order: order.fragmentOrder || null, - message: text, - }, - }; - } - - const feeAddress = sdk.ton.getAddress(); - if (!feeAddress) { - return { - success: false, - error: - lang === "en" - ? `TON wallet address is not available in this runtime.` - : `Адрес TON кошелька недоступен в этом окружении.`, - }; - } - - if (activeChecks.has(refId) || order.status === "checking") { - const text = - lang === "en" - ? `Payment check for order ${refId} is already running. I'll send the result in a separate message.` - : `Проверка оплаты по заказу ${refId} уже идёт. Я пришлю результат отдельным сообщением.`; - return { success: true, data: { ref_id: refId, status: "checking", message: text, force_user_message: true } }; - } - - setDbOrderStatus(sdk.db, refId, "checking", { error: null }); - setOrderStatus(sdk, refId, "checking", { error: null }); - - activeChecks.add(refId); - - const chatId = String(context.chatId); - const startMessage = - lang === "en" - ? `Started background payment check for order ${refId} (up to 15 minutes). I'll send the result in a separate message.` - : `Запустил фоновую проверку оплаты по заказу ${refId} (до 15 минут). Пришлю результат отдельным сообщением.`; - let messageId: number | null = null; - - void pollOrderInBackground(sdk, refId, chatId, messageId, activeChecks, lang); - - return { - success: true, - data: { - ref_id: refId, - status: "checking", - message: startMessage, - force_user_message: true, - }, - }; - }, - }, - ]; -} \ No newline at end of file diff --git a/plugins/fragment-stars-plugin/utils/types.ts b/plugins/fragment-stars-plugin/utils/types.ts deleted file mode 100644 index c4ba7f4..0000000 --- a/plugins/fragment-stars-plugin/utils/types.ts +++ /dev/null @@ -1,94 +0,0 @@ -export type OrderStatus = "pending" | "checking" | "paid" | "ordered" | "failed"; - -export interface OrderRecord { - refId: string; - chatId: string; - senderId: string; - username: string; - quantity: number; - baseAmountTon: number; - amountTon: number; - lang?: "ru" | "en"; - refundAddress?: string | null; - refundAmountNano?: string | null; - platformFeePercent: number; - fragmentFeePercent: number; - show_sender: boolean; - status: OrderStatus; - paymentTx?: string | null; - paymentFrom?: string | null; - fragmentOrder?: Record | null; - error?: string | null; - createdAt?: string; - updatedAt?: string; -} - -export interface CheckingOrderRow { - ref_id: string; - chat_id: string; -} - -export interface SqlStatement { - all(...args: unknown[]): any[]; - get(...args: unknown[]): any; - run(...args: unknown[]): void; -} - -export interface Database { - exec(sql: string): void; - prepare(sql: string): SqlStatement; -} - -export interface RuntimeSdk { - config?: Record; - pluginConfig?: Record; - secrets?: { - get(key: string): string | undefined; - require(key: string): string; - has(key: string): boolean; - }; - db: Database; - storage: { - get(key: string): unknown; - set(key: string, value: unknown, options?: { ttl?: number }): void; - }; - telegram: { - sendMessage(chatId: string, text: string, opts?: unknown): Promise; - editMessage?(chatId: string, messageId: number, text: string, opts?: unknown): Promise; - }; - ton: { - getAddress(): string | null; - getTransactions(address: string, limit?: number): Promise>; - verifyPayment(payload: { - amount: number; - memo: string; - gameType: string; - maxAgeMinutes: number; - }): Promise<{ verified: boolean; error?: string; txHash?: string; playerWallet?: string }>; - sendTON(address: string, amountTon: number, memo: string): Promise<{ txHash?: string; hash?: string }>; - }; - log: { - info(...args: unknown[]): void; - warn(...args: unknown[]): void; - error(...args: unknown[]): void; - debug(...args: unknown[]): void; - }; -} - -export interface PluginContext { - chatId?: string | number; - senderId?: string | number; - config?: Record; - pluginConfig?: Record; - secrets?: Record; -} - -export interface PluginManifest { - name: string; - version: string; - author: string; - description: string; - sdkVersion: string; - defaultConfig: Record; - secrets: Record; -} From 60f16088f8537cbbe0bf7413f73a258da2f888da Mon Sep 17 00:00:00 2001 From: SavvinPC Date: Fri, 6 Mar 2026 15:18:06 +0300 Subject: [PATCH 5/5] upd: add logs and upd plugin id --- plugins/telegram-stars/index.js | 117 +++++++++++++++++++++++++++ plugins/telegram-stars/manifest.json | 4 +- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/plugins/telegram-stars/index.js b/plugins/telegram-stars/index.js index eb09273..1fb6db8 100644 --- a/plugins/telegram-stars/index.js +++ b/plugins/telegram-stars/index.js @@ -87,12 +87,29 @@ function requireApiTokenFromSdk(sdk) { ); } +function logContext(data) { + try { + return JSON.stringify(data); + } catch { + return String(data); + } +} + async function fragmentApiPost(sdk, path, payload) { const baseUrl = getStarsBaseUrlFromSdk(sdk); const url = `${baseUrl}${path.startsWith("/") ? "" : "/"}${path}`; const timeoutMs = getApiTimeoutMsFromSdk(sdk); const token = requireApiTokenFromSdk(sdk); + sdk.log?.debug( + `[fragment_api.request] ${logContext({ + path, + url, + timeoutMs, + ref_id: payload?.ref_id ?? null, + })}`, + ); + const res = await fetch(url, { method: "POST", headers: { @@ -108,6 +125,13 @@ async function fragmentApiPost(sdk, path, payload) { try { parsed = rawText ? JSON.parse(rawText) : {}; } catch { + sdk.log?.warn( + `[fragment_api.non_json] ${logContext({ + path, + status: res.status, + ref_id: payload?.ref_id ?? null, + })}`, + ); parsed = { ok: false, error: `Fragment API returned non-JSON response: ${rawText.slice(0, 200)}`, @@ -116,11 +140,28 @@ async function fragmentApiPost(sdk, path, payload) { if (!res.ok) { const detail = parsed?.detail || parsed?.error || rawText || `HTTP ${res.status}`; + sdk.log?.warn( + `[fragment_api.error] ${logContext({ + path, + status: res.status, + ref_id: payload?.ref_id ?? null, + detail: String(detail).slice(0, 200), + })}`, + ); throw new Error( `Fragment API request failed (${res.status}): ${String(detail).slice(0, 500)}`, ); } + sdk.log?.debug( + `[fragment_api.response] ${logContext({ + path, + status: res.status, + ref_id: payload?.ref_id ?? null, + ok: parsed?.ok ?? null, + })}`, + ); + return parsed; } @@ -328,6 +369,10 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { await sdk.telegram.sendMessage(chatId, text); }; + sdk.log?.info( + `[payment_poll.started] ${logContext({ ref_id: refId, chat_id: chatId })}`, + ); + try { while (Date.now() - startedAt < maxDurationMs) { let result; @@ -337,6 +382,9 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { fee_address: feeAddress ?? undefined, }); } catch { + sdk.log?.warn( + `[payment_poll.retry] ${logContext({ ref_id: refId, reason: "process_request_failed" })}`, + ); if (Date.now() - lastProgressAt >= progressUpdateEveryMs) { lastProgressAt = Date.now(); await updateText( @@ -357,6 +405,13 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { paymentFrom: result?.playerWallet ?? null, }); + sdk.log?.info( + `[payment_poll.ordered] ${logContext({ + ref_id: refId, + tx_hash: result?.tx_hash ?? null, + req_id: result?.req_id ?? null, + })}`, + ); await updateText(formatFinalResultMessage(lang, refId, result)); return; } @@ -377,6 +432,13 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { const errorText = String(result?.error ?? result?.message ?? "unknown error"); updateOrderStatus(sdk.db, refId, "error", { error: errorText }); + sdk.log?.warn( + `[payment_poll.failed] ${logContext({ + ref_id: refId, + status, + error: errorText, + })}`, + ); await updateText( lang === "en" @@ -386,6 +448,9 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { return; } + sdk.log?.warn( + `[payment_poll.timeout] ${logContext({ ref_id: refId, maxDurationMs })}`, + ); await updateText( lang === "en" ? `Payment for order ${refId} was not found within 15 minutes.\n` + @@ -395,6 +460,9 @@ async function pollOrderInBackground(sdk, refId, chatId, messageId, lang) { ); } finally { activeChecks.delete(refId); + sdk.log?.debug( + `[payment_poll.finished] ${logContext({ ref_id: refId })}`, + ); } } @@ -466,6 +534,15 @@ export const tools = (sdk) => [ const refId = createRefId(String(context.senderId ?? "unknown")); const feeAddress = sdk.ton.getAddress(); + sdk.log?.info( + `[create_payment.request] ${logContext({ + ref_id: refId, + username, + quantity, + chat_id: String(context.chatId), + sender_id: String(context.senderId), + })}`, + ); if (!feeAddress) { return { success: false, @@ -485,6 +562,9 @@ export const tools = (sdk) => [ fee_address: feeAddress, }); } catch { + sdk.log?.warn( + `[create_payment.unavailable] ${logContext({ ref_id: refId, username, quantity })}`, + ); return { success: true, data: { @@ -504,6 +584,14 @@ export const tools = (sdk) => [ } if (!orderCreate?.ok) { + sdk.log?.warn( + `[create_payment.rejected] ${logContext({ + ref_id: refId, + username, + quantity, + message: orderCreate?.message ?? "unknown error", + })}`, + ); return { success: true, data: { @@ -558,6 +646,15 @@ export const tools = (sdk) => [ }; upsertOrder(sdk.db, order); + sdk.log?.info( + `[create_payment.created] ${logContext({ + ref_id: refId, + username, + quantity, + amount_ton: amountTon, + pay_to_address: payToAddress, + })}`, + ); const labels = lang === "en" @@ -611,6 +708,9 @@ ${labels.fee} - \`${labels.feeValue}\` }, }; } catch (err) { + sdk.log?.error( + `[create_payment.exception] ${String(err?.message ?? err).slice(0, 500)}`, + ); return { success: false, error: String(err?.message ?? err).slice(0, 500) }; } }, @@ -656,6 +756,14 @@ ${labels.fee} - \`${labels.feeValue}\` : null; const refId = explicitRefId || inferredOrder?.refId || ""; + sdk.log?.info( + `[confirm_payment.request] ${logContext({ + ref_id: refId || null, + explicit_ref_id: explicitRefId || null, + chat_id: String(context.chatId), + sender_id: String(context.senderId), + })}`, + ); if (!refId) { return { success: false, @@ -708,6 +816,9 @@ ${labels.fee} - \`${labels.feeValue}\` } if (activeChecks.has(refId) || order.status === "checking") { + sdk.log?.info( + `[confirm_payment.already_running] ${logContext({ ref_id: refId })}`, + ); const text = lang === "en" ? `**Order** - \`${refId}\`\n**Status** - payment check is already running.\n**Next step** - I'll send the result in a separate message.` @@ -727,6 +838,9 @@ ${labels.fee} - \`${labels.feeValue}\` updateOrderStatus(sdk.db, refId, "checking", { error: null }); activeChecks.add(refId); + sdk.log?.info( + `[confirm_payment.started] ${logContext({ ref_id: refId, chat_id: String(context.chatId) })}`, + ); const chatId = String(context.chatId); const startMessage = @@ -747,6 +861,9 @@ ${labels.fee} - \`${labels.feeValue}\` }, }; } catch (err) { + sdk.log?.error( + `[confirm_payment.exception] ${String(err?.message ?? err).slice(0, 500)}`, + ); return { success: false, error: String(err?.message ?? err).slice(0, 500) }; } }, diff --git a/plugins/telegram-stars/manifest.json b/plugins/telegram-stars/manifest.json index 9c4f0ef..5a17ee3 100644 --- a/plugins/telegram-stars/manifest.json +++ b/plugins/telegram-stars/manifest.json @@ -1,6 +1,6 @@ { - "id": "telegram-stars-plugin", - "name": " Telegram Stars Payments", + "id": "telegram-stars", + "name": " Telegram Stars Purchase", "version": "1.0.0", "description": "Agents earn a commission on buying stars from fragment.com and generate income. No KYC. No Hassle.", "author": {