@adstelo/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.cjs +228 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# @adstelo/sdk
|
|
2
|
+
|
|
3
|
+
Adstelo JS SDK for Telegram bots. Supports Telegraf, grammY, and manual event reporting.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @adstelo/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start (One-liner)
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { Bot } from "grammy";
|
|
15
|
+
import { adstelo } from "@adstelo/sdk";
|
|
16
|
+
|
|
17
|
+
const bot = new Bot(process.env.BOT_TOKEN);
|
|
18
|
+
|
|
19
|
+
bot.use(
|
|
20
|
+
adstelo.grammy({
|
|
21
|
+
botSecret: process.env.ADSTELO_BOT_SECRET,
|
|
22
|
+
apiKey: process.env.ADSTELO_API_KEY,
|
|
23
|
+
cooldownSeconds: 2
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Optional Explicit Init
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { adstelo, init } from "@adstelo/sdk";
|
|
32
|
+
|
|
33
|
+
init({ botSecret: "bs-...", apiKey: "sk-..." });
|
|
34
|
+
|
|
35
|
+
bot.use(adstelo.grammy());
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Telegraf
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import { Telegraf } from "telegraf";
|
|
42
|
+
import { adstelo } from "@adstelo/sdk";
|
|
43
|
+
|
|
44
|
+
const bot = new Telegraf(process.env.BOT_TOKEN);
|
|
45
|
+
|
|
46
|
+
bot.use(
|
|
47
|
+
adstelo.telegraf({
|
|
48
|
+
botSecret: process.env.ADSTELO_BOT_SECRET,
|
|
49
|
+
apiKey: process.env.ADSTELO_API_KEY
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Manual Hook (no middleware)
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
import { init, reportEvent } from "@adstelo/sdk";
|
|
58
|
+
|
|
59
|
+
init({ botSecret: "bs-...", apiKey: "sk-..." });
|
|
60
|
+
|
|
61
|
+
// In your update handler:
|
|
62
|
+
reportEvent({
|
|
63
|
+
chatId: msg.chat.id,
|
|
64
|
+
userId: msg.from.id,
|
|
65
|
+
event: "message",
|
|
66
|
+
isStart: msg.text === "/start"
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## API
|
|
71
|
+
|
|
72
|
+
- `init({ botSecret, apiKey, cooldownSeconds })`
|
|
73
|
+
- `validateInitCredentials()`
|
|
74
|
+
- `reportEvent({ chatId, userId, event, isStart })`
|
|
75
|
+
- `adstelo.telegraf(options?)`
|
|
76
|
+
- `adstelo.grammy(options?)`
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
_internal: () => _internal,
|
|
34
|
+
adstelo: () => adstelo,
|
|
35
|
+
init: () => init,
|
|
36
|
+
reportEvent: () => reportEvent,
|
|
37
|
+
validateInitCredentials: () => validateInitCredentials
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
41
|
+
var DEFAULT_BASE_URL = "https://sdk.adstelo.com";
|
|
42
|
+
var config = {
|
|
43
|
+
apiKey: null,
|
|
44
|
+
botSecret: null,
|
|
45
|
+
endpoint: `${DEFAULT_BASE_URL}/event`,
|
|
46
|
+
validateEndpoint: `${DEFAULT_BASE_URL}/event/validate`,
|
|
47
|
+
cooldownSeconds: 2
|
|
48
|
+
};
|
|
49
|
+
var initialized = false;
|
|
50
|
+
var validationPromise = null;
|
|
51
|
+
var cooldownMap = /* @__PURE__ */ new Map();
|
|
52
|
+
function buildHeaders(payloadBytes) {
|
|
53
|
+
const headers = {
|
|
54
|
+
"Content-Type": "application/json",
|
|
55
|
+
"ngrok-skip-browser-warning": "true"
|
|
56
|
+
};
|
|
57
|
+
if (config.apiKey) {
|
|
58
|
+
headers["X-Api-Key"] = config.apiKey;
|
|
59
|
+
const signature = import_node_crypto.default.createHmac("sha256", config.apiKey).update(payloadBytes).digest("hex");
|
|
60
|
+
headers["X-Signature"] = signature;
|
|
61
|
+
}
|
|
62
|
+
return headers;
|
|
63
|
+
}
|
|
64
|
+
function nowSeconds() {
|
|
65
|
+
return Math.floor(Date.now() / 1e3);
|
|
66
|
+
}
|
|
67
|
+
function cooldownKey(userId, event) {
|
|
68
|
+
return `${userId}:${event}`;
|
|
69
|
+
}
|
|
70
|
+
function allowEvent(userId, event) {
|
|
71
|
+
const key = cooldownKey(userId, event);
|
|
72
|
+
const current = nowSeconds();
|
|
73
|
+
const last = cooldownMap.get(key) ?? 0;
|
|
74
|
+
if (current - last < config.cooldownSeconds) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
cooldownMap.set(key, current);
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
function init(options) {
|
|
81
|
+
if (!options || !options.botSecret || !options.apiKey) {
|
|
82
|
+
throw new Error("botSecret and apiKey are required");
|
|
83
|
+
}
|
|
84
|
+
config.botSecret = options.botSecret;
|
|
85
|
+
config.apiKey = options.apiKey;
|
|
86
|
+
config.cooldownSeconds = options.cooldownSeconds ?? 2;
|
|
87
|
+
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
88
|
+
config.endpoint = `${baseUrl.replace(/\/$/, "")}/event`;
|
|
89
|
+
config.validateEndpoint = `${baseUrl.replace(/\/$/, "")}/event/validate`;
|
|
90
|
+
initialized = true;
|
|
91
|
+
}
|
|
92
|
+
function ensureInit(options) {
|
|
93
|
+
if (initialized) return;
|
|
94
|
+
if (!options?.botSecret || !options?.apiKey) {
|
|
95
|
+
throw new Error("Missing botSecret or apiKey. Pass them to adstelo.grammy/adstelo.telegraf or call init().");
|
|
96
|
+
}
|
|
97
|
+
init(options);
|
|
98
|
+
if (!validationPromise) {
|
|
99
|
+
validationPromise = validateInitCredentials().then(({ ok, detail }) => {
|
|
100
|
+
if (!ok) {
|
|
101
|
+
console.error("[adstelo] init validation failed:", detail);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
console.log("[adstelo] init validation ok");
|
|
105
|
+
}).catch((err) => {
|
|
106
|
+
console.error("[adstelo] init validation failed:", err?.message || err);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function validateInitCredentials() {
|
|
112
|
+
if (!config.botSecret) {
|
|
113
|
+
return { ok: false, detail: "Missing botSecret" };
|
|
114
|
+
}
|
|
115
|
+
if (!config.apiKey) {
|
|
116
|
+
return { ok: false, detail: "Missing apiKey" };
|
|
117
|
+
}
|
|
118
|
+
const payload = { secret: config.botSecret };
|
|
119
|
+
const payloadBytes = JSON.stringify(payload);
|
|
120
|
+
const res = await fetch(config.validateEndpoint, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: buildHeaders(payloadBytes),
|
|
123
|
+
body: payloadBytes
|
|
124
|
+
});
|
|
125
|
+
if (res.ok) {
|
|
126
|
+
return { ok: true, detail: "ok" };
|
|
127
|
+
}
|
|
128
|
+
let detail = `HTTP ${res.status}`;
|
|
129
|
+
try {
|
|
130
|
+
const data = await res.json();
|
|
131
|
+
if (data && typeof data.detail === "string") {
|
|
132
|
+
detail = data.detail;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
return { ok: false, detail };
|
|
137
|
+
}
|
|
138
|
+
async function reportEvent(input) {
|
|
139
|
+
if (!config.botSecret || !config.apiKey) {
|
|
140
|
+
throw new Error("SDK not initialized. Call init() first.");
|
|
141
|
+
}
|
|
142
|
+
if (!input?.userId || !input?.event) {
|
|
143
|
+
return { ok: false, detail: "Missing userId or event" };
|
|
144
|
+
}
|
|
145
|
+
if (!allowEvent(input.userId, input.event)) {
|
|
146
|
+
return { ok: true, detail: "cooldown" };
|
|
147
|
+
}
|
|
148
|
+
const payload = {
|
|
149
|
+
chat_id: input.chatId,
|
|
150
|
+
user_id: input.userId,
|
|
151
|
+
event: input.event,
|
|
152
|
+
ts: Date.now() / 1e3,
|
|
153
|
+
is_start: Boolean(input.isStart),
|
|
154
|
+
secret: config.botSecret
|
|
155
|
+
};
|
|
156
|
+
const payloadBytes = JSON.stringify(payload);
|
|
157
|
+
const res = await fetch(config.endpoint, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: buildHeaders(payloadBytes),
|
|
160
|
+
body: payloadBytes
|
|
161
|
+
});
|
|
162
|
+
return { ok: res.ok, status: res.status };
|
|
163
|
+
}
|
|
164
|
+
function inferEventFromMessage(message) {
|
|
165
|
+
if (!message) return "message";
|
|
166
|
+
if (message.photo?.length) return "photo";
|
|
167
|
+
if (message.video) return "video";
|
|
168
|
+
if (message.sticker) return "sticker";
|
|
169
|
+
if (message.animation) return "media";
|
|
170
|
+
return "message";
|
|
171
|
+
}
|
|
172
|
+
function adsteloTelegrafMiddleware(options) {
|
|
173
|
+
ensureInit(options);
|
|
174
|
+
return async (ctx, next) => {
|
|
175
|
+
try {
|
|
176
|
+
const event = ctx?.callbackQuery ? "button" : ctx?.inlineQuery ? "inline_query" : inferEventFromMessage(ctx?.message);
|
|
177
|
+
const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;
|
|
178
|
+
const userId = ctx?.from?.id;
|
|
179
|
+
const isStart = typeof ctx?.message?.text === "string" && ctx.message.text.startsWith("/start");
|
|
180
|
+
if (chatId && userId) {
|
|
181
|
+
await reportEvent({ chatId, userId, event, isStart });
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
if (next) {
|
|
186
|
+
await next();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function adsteloGrammyMiddleware(options) {
|
|
191
|
+
ensureInit(options);
|
|
192
|
+
return async (ctx, next) => {
|
|
193
|
+
try {
|
|
194
|
+
const event = ctx?.callbackQuery ? "button" : ctx?.inlineQuery ? "inline_query" : inferEventFromMessage(ctx?.message);
|
|
195
|
+
const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;
|
|
196
|
+
const userId = ctx?.from?.id;
|
|
197
|
+
const isStart = typeof ctx?.message?.text === "string" && ctx.message.text.startsWith("/start");
|
|
198
|
+
if (chatId && userId) {
|
|
199
|
+
await reportEvent({ chatId, userId, event, isStart });
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
if (next) {
|
|
204
|
+
await next();
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
var adstelo = {
|
|
209
|
+
init,
|
|
210
|
+
reportEvent,
|
|
211
|
+
validateInitCredentials,
|
|
212
|
+
telegraf: adsteloTelegrafMiddleware,
|
|
213
|
+
grammy: adsteloGrammyMiddleware
|
|
214
|
+
};
|
|
215
|
+
var _internal = {
|
|
216
|
+
buildHeaders,
|
|
217
|
+
allowEvent,
|
|
218
|
+
inferEventFromMessage
|
|
219
|
+
};
|
|
220
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
221
|
+
0 && (module.exports = {
|
|
222
|
+
_internal,
|
|
223
|
+
adstelo,
|
|
224
|
+
init,
|
|
225
|
+
reportEvent,
|
|
226
|
+
validateInitCredentials
|
|
227
|
+
});
|
|
228
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nexport type InitOptions = {\n botSecret: string;\n apiKey: string;\n cooldownSeconds?: number;\n baseUrl?: string;\n};\n\nexport type ReportEventInput = {\n chatId: number | string;\n userId: number | string;\n event: string;\n isStart?: boolean;\n};\n\ntype Config = {\n apiKey: string | null;\n botSecret: string | null;\n endpoint: string;\n validateEndpoint: string;\n cooldownSeconds: number;\n};\n\nconst DEFAULT_BASE_URL = \"https://sdk.adstelo.com\";\n\nconst config: Config = {\n apiKey: null,\n botSecret: null,\n endpoint: `${DEFAULT_BASE_URL}/event`,\n validateEndpoint: `${DEFAULT_BASE_URL}/event/validate`,\n cooldownSeconds: 2\n};\n\nlet initialized = false;\nlet validationPromise: Promise<void> | null = null;\nconst cooldownMap = new Map<string, number>();\n\nfunction buildHeaders(payloadBytes: string) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"ngrok-skip-browser-warning\": \"true\"\n };\n\n if (config.apiKey) {\n headers[\"X-Api-Key\"] = config.apiKey;\n const signature = crypto\n .createHmac(\"sha256\", config.apiKey)\n .update(payloadBytes)\n .digest(\"hex\");\n headers[\"X-Signature\"] = signature;\n }\n\n return headers;\n}\n\nfunction nowSeconds() {\n return Math.floor(Date.now() / 1000);\n}\n\nfunction cooldownKey(userId: string | number, event: string) {\n return `${userId}:${event}`;\n}\n\nfunction allowEvent(userId: string | number, event: string) {\n const key = cooldownKey(userId, event);\n const current = nowSeconds();\n const last = cooldownMap.get(key) ?? 0;\n if (current - last < config.cooldownSeconds) {\n return false;\n }\n cooldownMap.set(key, current);\n return true;\n}\n\nexport function init(options: InitOptions) {\n if (!options || !options.botSecret || !options.apiKey) {\n throw new Error(\"botSecret and apiKey are required\");\n }\n\n config.botSecret = options.botSecret;\n config.apiKey = options.apiKey;\n config.cooldownSeconds = options.cooldownSeconds ?? 2;\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n config.endpoint = `${baseUrl.replace(/\\/$/, \"\")}/event`;\n config.validateEndpoint = `${baseUrl.replace(/\\/$/, \"\")}/event/validate`;\n initialized = true;\n}\n\nfunction ensureInit(options?: InitOptions) {\n if (initialized) return;\n if (!options?.botSecret || !options?.apiKey) {\n throw new Error(\"Missing botSecret or apiKey. Pass them to adstelo.grammy/adstelo.telegraf or call init().\");\n }\n init(options);\n if (!validationPromise) {\n validationPromise = validateInitCredentials()\n .then(({ ok, detail }) => {\n if (!ok) {\n console.error(\"[adstelo] init validation failed:\", detail);\n process.exit(1);\n }\n console.log(\"[adstelo] init validation ok\");\n })\n .catch((err) => {\n console.error(\"[adstelo] init validation failed:\", err?.message || err);\n process.exit(1);\n });\n }\n}\n\nexport async function validateInitCredentials() {\n if (!config.botSecret) {\n return { ok: false, detail: \"Missing botSecret\" };\n }\n if (!config.apiKey) {\n return { ok: false, detail: \"Missing apiKey\" };\n }\n\n const payload = { secret: config.botSecret };\n const payloadBytes = JSON.stringify(payload);\n const res = await fetch(config.validateEndpoint, {\n method: \"POST\",\n headers: buildHeaders(payloadBytes),\n body: payloadBytes\n });\n\n if (res.ok) {\n return { ok: true, detail: \"ok\" };\n }\n\n let detail = `HTTP ${res.status}`;\n try {\n const data = await res.json();\n if (data && typeof data.detail === \"string\") {\n detail = data.detail;\n }\n } catch {\n // ignore\n }\n return { ok: false, detail };\n}\n\nexport async function reportEvent(input: ReportEventInput) {\n if (!config.botSecret || !config.apiKey) {\n throw new Error(\"SDK not initialized. Call init() first.\");\n }\n if (!input?.userId || !input?.event) {\n return { ok: false, detail: \"Missing userId or event\" };\n }\n\n if (!allowEvent(input.userId, input.event)) {\n return { ok: true, detail: \"cooldown\" };\n }\n\n const payload = {\n chat_id: input.chatId,\n user_id: input.userId,\n event: input.event,\n ts: Date.now() / 1000,\n is_start: Boolean(input.isStart),\n secret: config.botSecret\n };\n\n const payloadBytes = JSON.stringify(payload);\n const res = await fetch(config.endpoint, {\n method: \"POST\",\n headers: buildHeaders(payloadBytes),\n body: payloadBytes\n });\n\n return { ok: res.ok, status: res.status };\n}\n\nfunction inferEventFromMessage(message: any) {\n if (!message) return \"message\";\n if (message.photo?.length) return \"photo\";\n if (message.video) return \"video\";\n if (message.sticker) return \"sticker\";\n if (message.animation) return \"media\";\n return \"message\";\n}\n\nfunction adsteloTelegrafMiddleware(options?: InitOptions) {\n ensureInit(options);\n return async (ctx: any, next: () => Promise<void>) => {\n try {\n const event = ctx?.callbackQuery\n ? \"button\"\n : ctx?.inlineQuery\n ? \"inline_query\"\n : inferEventFromMessage(ctx?.message);\n const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;\n const userId = ctx?.from?.id;\n const isStart = typeof ctx?.message?.text === \"string\" && ctx.message.text.startsWith(\"/start\");\n\n if (chatId && userId) {\n await reportEvent({ chatId, userId, event, isStart });\n }\n } catch {\n // swallow\n }\n\n if (next) {\n await next();\n }\n };\n}\n\nfunction adsteloGrammyMiddleware(options?: InitOptions) {\n ensureInit(options);\n return async (ctx: any, next: () => Promise<void>) => {\n try {\n const event = ctx?.callbackQuery\n ? \"button\"\n : ctx?.inlineQuery\n ? \"inline_query\"\n : inferEventFromMessage(ctx?.message);\n const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;\n const userId = ctx?.from?.id;\n const isStart = typeof ctx?.message?.text === \"string\" && ctx.message.text.startsWith(\"/start\");\n\n if (chatId && userId) {\n await reportEvent({ chatId, userId, event, isStart });\n }\n } catch {\n // swallow\n }\n\n if (next) {\n await next();\n }\n };\n}\n\nexport const adstelo = {\n init,\n reportEvent,\n validateInitCredentials,\n telegraf: adsteloTelegrafMiddleware,\n grammy: adsteloGrammyMiddleware\n};\n\nexport const _internal = {\n buildHeaders,\n allowEvent,\n inferEventFromMessage\n};\n"],"mappings":"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","names":["crypto"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type InitOptions = {
|
|
2
|
+
botSecret: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
cooldownSeconds?: number;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
};
|
|
7
|
+
type ReportEventInput = {
|
|
8
|
+
chatId: number | string;
|
|
9
|
+
userId: number | string;
|
|
10
|
+
event: string;
|
|
11
|
+
isStart?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare function buildHeaders(payloadBytes: string): Record<string, string>;
|
|
14
|
+
declare function allowEvent(userId: string | number, event: string): boolean;
|
|
15
|
+
declare function init(options: InitOptions): void;
|
|
16
|
+
declare function validateInitCredentials(): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
detail: string;
|
|
19
|
+
}>;
|
|
20
|
+
declare function reportEvent(input: ReportEventInput): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
detail: string;
|
|
23
|
+
status?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: number;
|
|
27
|
+
detail?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
declare function inferEventFromMessage(message: any): "message" | "photo" | "video" | "sticker" | "media";
|
|
30
|
+
declare function adsteloTelegrafMiddleware(options?: InitOptions): (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
31
|
+
declare function adsteloGrammyMiddleware(options?: InitOptions): (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
32
|
+
declare const adstelo: {
|
|
33
|
+
init: typeof init;
|
|
34
|
+
reportEvent: typeof reportEvent;
|
|
35
|
+
validateInitCredentials: typeof validateInitCredentials;
|
|
36
|
+
telegraf: typeof adsteloTelegrafMiddleware;
|
|
37
|
+
grammy: typeof adsteloGrammyMiddleware;
|
|
38
|
+
};
|
|
39
|
+
declare const _internal: {
|
|
40
|
+
buildHeaders: typeof buildHeaders;
|
|
41
|
+
allowEvent: typeof allowEvent;
|
|
42
|
+
inferEventFromMessage: typeof inferEventFromMessage;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { type InitOptions, type ReportEventInput, _internal, adstelo, init, reportEvent, validateInitCredentials };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
type InitOptions = {
|
|
2
|
+
botSecret: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
cooldownSeconds?: number;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
};
|
|
7
|
+
type ReportEventInput = {
|
|
8
|
+
chatId: number | string;
|
|
9
|
+
userId: number | string;
|
|
10
|
+
event: string;
|
|
11
|
+
isStart?: boolean;
|
|
12
|
+
};
|
|
13
|
+
declare function buildHeaders(payloadBytes: string): Record<string, string>;
|
|
14
|
+
declare function allowEvent(userId: string | number, event: string): boolean;
|
|
15
|
+
declare function init(options: InitOptions): void;
|
|
16
|
+
declare function validateInitCredentials(): Promise<{
|
|
17
|
+
ok: boolean;
|
|
18
|
+
detail: string;
|
|
19
|
+
}>;
|
|
20
|
+
declare function reportEvent(input: ReportEventInput): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
detail: string;
|
|
23
|
+
status?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
status: number;
|
|
27
|
+
detail?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
declare function inferEventFromMessage(message: any): "message" | "photo" | "video" | "sticker" | "media";
|
|
30
|
+
declare function adsteloTelegrafMiddleware(options?: InitOptions): (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
31
|
+
declare function adsteloGrammyMiddleware(options?: InitOptions): (ctx: any, next: () => Promise<void>) => Promise<void>;
|
|
32
|
+
declare const adstelo: {
|
|
33
|
+
init: typeof init;
|
|
34
|
+
reportEvent: typeof reportEvent;
|
|
35
|
+
validateInitCredentials: typeof validateInitCredentials;
|
|
36
|
+
telegraf: typeof adsteloTelegrafMiddleware;
|
|
37
|
+
grammy: typeof adsteloGrammyMiddleware;
|
|
38
|
+
};
|
|
39
|
+
declare const _internal: {
|
|
40
|
+
buildHeaders: typeof buildHeaders;
|
|
41
|
+
allowEvent: typeof allowEvent;
|
|
42
|
+
inferEventFromMessage: typeof inferEventFromMessage;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export { type InitOptions, type ReportEventInput, _internal, adstelo, init, reportEvent, validateInitCredentials };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
var DEFAULT_BASE_URL = "https://sdk.adstelo.com";
|
|
4
|
+
var config = {
|
|
5
|
+
apiKey: null,
|
|
6
|
+
botSecret: null,
|
|
7
|
+
endpoint: `${DEFAULT_BASE_URL}/event`,
|
|
8
|
+
validateEndpoint: `${DEFAULT_BASE_URL}/event/validate`,
|
|
9
|
+
cooldownSeconds: 2
|
|
10
|
+
};
|
|
11
|
+
var initialized = false;
|
|
12
|
+
var validationPromise = null;
|
|
13
|
+
var cooldownMap = /* @__PURE__ */ new Map();
|
|
14
|
+
function buildHeaders(payloadBytes) {
|
|
15
|
+
const headers = {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
"ngrok-skip-browser-warning": "true"
|
|
18
|
+
};
|
|
19
|
+
if (config.apiKey) {
|
|
20
|
+
headers["X-Api-Key"] = config.apiKey;
|
|
21
|
+
const signature = crypto.createHmac("sha256", config.apiKey).update(payloadBytes).digest("hex");
|
|
22
|
+
headers["X-Signature"] = signature;
|
|
23
|
+
}
|
|
24
|
+
return headers;
|
|
25
|
+
}
|
|
26
|
+
function nowSeconds() {
|
|
27
|
+
return Math.floor(Date.now() / 1e3);
|
|
28
|
+
}
|
|
29
|
+
function cooldownKey(userId, event) {
|
|
30
|
+
return `${userId}:${event}`;
|
|
31
|
+
}
|
|
32
|
+
function allowEvent(userId, event) {
|
|
33
|
+
const key = cooldownKey(userId, event);
|
|
34
|
+
const current = nowSeconds();
|
|
35
|
+
const last = cooldownMap.get(key) ?? 0;
|
|
36
|
+
if (current - last < config.cooldownSeconds) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
cooldownMap.set(key, current);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
function init(options) {
|
|
43
|
+
if (!options || !options.botSecret || !options.apiKey) {
|
|
44
|
+
throw new Error("botSecret and apiKey are required");
|
|
45
|
+
}
|
|
46
|
+
config.botSecret = options.botSecret;
|
|
47
|
+
config.apiKey = options.apiKey;
|
|
48
|
+
config.cooldownSeconds = options.cooldownSeconds ?? 2;
|
|
49
|
+
const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
|
|
50
|
+
config.endpoint = `${baseUrl.replace(/\/$/, "")}/event`;
|
|
51
|
+
config.validateEndpoint = `${baseUrl.replace(/\/$/, "")}/event/validate`;
|
|
52
|
+
initialized = true;
|
|
53
|
+
}
|
|
54
|
+
function ensureInit(options) {
|
|
55
|
+
if (initialized) return;
|
|
56
|
+
if (!options?.botSecret || !options?.apiKey) {
|
|
57
|
+
throw new Error("Missing botSecret or apiKey. Pass them to adstelo.grammy/adstelo.telegraf or call init().");
|
|
58
|
+
}
|
|
59
|
+
init(options);
|
|
60
|
+
if (!validationPromise) {
|
|
61
|
+
validationPromise = validateInitCredentials().then(({ ok, detail }) => {
|
|
62
|
+
if (!ok) {
|
|
63
|
+
console.error("[adstelo] init validation failed:", detail);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
console.log("[adstelo] init validation ok");
|
|
67
|
+
}).catch((err) => {
|
|
68
|
+
console.error("[adstelo] init validation failed:", err?.message || err);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function validateInitCredentials() {
|
|
74
|
+
if (!config.botSecret) {
|
|
75
|
+
return { ok: false, detail: "Missing botSecret" };
|
|
76
|
+
}
|
|
77
|
+
if (!config.apiKey) {
|
|
78
|
+
return { ok: false, detail: "Missing apiKey" };
|
|
79
|
+
}
|
|
80
|
+
const payload = { secret: config.botSecret };
|
|
81
|
+
const payloadBytes = JSON.stringify(payload);
|
|
82
|
+
const res = await fetch(config.validateEndpoint, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: buildHeaders(payloadBytes),
|
|
85
|
+
body: payloadBytes
|
|
86
|
+
});
|
|
87
|
+
if (res.ok) {
|
|
88
|
+
return { ok: true, detail: "ok" };
|
|
89
|
+
}
|
|
90
|
+
let detail = `HTTP ${res.status}`;
|
|
91
|
+
try {
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
if (data && typeof data.detail === "string") {
|
|
94
|
+
detail = data.detail;
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
return { ok: false, detail };
|
|
99
|
+
}
|
|
100
|
+
async function reportEvent(input) {
|
|
101
|
+
if (!config.botSecret || !config.apiKey) {
|
|
102
|
+
throw new Error("SDK not initialized. Call init() first.");
|
|
103
|
+
}
|
|
104
|
+
if (!input?.userId || !input?.event) {
|
|
105
|
+
return { ok: false, detail: "Missing userId or event" };
|
|
106
|
+
}
|
|
107
|
+
if (!allowEvent(input.userId, input.event)) {
|
|
108
|
+
return { ok: true, detail: "cooldown" };
|
|
109
|
+
}
|
|
110
|
+
const payload = {
|
|
111
|
+
chat_id: input.chatId,
|
|
112
|
+
user_id: input.userId,
|
|
113
|
+
event: input.event,
|
|
114
|
+
ts: Date.now() / 1e3,
|
|
115
|
+
is_start: Boolean(input.isStart),
|
|
116
|
+
secret: config.botSecret
|
|
117
|
+
};
|
|
118
|
+
const payloadBytes = JSON.stringify(payload);
|
|
119
|
+
const res = await fetch(config.endpoint, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: buildHeaders(payloadBytes),
|
|
122
|
+
body: payloadBytes
|
|
123
|
+
});
|
|
124
|
+
return { ok: res.ok, status: res.status };
|
|
125
|
+
}
|
|
126
|
+
function inferEventFromMessage(message) {
|
|
127
|
+
if (!message) return "message";
|
|
128
|
+
if (message.photo?.length) return "photo";
|
|
129
|
+
if (message.video) return "video";
|
|
130
|
+
if (message.sticker) return "sticker";
|
|
131
|
+
if (message.animation) return "media";
|
|
132
|
+
return "message";
|
|
133
|
+
}
|
|
134
|
+
function adsteloTelegrafMiddleware(options) {
|
|
135
|
+
ensureInit(options);
|
|
136
|
+
return async (ctx, next) => {
|
|
137
|
+
try {
|
|
138
|
+
const event = ctx?.callbackQuery ? "button" : ctx?.inlineQuery ? "inline_query" : inferEventFromMessage(ctx?.message);
|
|
139
|
+
const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;
|
|
140
|
+
const userId = ctx?.from?.id;
|
|
141
|
+
const isStart = typeof ctx?.message?.text === "string" && ctx.message.text.startsWith("/start");
|
|
142
|
+
if (chatId && userId) {
|
|
143
|
+
await reportEvent({ chatId, userId, event, isStart });
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
}
|
|
147
|
+
if (next) {
|
|
148
|
+
await next();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function adsteloGrammyMiddleware(options) {
|
|
153
|
+
ensureInit(options);
|
|
154
|
+
return async (ctx, next) => {
|
|
155
|
+
try {
|
|
156
|
+
const event = ctx?.callbackQuery ? "button" : ctx?.inlineQuery ? "inline_query" : inferEventFromMessage(ctx?.message);
|
|
157
|
+
const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;
|
|
158
|
+
const userId = ctx?.from?.id;
|
|
159
|
+
const isStart = typeof ctx?.message?.text === "string" && ctx.message.text.startsWith("/start");
|
|
160
|
+
if (chatId && userId) {
|
|
161
|
+
await reportEvent({ chatId, userId, event, isStart });
|
|
162
|
+
}
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
if (next) {
|
|
166
|
+
await next();
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
var adstelo = {
|
|
171
|
+
init,
|
|
172
|
+
reportEvent,
|
|
173
|
+
validateInitCredentials,
|
|
174
|
+
telegraf: adsteloTelegrafMiddleware,
|
|
175
|
+
grammy: adsteloGrammyMiddleware
|
|
176
|
+
};
|
|
177
|
+
var _internal = {
|
|
178
|
+
buildHeaders,
|
|
179
|
+
allowEvent,
|
|
180
|
+
inferEventFromMessage
|
|
181
|
+
};
|
|
182
|
+
export {
|
|
183
|
+
_internal,
|
|
184
|
+
adstelo,
|
|
185
|
+
init,
|
|
186
|
+
reportEvent,
|
|
187
|
+
validateInitCredentials
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nexport type InitOptions = {\n botSecret: string;\n apiKey: string;\n cooldownSeconds?: number;\n baseUrl?: string;\n};\n\nexport type ReportEventInput = {\n chatId: number | string;\n userId: number | string;\n event: string;\n isStart?: boolean;\n};\n\ntype Config = {\n apiKey: string | null;\n botSecret: string | null;\n endpoint: string;\n validateEndpoint: string;\n cooldownSeconds: number;\n};\n\nconst DEFAULT_BASE_URL = \"https://sdk.adstelo.com\";\n\nconst config: Config = {\n apiKey: null,\n botSecret: null,\n endpoint: `${DEFAULT_BASE_URL}/event`,\n validateEndpoint: `${DEFAULT_BASE_URL}/event/validate`,\n cooldownSeconds: 2\n};\n\nlet initialized = false;\nlet validationPromise: Promise<void> | null = null;\nconst cooldownMap = new Map<string, number>();\n\nfunction buildHeaders(payloadBytes: string) {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"ngrok-skip-browser-warning\": \"true\"\n };\n\n if (config.apiKey) {\n headers[\"X-Api-Key\"] = config.apiKey;\n const signature = crypto\n .createHmac(\"sha256\", config.apiKey)\n .update(payloadBytes)\n .digest(\"hex\");\n headers[\"X-Signature\"] = signature;\n }\n\n return headers;\n}\n\nfunction nowSeconds() {\n return Math.floor(Date.now() / 1000);\n}\n\nfunction cooldownKey(userId: string | number, event: string) {\n return `${userId}:${event}`;\n}\n\nfunction allowEvent(userId: string | number, event: string) {\n const key = cooldownKey(userId, event);\n const current = nowSeconds();\n const last = cooldownMap.get(key) ?? 0;\n if (current - last < config.cooldownSeconds) {\n return false;\n }\n cooldownMap.set(key, current);\n return true;\n}\n\nexport function init(options: InitOptions) {\n if (!options || !options.botSecret || !options.apiKey) {\n throw new Error(\"botSecret and apiKey are required\");\n }\n\n config.botSecret = options.botSecret;\n config.apiKey = options.apiKey;\n config.cooldownSeconds = options.cooldownSeconds ?? 2;\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n config.endpoint = `${baseUrl.replace(/\\/$/, \"\")}/event`;\n config.validateEndpoint = `${baseUrl.replace(/\\/$/, \"\")}/event/validate`;\n initialized = true;\n}\n\nfunction ensureInit(options?: InitOptions) {\n if (initialized) return;\n if (!options?.botSecret || !options?.apiKey) {\n throw new Error(\"Missing botSecret or apiKey. Pass them to adstelo.grammy/adstelo.telegraf or call init().\");\n }\n init(options);\n if (!validationPromise) {\n validationPromise = validateInitCredentials()\n .then(({ ok, detail }) => {\n if (!ok) {\n console.error(\"[adstelo] init validation failed:\", detail);\n process.exit(1);\n }\n console.log(\"[adstelo] init validation ok\");\n })\n .catch((err) => {\n console.error(\"[adstelo] init validation failed:\", err?.message || err);\n process.exit(1);\n });\n }\n}\n\nexport async function validateInitCredentials() {\n if (!config.botSecret) {\n return { ok: false, detail: \"Missing botSecret\" };\n }\n if (!config.apiKey) {\n return { ok: false, detail: \"Missing apiKey\" };\n }\n\n const payload = { secret: config.botSecret };\n const payloadBytes = JSON.stringify(payload);\n const res = await fetch(config.validateEndpoint, {\n method: \"POST\",\n headers: buildHeaders(payloadBytes),\n body: payloadBytes\n });\n\n if (res.ok) {\n return { ok: true, detail: \"ok\" };\n }\n\n let detail = `HTTP ${res.status}`;\n try {\n const data = await res.json();\n if (data && typeof data.detail === \"string\") {\n detail = data.detail;\n }\n } catch {\n // ignore\n }\n return { ok: false, detail };\n}\n\nexport async function reportEvent(input: ReportEventInput) {\n if (!config.botSecret || !config.apiKey) {\n throw new Error(\"SDK not initialized. Call init() first.\");\n }\n if (!input?.userId || !input?.event) {\n return { ok: false, detail: \"Missing userId or event\" };\n }\n\n if (!allowEvent(input.userId, input.event)) {\n return { ok: true, detail: \"cooldown\" };\n }\n\n const payload = {\n chat_id: input.chatId,\n user_id: input.userId,\n event: input.event,\n ts: Date.now() / 1000,\n is_start: Boolean(input.isStart),\n secret: config.botSecret\n };\n\n const payloadBytes = JSON.stringify(payload);\n const res = await fetch(config.endpoint, {\n method: \"POST\",\n headers: buildHeaders(payloadBytes),\n body: payloadBytes\n });\n\n return { ok: res.ok, status: res.status };\n}\n\nfunction inferEventFromMessage(message: any) {\n if (!message) return \"message\";\n if (message.photo?.length) return \"photo\";\n if (message.video) return \"video\";\n if (message.sticker) return \"sticker\";\n if (message.animation) return \"media\";\n return \"message\";\n}\n\nfunction adsteloTelegrafMiddleware(options?: InitOptions) {\n ensureInit(options);\n return async (ctx: any, next: () => Promise<void>) => {\n try {\n const event = ctx?.callbackQuery\n ? \"button\"\n : ctx?.inlineQuery\n ? \"inline_query\"\n : inferEventFromMessage(ctx?.message);\n const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;\n const userId = ctx?.from?.id;\n const isStart = typeof ctx?.message?.text === \"string\" && ctx.message.text.startsWith(\"/start\");\n\n if (chatId && userId) {\n await reportEvent({ chatId, userId, event, isStart });\n }\n } catch {\n // swallow\n }\n\n if (next) {\n await next();\n }\n };\n}\n\nfunction adsteloGrammyMiddleware(options?: InitOptions) {\n ensureInit(options);\n return async (ctx: any, next: () => Promise<void>) => {\n try {\n const event = ctx?.callbackQuery\n ? \"button\"\n : ctx?.inlineQuery\n ? \"inline_query\"\n : inferEventFromMessage(ctx?.message);\n const chatId = ctx?.chat?.id ?? ctx?.message?.chat?.id;\n const userId = ctx?.from?.id;\n const isStart = typeof ctx?.message?.text === \"string\" && ctx.message.text.startsWith(\"/start\");\n\n if (chatId && userId) {\n await reportEvent({ chatId, userId, event, isStart });\n }\n } catch {\n // swallow\n }\n\n if (next) {\n await next();\n }\n };\n}\n\nexport const adstelo = {\n init,\n reportEvent,\n validateInitCredentials,\n telegraf: adsteloTelegrafMiddleware,\n grammy: adsteloGrammyMiddleware\n};\n\nexport const _internal = {\n buildHeaders,\n allowEvent,\n inferEventFromMessage\n};\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adstelo/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Adstelo Telegram ads SDK for bots",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsup --watch",
|
|
24
|
+
"test": "vitest run"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.10.0",
|
|
28
|
+
"tsup": "^8.3.5",
|
|
29
|
+
"typescript": "^5.6.3",
|
|
30
|
+
"vitest": "^2.1.5"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"grammy": "^1.41.1",
|
|
34
|
+
"telegraf": "^4.16.3"
|
|
35
|
+
}
|
|
36
|
+
}
|