tgwidget 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 +75 -0
- package/dist/builder.d.ts +29 -0
- package/dist/builder.js +89 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/parser.d.ts +10 -0
- package/dist/parser.js +97 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# tgwidget
|
|
2
|
+
|
|
3
|
+
Node.js SDK for [TeleWidget](https://tgwidget.github.io/) — beautiful Telegram Mini App widgets for bots.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install tgwidget
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Generate widget URL
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { tgwidget } from "tgwidget";
|
|
17
|
+
|
|
18
|
+
// Date picker
|
|
19
|
+
const url = tgwidget("your_bot").date({ mode: "datetime", format: "unix-s" }).url();
|
|
20
|
+
|
|
21
|
+
// Color picker
|
|
22
|
+
const url = tgwidget("your_bot").color({ format: "hex" }).url();
|
|
23
|
+
|
|
24
|
+
// Schedule
|
|
25
|
+
const url = tgwidget("your_bot").schedule().url();
|
|
26
|
+
|
|
27
|
+
// With styling
|
|
28
|
+
const url = tgwidget("your_bot")
|
|
29
|
+
.date({ mode: "date" })
|
|
30
|
+
.style({ colorScheme: "dark", accent: "#FF6600", adoptTgPalette: true })
|
|
31
|
+
.url();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Parse results
|
|
35
|
+
|
|
36
|
+
When a user completes the widget, the result comes back via deep link `t.me/your_bot?start=VALUE`. Parse the value:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { parseDate, parseColor, parseSchedule } from "tgwidget";
|
|
40
|
+
|
|
41
|
+
// Date result
|
|
42
|
+
const result = parseDate("2025-03-15", { mode: "date" });
|
|
43
|
+
// { date: '2025-03-15' }
|
|
44
|
+
|
|
45
|
+
// Unix timestamp
|
|
46
|
+
const result = parseDate("1710460800_1718236800", { mode: "date-range", format: "unix-s" });
|
|
47
|
+
// { timestamp: 1710460800, timestampEnd: 1718236800, date: '2025-03-15', ... }
|
|
48
|
+
|
|
49
|
+
// Color
|
|
50
|
+
const result = parseColor("FF6600", { format: "hex" });
|
|
51
|
+
// { raw: 'FF6600', hex: '#FF6600' }
|
|
52
|
+
|
|
53
|
+
// Schedule (56-char bunch format)
|
|
54
|
+
const result = parseSchedule("09001800090018000000000009001800090018000000000000000000");
|
|
55
|
+
// [{ enabled: true, start: '09:00', end: '18:00' }, ...]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API
|
|
59
|
+
|
|
60
|
+
### `tgwidget(botUsername)`
|
|
61
|
+
|
|
62
|
+
Create a widget builder. Returns a chainable `TgWidget` instance.
|
|
63
|
+
|
|
64
|
+
- `.date({ mode?, format?, order? })` — Date/time picker
|
|
65
|
+
- `.color({ format? })` — Color picker
|
|
66
|
+
- `.schedule()` — Weekly schedule
|
|
67
|
+
- `.style({ colorScheme?, accent?, tint?, liquidGlass?, adaptTgTheme?, adoptTgPalette? })` — Styling
|
|
68
|
+
- `.url(baseUrl?)` — Generate the final URL
|
|
69
|
+
- `.payload()` — Get the raw payload object
|
|
70
|
+
|
|
71
|
+
### Parsers
|
|
72
|
+
|
|
73
|
+
- `parseDate(value, { mode?, format?, order? })` → `DateResult`
|
|
74
|
+
- `parseColor(value, { format? })` → `ColorResult`
|
|
75
|
+
- `parseSchedule(value)` → `ScheduleDay[]`
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DateMode, DateFormat, DateOrder, ColorFormat, ColorScheme } from "./types";
|
|
2
|
+
export declare class TgWidget {
|
|
3
|
+
private _botUsername;
|
|
4
|
+
private _widget;
|
|
5
|
+
private _payload;
|
|
6
|
+
private _style;
|
|
7
|
+
constructor(botUsername: string);
|
|
8
|
+
date(opts?: {
|
|
9
|
+
mode?: DateMode;
|
|
10
|
+
format?: DateFormat;
|
|
11
|
+
order?: DateOrder;
|
|
12
|
+
}): this;
|
|
13
|
+
color(opts?: {
|
|
14
|
+
format?: ColorFormat;
|
|
15
|
+
}): this;
|
|
16
|
+
schedule(): this;
|
|
17
|
+
style(opts?: {
|
|
18
|
+
colorScheme?: ColorScheme;
|
|
19
|
+
accent?: string;
|
|
20
|
+
tint?: string;
|
|
21
|
+
liquidGlass?: boolean;
|
|
22
|
+
adaptTgTheme?: boolean;
|
|
23
|
+
adoptTgPalette?: boolean;
|
|
24
|
+
}): this;
|
|
25
|
+
private _buildPayload;
|
|
26
|
+
url(baseUrl?: string): string;
|
|
27
|
+
payload(): Record<string, unknown>;
|
|
28
|
+
}
|
|
29
|
+
export declare function tgwidget(botUsername: string): TgWidget;
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const BASE_URL = "https://tgwidget.github.io/";
|
|
2
|
+
const HEX_RE = /^#?[0-9A-Fa-f]{6}$/;
|
|
3
|
+
const VALID_DATE_MODES = new Set(["date", "time", "datetime", "date-range", "time-range"]);
|
|
4
|
+
const VALID_DATE_FORMATS = new Set(["default", "unix-s", "unix-ms"]);
|
|
5
|
+
const VALID_DATE_ORDERS = new Set(["ymd", "dmy", "mdy"]);
|
|
6
|
+
const VALID_COLOR_FORMATS = new Set(["hex", "rgb", "hsl"]);
|
|
7
|
+
const VALID_COLOR_SCHEMES = new Set(["light", "dark", "auto"]);
|
|
8
|
+
function validateHex(color, name) {
|
|
9
|
+
if (!HEX_RE.test(color)) {
|
|
10
|
+
throw new Error(`${name} must be a valid hex color (e.g. '#FF0000'), got '${color}'`);
|
|
11
|
+
}
|
|
12
|
+
return color.startsWith("#") ? color : `#${color}`;
|
|
13
|
+
}
|
|
14
|
+
function toBase64(str) {
|
|
15
|
+
if (typeof Buffer !== "undefined") {
|
|
16
|
+
return Buffer.from(str).toString("base64");
|
|
17
|
+
}
|
|
18
|
+
return btoa(str);
|
|
19
|
+
}
|
|
20
|
+
export class TgWidget {
|
|
21
|
+
constructor(botUsername) {
|
|
22
|
+
this._widget = null;
|
|
23
|
+
this._payload = {};
|
|
24
|
+
this._style = {};
|
|
25
|
+
if (!botUsername)
|
|
26
|
+
throw new Error("botUsername is required");
|
|
27
|
+
this._botUsername = botUsername;
|
|
28
|
+
}
|
|
29
|
+
date(opts = {}) {
|
|
30
|
+
const { mode = "date", format = "default", order = "ymd" } = opts;
|
|
31
|
+
if (!VALID_DATE_MODES.has(mode))
|
|
32
|
+
throw new Error(`Invalid date mode '${mode}'`);
|
|
33
|
+
if (!VALID_DATE_FORMATS.has(format))
|
|
34
|
+
throw new Error(`Invalid date format '${format}'`);
|
|
35
|
+
if (!VALID_DATE_ORDERS.has(order))
|
|
36
|
+
throw new Error(`Invalid date order '${order}'`);
|
|
37
|
+
this._widget = "date";
|
|
38
|
+
this._payload = { mode, format, order };
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
color(opts = {}) {
|
|
42
|
+
const { format = "hex" } = opts;
|
|
43
|
+
if (!VALID_COLOR_FORMATS.has(format))
|
|
44
|
+
throw new Error(`Invalid color format '${format}'`);
|
|
45
|
+
this._widget = "color";
|
|
46
|
+
this._payload = { format };
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
schedule() {
|
|
50
|
+
this._widget = "schedule";
|
|
51
|
+
this._payload = { format: "bunch" };
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
style(opts = {}) {
|
|
55
|
+
const { colorScheme = "light", accent, tint, liquidGlass = false, adaptTgTheme = false, adoptTgPalette = false } = opts;
|
|
56
|
+
if (!VALID_COLOR_SCHEMES.has(colorScheme))
|
|
57
|
+
throw new Error(`Invalid colorScheme '${colorScheme}'`);
|
|
58
|
+
this._style = { colorScheme, liquidGlass, adaptTgTheme, adoptTgPalette };
|
|
59
|
+
if (accent)
|
|
60
|
+
this._style.accent = validateHex(accent, "accent");
|
|
61
|
+
if (tint)
|
|
62
|
+
this._style.tint = validateHex(tint, "tint");
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
_buildPayload() {
|
|
66
|
+
if (!this._widget)
|
|
67
|
+
throw new Error("No widget type set. Call .date(), .color(), or .schedule() first.");
|
|
68
|
+
const payload = {
|
|
69
|
+
widget: this._widget,
|
|
70
|
+
bot_username: this._botUsername,
|
|
71
|
+
...this._payload,
|
|
72
|
+
};
|
|
73
|
+
if (Object.keys(this._style).length > 0) {
|
|
74
|
+
payload.style = this._style;
|
|
75
|
+
}
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
url(baseUrl = BASE_URL) {
|
|
79
|
+
const payload = this._buildPayload();
|
|
80
|
+
const encoded = toBase64(JSON.stringify(payload));
|
|
81
|
+
return `${baseUrl}?p=${encoded}`;
|
|
82
|
+
}
|
|
83
|
+
payload() {
|
|
84
|
+
return this._buildPayload();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export function tgwidget(botUsername) {
|
|
88
|
+
return new TgWidget(botUsername);
|
|
89
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { DateMode, DateFormat, DateOrder, ColorFormat, DateResult, ColorResult, ScheduleDay } from "./types";
|
|
2
|
+
export declare function parseDate(value: string, opts?: {
|
|
3
|
+
mode?: DateMode;
|
|
4
|
+
format?: DateFormat;
|
|
5
|
+
order?: DateOrder;
|
|
6
|
+
}): DateResult;
|
|
7
|
+
export declare function parseColor(value: string, opts?: {
|
|
8
|
+
format?: ColorFormat;
|
|
9
|
+
}): ColorResult;
|
|
10
|
+
export declare function parseSchedule(value: string): ScheduleDay[];
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
function parseDateStr(value, order) {
|
|
2
|
+
const parts = value.split("-");
|
|
3
|
+
if (order === "ymd")
|
|
4
|
+
return `${parts[0]}-${parts[1]}-${parts[2]}`;
|
|
5
|
+
if (order === "dmy")
|
|
6
|
+
return `${parts[2]}-${parts[1]}-${parts[0]}`;
|
|
7
|
+
return `${parts[2]}-${parts[0]}-${parts[1]}`; // mdy
|
|
8
|
+
}
|
|
9
|
+
export function parseDate(value, opts = {}) {
|
|
10
|
+
const { mode = "date", format = "default", order = "ymd" } = opts;
|
|
11
|
+
const result = {};
|
|
12
|
+
if (format === "unix-s" || format === "unix-ms") {
|
|
13
|
+
const parts = value.split("_");
|
|
14
|
+
const mul = format === "unix-s" ? 1000 : 1;
|
|
15
|
+
const ts = parseInt(parts[0], 10);
|
|
16
|
+
result.timestamp = ts;
|
|
17
|
+
const dt = new Date(ts * mul);
|
|
18
|
+
result.date = dt.toISOString().slice(0, 10);
|
|
19
|
+
result.time = dt.toISOString().slice(11, 16);
|
|
20
|
+
if (parts.length > 1) {
|
|
21
|
+
const tsEnd = parseInt(parts[1], 10);
|
|
22
|
+
result.timestampEnd = tsEnd;
|
|
23
|
+
const dtEnd = new Date(tsEnd * mul);
|
|
24
|
+
result.dateEnd = dtEnd.toISOString().slice(0, 10);
|
|
25
|
+
result.timeEnd = dtEnd.toISOString().slice(11, 16);
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
switch (mode) {
|
|
30
|
+
case "date":
|
|
31
|
+
result.date = parseDateStr(value, order);
|
|
32
|
+
break;
|
|
33
|
+
case "time": {
|
|
34
|
+
const [h, m] = value.split("-");
|
|
35
|
+
result.time = `${h}:${m}`;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
case "datetime": {
|
|
39
|
+
const [datePart, timePart] = value.split("_");
|
|
40
|
+
result.date = parseDateStr(datePart, order);
|
|
41
|
+
const [h, m] = timePart.split("-");
|
|
42
|
+
result.time = `${h}:${m}`;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "date-range": {
|
|
46
|
+
const parts = value.split("_");
|
|
47
|
+
result.date = parseDateStr(parts[0], order);
|
|
48
|
+
result.dateEnd = parseDateStr(parts[1], order);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case "time-range": {
|
|
52
|
+
const parts = value.split("_");
|
|
53
|
+
const [h1, m1] = parts[0].split("-");
|
|
54
|
+
const [h2, m2] = parts[1].split("-");
|
|
55
|
+
result.time = `${h1}:${m1}`;
|
|
56
|
+
result.timeEnd = `${h2}:${m2}`;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
export function parseColor(value, opts = {}) {
|
|
63
|
+
const { format = "hex" } = opts;
|
|
64
|
+
const result = { raw: value };
|
|
65
|
+
if (format === "hex") {
|
|
66
|
+
result.hex = `#${value}`;
|
|
67
|
+
}
|
|
68
|
+
else if (format === "rgb") {
|
|
69
|
+
const parts = value.split("_").map(Number);
|
|
70
|
+
result.rgb = [parts[0], parts[1], parts[2]];
|
|
71
|
+
}
|
|
72
|
+
else if (format === "hsl") {
|
|
73
|
+
const parts = value.split("_").map(Number);
|
|
74
|
+
result.hsl = [parts[0], parts[1], parts[2]];
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
export function parseSchedule(value) {
|
|
79
|
+
if (value.length !== 56) {
|
|
80
|
+
throw new Error(`Schedule bunch format must be 56 chars, got ${value.length}`);
|
|
81
|
+
}
|
|
82
|
+
const days = [];
|
|
83
|
+
for (let i = 0; i < 7; i++) {
|
|
84
|
+
const chunk = value.slice(i * 8, (i + 1) * 8);
|
|
85
|
+
if (chunk === "00000000") {
|
|
86
|
+
days.push({ enabled: false });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
days.push({
|
|
90
|
+
enabled: true,
|
|
91
|
+
start: `${chunk.slice(0, 2)}:${chunk.slice(2, 4)}`,
|
|
92
|
+
end: `${chunk.slice(4, 6)}:${chunk.slice(6, 8)}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return days;
|
|
97
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type DateMode = "date" | "time" | "datetime" | "date-range" | "time-range";
|
|
2
|
+
export type DateFormat = "default" | "unix-s" | "unix-ms";
|
|
3
|
+
export type DateOrder = "ymd" | "dmy" | "mdy";
|
|
4
|
+
export type ColorFormat = "hex" | "rgb" | "hsl";
|
|
5
|
+
export type ColorScheme = "light" | "dark" | "auto";
|
|
6
|
+
export interface WidgetStyle {
|
|
7
|
+
colorScheme?: ColorScheme;
|
|
8
|
+
accent?: string;
|
|
9
|
+
tint?: string;
|
|
10
|
+
liquidGlass?: boolean;
|
|
11
|
+
adaptTgTheme?: boolean;
|
|
12
|
+
adoptTgPalette?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface DateResult {
|
|
15
|
+
date?: string;
|
|
16
|
+
time?: string;
|
|
17
|
+
dateEnd?: string;
|
|
18
|
+
timeEnd?: string;
|
|
19
|
+
timestamp?: number;
|
|
20
|
+
timestampEnd?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ColorResult {
|
|
23
|
+
raw: string;
|
|
24
|
+
hex?: string;
|
|
25
|
+
rgb?: [number, number, number];
|
|
26
|
+
hsl?: [number, number, number];
|
|
27
|
+
}
|
|
28
|
+
export interface ScheduleDay {
|
|
29
|
+
enabled: boolean;
|
|
30
|
+
start?: string;
|
|
31
|
+
end?: string;
|
|
32
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tgwidget",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node.js SDK for TeleWidget — Telegram Mini App widgets",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"prepublishOnly": "tsc"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["telegram", "mini-apps", "widget", "bot", "tgwidget"],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/tgwidget/tgwidget-node"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/tgwidget/tgwidget-node",
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.6.2",
|
|
28
|
+
"typescript": "^5.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|