feat: telegram bot (#238)

This commit is contained in:
Dream Hunter
2024-05-16 12:57:23 +08:00
committed by GitHub
parent 6bb6fa8298
commit 78badf2eaa
23 changed files with 844 additions and 57 deletions
+6
View File
@@ -17,6 +17,7 @@ import MailsUnknow from './admin/MailsUnknow.vue';
import About from './common/About.vue'; import About from './common/About.vue';
import Maintenance from './admin/Maintenance.vue'; import Maintenance from './admin/Maintenance.vue';
import Appearance from './common/Appearance.vue'; import Appearance from './common/Appearance.vue';
import Telegram from './admin/Telegram.vue';
const { const {
localeCache, adminAuth, showAdminAuth, adminTab, loading, globalTabplacement localeCache, adminAuth, showAdminAuth, adminTab, loading, globalTabplacement
@@ -46,6 +47,7 @@ const { t } = useI18n({
unknow: 'Mails with unknow receiver', unknow: 'Mails with unknow receiver',
senderAccess: 'Sender Access Control', senderAccess: 'Sender Access Control',
sendBox: 'Send Box', sendBox: 'Send Box',
telegram: 'Telegram Bot',
statistics: 'Statistics', statistics: 'Statistics',
maintenance: 'Maintenance', maintenance: 'Maintenance',
appearance: 'Appearance', appearance: 'Appearance',
@@ -64,6 +66,7 @@ const { t } = useI18n({
unknow: '无收件人邮件', unknow: '无收件人邮件',
senderAccess: '发件权限控制', senderAccess: '发件权限控制',
sendBox: '发件箱', sendBox: '发件箱',
telegram: '电报机器人',
statistics: '统计', statistics: '统计',
maintenance: '维护', maintenance: '维护',
appearance: '外观', appearance: '外观',
@@ -121,6 +124,9 @@ onMounted(async () => {
<n-tab-pane name="sendBox" :tab="t('sendBox')"> <n-tab-pane name="sendBox" :tab="t('sendBox')">
<SendBox /> <SendBox />
</n-tab-pane> </n-tab-pane>
<n-tab-pane name="telegram" :tab="t('telegram')">
<Telegram />
</n-tab-pane>
<n-tab-pane name="statistics" :tab="t('statistics')"> <n-tab-pane name="statistics" :tab="t('statistics')">
<Statistics /> <Statistics />
</n-tab-pane> </n-tab-pane>
+77
View File
@@ -0,0 +1,77 @@
<script setup>
import { useI18n } from 'vue-i18n'
import { useGlobalState } from '../../store'
import { api } from '../../api'
const { localeCache } = useGlobalState()
const message = useMessage()
const { t } = useI18n({
locale: localeCache.value || 'zh',
messages: {
en: {
init: 'Init',
successTip: 'Success',
status: 'Check Status',
},
zh: {
init: '初始化',
successTip: '成功',
status: '查看状态',
}
}
});
const status = ref({
fetched: false,
})
const fetchData = async () => {
try {
const res = await api.fetch(`/admin/telegram/status`)
Object.assign(status.value, res)
status.value.fetched = true
} catch (error) {
message.error(error.message || "error");
}
}
const save = async () => {
try {
await api.fetch(`/admin/telegram/init`, {
method: 'POST',
})
message.success(t('successTip'))
} catch (error) {
message.error(error.message || "error");
}
}
</script>
<template>
<div class="center">
<n-card style="max-width: 800px; overflow: auto;">
<n-button @click="save" type="primary" block>
{{ t('init') }}
</n-button>
<n-button @click="fetchData" secondary block>
{{ t('status') }}
</n-button>
<pre v-if="status.fetched">{{ JSON.stringify(status, null, 2) }}</pre>
</n-card>
</div>
</template>
<style scoped>
.center {
display: flex;
text-align: left;
place-items: center;
justify-content: center;
}
.n-button {
margin-top: 10px;
}
</style>
+4 -7
View File
@@ -5,22 +5,19 @@ import { GithubAlt, Discord, Telegram } from '@vicons/fa'
<template> <template>
<div class="center"> <div class="center">
<n-card> <n-card>
<n-button tag="a" target="_blank" href="https://github.com/dreamhunter2333/cloudflare_temp_email" secondary <n-button tag="a" target="_blank" href="https://github.com/dreamhunter2333/cloudflare_temp_email">
block strong>
<template #icon> <template #icon>
<n-icon :component="GithubAlt" /> <n-icon :component="GithubAlt" />
</template> </template>
Github Github
</n-button> </n-button>
<n-button tag="a" target="_blank" href="https://discord.gg/dQEwTWhA6Q" secondary <n-button tag="a" target="_blank" href="https://discord.gg/dQEwTWhA6Q">
block strong>
<template #icon> <template #icon>
<n-icon :component="Discord" /> <n-icon :component="Discord" />
</template> </template>
Discord Discord
</n-button> </n-button>
<n-button tag="a" target="_blank" href="https://t.me/cloudflare_temp_email" secondary <n-button tag="a" target="_blank" href="https://t.me/cloudflare_temp_email">
block strong>
<template #icon> <template #icon>
<n-icon :component="Telegram" /> <n-icon :component="Telegram" />
</template> </template>
@@ -38,10 +35,10 @@ import { GithubAlt, Discord, Telegram } from '@vicons/fa'
.n-card { .n-card {
max-width: 800px; max-width: 800px;
text-align: left;
} }
.n-button { .n-button {
margin-top: 10px; margin-top: 10px;
margin-left: 10px;
} }
</style> </style>
+2
View File
@@ -95,6 +95,8 @@ ENABLE_AUTO_REPLY = false
# dkim config # dkim config
# DKIM_SELECTOR = "mailchannels" # Refer to the DKIM section mailchannels._domainkey for mailchannels # DKIM_SELECTOR = "mailchannels" # Refer to the DKIM section mailchannels._domainkey for mailchannels
# DKIM_PRIVATE_KEY = "" # Refer to the contents of priv_key.txt in the DKIM section # DKIM_PRIVATE_KEY = "" # Refer to the contents of priv_key.txt in the DKIM section
# telegram bot
# TG_MAX_ACCOUNTS = 5
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"
@@ -12,6 +12,7 @@ cp wrangler.toml.template wrangler.toml
> [!NOTE] > [!NOTE]
> 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤 > 如果你要启用注册用户功能,并需要发送邮件验证,则需要创建 `KV` 缓存, 不需要可跳过此步骤
> 如果需要 Telegram Bot,需要创建 `KV` 缓存,不需要可跳过此步骤
通过命令行创建 KV 缓存,或者在 Cloudflare 控制台创建,然后复制对应配置到 `wrangler.toml` 文件中 通过命令行创建 KV 缓存,或者在 Cloudflare 控制台创建,然后复制对应配置到 `wrangler.toml` 文件中
@@ -62,6 +63,8 @@ ENABLE_AUTO_REPLY = false
# dkim config # dkim config
# DKIM_SELECTOR = "mailchannels" # 参考 DKIM 部分 mailchannels._domainkey 的 mailchannels # DKIM_SELECTOR = "mailchannels" # 参考 DKIM 部分 mailchannels._domainkey 的 mailchannels
# DKIM_PRIVATE_KEY = "" # 参考 DKIM 部分 priv_key.txt 的内容 # DKIM_PRIVATE_KEY = "" # 参考 DKIM 部分 priv_key.txt 的内容
# telegram bot 最多绑定邮箱数量
# TG_MAX_ACCOUNTS = 5
# D1 数据库的名称和 ID 可以在 cloudflare 控制台查看 # D1 数据库的名称和 ID 可以在 cloudflare 控制台查看
[[d1_databases]] [[d1_databases]]
@@ -83,6 +86,17 @@ database_id = "xxx" # D1 数据库 ID
# simple = { limit = 10, period = 60 } # simple = { limit = 10, period = 60 }
``` ```
## Telegram Bot 配置
> [!NOTE]
> 如果不需要 Telegram Bot, 可跳过此步骤
请先创建一个 Telegram Bot,然后获取 `token`,然后执行下面的命令,将 `token` 添加到 secrets 中
```bash
pnpm wrangler secret put TELEGRAM_BOT_TOKEN
```
## 部署 ## 部署
第一次部署会提示创建项目, `production` 分支请填写 `production` 第一次部署会提示创建项目, `production` 分支请填写 `production`
@@ -28,6 +28,8 @@ docker-compose up -d
修改 docker-compose.yaml 中的环境变量, 注意选择合适的 `tag` 修改 docker-compose.yaml 中的环境变量, 注意选择合适的 `tag`
`proxy_url``worker` 的 URL 地址
```yaml ```yaml
services: services:
smtp_proxy_server: smtp_proxy_server:
@@ -7,9 +7,9 @@
```python ```python
limit = 10 limit = 10
offset = 0 offset = 0
res = requests.post( res = requests.get(
f"http://localhost:8787/api/mails?limit={limit}&offset={offset}`;", f"http://localhost:8787/api/mails?limit={limit}&offset={offset}`;",
json=send_body, headers={ headers={
"Authorization": f"Bearer {你的JWT密码}", "Authorization": f"Bearer {你的JWT密码}",
# "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码 # "x-custom-auth": "<你的网站密码>", # 如果启用了自定义密码
"Content-Type": "application/json" "Content-Type": "application/json"
@@ -45,3 +45,10 @@
![worker-kv](/ui_install/worker-kv.png) ![worker-kv](/ui_install/worker-kv.png)
![worker-kv-bind](/ui_install/worker-kv-bind.png) ![worker-kv-bind](/ui_install/worker-kv-bind.png)
9. Telegram Bot 配置
> [!NOTE]
> 如果不需要 Telegram Bot, 可跳过此步骤
请先创建一个 Telegram Bot,然后获取 `token`,然后执行下面的命令,将 `token` 添加到 `Variables` 中, Name: `TELEGRAM_BOT_TOKEN`
+1
View File
@@ -131,3 +131,4 @@ dist
.wrangler .wrangler
wrangler.toml wrangler.toml
.dev.vars
+12 -4
View File
@@ -5,15 +5,23 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "wrangler dev", "dev": "wrangler dev",
"deploy": "wrangler deploy", "deploy": "wrangler deploy --minify",
"start": "wrangler dev", "start": "wrangler dev",
"build": "wrangler deploy src/worker.js --dry-run --outdir dist --minify" "build": "wrangler deploy src/worker.js --dry-run --outdir dist --minify"
}, },
"devDependencies": { "devDependencies": {
"wrangler": "^3.53.1" "@cloudflare/workers-types": "^4.20240512.0",
"wrangler": "^3.55.0"
}, },
"dependencies": { "dependencies": {
"hono": "^4.3.0", "hono": "^4.3.6",
"mimetext": "^3.0.24" "mimetext": "^3.0.24",
"postal-mime": "^2.2.5",
"telegraf": "4.16.3"
},
"pnpm": {
"patchedDependencies": {
"telegraf@4.16.3": "patches/telegraf@4.16.3.patch"
}
} }
} }
+163
View File
@@ -0,0 +1,163 @@
diff --git a/lib/core/network/client.js b/lib/core/network/client.js
index 25fbbbb47c7f88e83ae26f629e5ae1a0c141725c..209d4a6bf05352f44eeb082eb327581d698de5ce 100644
--- a/lib/core/network/client.js
+++ b/lib/core/network/client.js
@@ -1,18 +1,18 @@
"use strict";
-var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
- desc = { enumerable: true, get: function() { return m[k]; } };
+ desc = { enumerable: true, get: function () { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
-}) : (function(o, m, k, k2) {
+}) : (function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
-var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
+var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function (o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
-}) : function(o, v) {
+}) : function (o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
@@ -29,8 +29,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
/* eslint @typescript-eslint/restrict-template-expressions: [ "error", { "allowNumber": true, "allowBoolean": true } ] */
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
-const promises_1 = require("fs/promises");
-const https = __importStar(require("https"));
+// const promises_1 = require("fs/promises");
+// const https = __importStar(require("https"));
const path = __importStar(require("path"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const check_1 = require("../helpers/check");
@@ -61,10 +61,10 @@ const DEFAULT_OPTIONS = {
apiRoot: 'https://api.telegram.org',
apiMode: 'bot',
webhookReply: true,
- agent: new https.Agent({
- keepAlive: true,
- keepAliveMsecs: 10000,
- }),
+ // agent: new https.Agent({
+ // keepAlive: true,
+ // keepAliveMsecs: 10000,
+ // }),
attachmentAgent: undefined,
testEnv: false,
};
@@ -112,9 +112,9 @@ async function buildFormDataConfig(payload, agent) {
}
const boundary = crypto.randomBytes(32).toString('hex');
const formData = new multipart_stream_1.default(boundary);
- await Promise.all(Object.keys(payload).map((key) =>
- // @ts-expect-error payload[key] can obviously index payload, but TS doesn't trust us
- attachFormValue(formData, key, payload[key], agent)));
+ await Promise.all(Object.keys(payload).map((key) =>
+ // @ts-expect-error payload[key] can obviously index payload, but TS doesn't trust us
+ attachFormValue(formData, key, payload[key], agent)));
return {
method: 'POST',
compress: true,
@@ -205,14 +205,15 @@ async function attachFormMedia(form, media, id, agent) {
if ('source' in media && media.source) {
let mediaSource = media.source;
if (typeof media.source === 'string') {
- const source = await (0, promises_1.realpath)(media.source);
- if ((await (0, promises_1.stat)(source)).isFile()) {
- fileName = (_c = media.filename) !== null && _c !== void 0 ? _c : path.basename(media.source);
- mediaSource = await fs.createReadStream(media.source);
- }
- else {
- throw new TypeError(`Unable to upload '${media.source}', not a file`);
- }
+ throw new TypeError(`Unable to upload '${media.source}', not a file`);
+ // const source = await (0, promises_1.realpath)(media.source);
+ // if ((await (0, promises_1.stat)(source)).isFile()) {
+ // fileName = (_c = media.filename) !== null && _c !== void 0 ? _c : path.basename(media.source);
+ // mediaSource = await fs.createReadStream(media.source);
+ // }
+ // else {
+ // throw new TypeError(`Unable to upload '${media.source}', not a file`);
+ // }
}
if (isStream(mediaSource) || Buffer.isBuffer(mediaSource)) {
form.addPart({
diff --git a/lib/core/network/polling.js b/lib/core/network/polling.js
index 42f20a5090304c56d0970da56eeaaacaa518ca92..0ae889c32d46e33440c62ad6d27a290c0fe3dda2 100644
--- a/lib/core/network/polling.js
+++ b/lib/core/network/polling.js
@@ -6,10 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.Polling = void 0;
const abort_controller_1 = __importDefault(require("abort-controller"));
const debug_1 = __importDefault(require("debug"));
-const util_1 = require("util");
+// const util_1 = require("util");
const error_1 = require("./error");
const debug = (0, debug_1.default)('telegraf:polling');
-const wait = (0, util_1.promisify)(setTimeout);
+// const wait = (0, util_1.promisify)(setTimeout);
function always(x) {
return () => x;
}
@@ -47,7 +47,8 @@ class Polling {
(err instanceof error_1.TelegramError && err.code >= 500)) {
const retryAfter = (_b = (_a = err.parameters) === null || _a === void 0 ? void 0 : _a.retry_after) !== null && _b !== void 0 ? _b : 5;
debug('Failed to fetch updates, retrying after %ds.', retryAfter, err);
- await wait(retryAfter * 1000);
+ // await wait(retryAfter * 1000);
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (err instanceof error_1.TelegramError &&
diff --git a/lib/telegraf.js b/lib/telegraf.js
index 23d021c3d5f98493bd714a2114ec8fa853560e5c..90094d18316138b7e12eab42f722e69ccc9b6c1f 100644
--- a/lib/telegraf.js
+++ b/lib/telegraf.js
@@ -28,8 +28,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.Telegraf = void 0;
const crypto = __importStar(require("crypto"));
-const http = __importStar(require("http"));
-const https = __importStar(require("https"));
+// const http = __importStar(require("http"));
+// const https = __importStar(require("https"));
const composer_1 = require("./composer");
const compact_1 = require("./core/helpers/compact");
const context_1 = __importDefault(require("./context"));
@@ -157,13 +157,13 @@ class Telegraf extends composer_1.Composer {
const callback = typeof cb === 'function'
? (req, res) => webhookCb(req, res, () => cb(req, res))
: webhookCb;
- this.webhookServer =
- tlsOptions != null
- ? https.createServer(tlsOptions, callback)
- : http.createServer(callback);
- this.webhookServer.listen(port, host, () => {
- debug('Webhook listening on port: %s', port);
- });
+ // this.webhookServer =
+ // tlsOptions != null
+ // ? https.createServer(tlsOptions, callback)
+ // : http.createServer(callback);
+ // this.webhookServer.listen(port, host, () => {
+ // debug('Webhook listening on port: %s', port);
+ // });
return this;
}
secretPathComponent() {
@@ -176,7 +176,7 @@ class Telegraf extends composer_1.Composer {
/**
* @see https://github.com/telegraf/telegraf/discussions/1344#discussioncomment-335700
*/
- async launch(config = {},
+ async launch(config = {},
/** @experimental */
onLaunch) {
var _a, _b;
+163 -26
View File
@@ -4,20 +4,34 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
patchedDependencies:
telegraf@4.16.3:
hash: pnqp5pf7vetkijrc5uah4r5w6m
path: patches/telegraf@4.16.3.patch
importers: importers:
.: .:
dependencies: dependencies:
hono: hono:
specifier: ^4.3.0 specifier: ^4.3.6
version: 4.3.0 version: 4.3.6
mimetext: mimetext:
specifier: ^3.0.24 specifier: ^3.0.24
version: 3.0.24 version: 3.0.24
postal-mime:
specifier: ^2.2.5
version: 2.2.5
telegraf:
specifier: 4.16.3
version: 4.16.3(patch_hash=pnqp5pf7vetkijrc5uah4r5w6m)
devDependencies: devDependencies:
'@cloudflare/workers-types':
specifier: ^4.20240512.0
version: 4.20240512.0
wrangler: wrangler:
specifier: ^3.53.1 specifier: ^3.55.0
version: 3.53.1 version: 3.55.0(@cloudflare/workers-types@4.20240512.0)
packages: packages:
@@ -63,6 +77,9 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@cloudflare/workers-types@4.20240512.0':
resolution: {integrity: sha512-o2yTEWg+YK/I1t/Me+dA0oarO0aCbjibp6wSeaw52DSE9tDyKJ7S+Qdyw/XsMrKn4t8kF6f/YOba+9O4MJfW9w==}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -223,11 +240,18 @@ packages:
'@jridgewell/trace-mapping@0.3.9': '@jridgewell/trace-mapping@0.3.9':
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
'@telegraf/types@7.1.0':
resolution: {integrity: sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==}
'@types/node-forge@1.3.11': '@types/node-forge@1.3.11':
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==} resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
'@types/node@20.12.8': '@types/node@20.12.12':
resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
acorn-walk@8.3.2: acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
@@ -256,6 +280,15 @@ packages:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'} engines: {node: '>=8'}
buffer-alloc-unsafe@1.1.0:
resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==}
buffer-alloc@1.2.0:
resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==}
buffer-fill@1.0.0:
resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==}
capnp-ts@0.7.0: capnp-ts@0.7.0:
resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==} resolution: {integrity: sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==}
@@ -267,8 +300,8 @@ packages:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
core-js-pure@3.37.0: core-js-pure@3.37.1:
resolution: {integrity: sha512-d3BrpyFr5eD4KcbRvQ3FTUx/KWmaDesr7+a3+1+P46IUnNoEt+oiLijPINZMEon7w9oGkIINWxrBAU9DEciwFQ==} resolution: {integrity: sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==}
data-uri-to-buffer@2.0.2: data-uri-to-buffer@2.0.2:
resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==} resolution: {integrity: sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==}
@@ -294,6 +327,10 @@ packages:
estree-walker@0.6.1: estree-walker@0.6.1:
resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
exit-hook@2.2.1: exit-hook@2.2.1:
resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -324,8 +361,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
hono@4.3.0: hono@4.3.6:
resolution: {integrity: sha512-rf9142VLQNMVBj+BjVLISgDWDxnJGUIuX39dvqcdySwr2gTsPfsqW1twWDUjfwQNWm9hEn40MpDu9RFGUN+e8A==} resolution: {integrity: sha512-2IqXwrxWF4tG2AR7b5tMYn+KEnWK8UvdC/NUSbOKWj/Kj11OJqel58FxyiXLK5CcKLiL8aGtTe4lkBKXyaHMBQ==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
is-binary-path@2.1.0: is-binary-path@2.1.0:
@@ -369,11 +406,15 @@ packages:
mimetext@3.0.24: mimetext@3.0.24:
resolution: {integrity: sha512-UdG1KVfcxeEfo6el91lzFG2WLLTm8DxSK/rosxx5H2Pjla50+DSsjTgr9BRAfAkbQWaxvzcaTO+bHK5ZrdKdfA==} resolution: {integrity: sha512-UdG1KVfcxeEfo6el91lzFG2WLLTm8DxSK/rosxx5H2Pjla50+DSsjTgr9BRAfAkbQWaxvzcaTO+bHK5ZrdKdfA==}
miniflare@3.20240419.0: miniflare@3.20240419.1:
resolution: {integrity: sha512-fIev1PP4H+fQp5FtvzHqRY2v5s+jxh/a0xAhvM5fBNXvxWX7Zod1OatXfXwYbse3hqO3KeVMhb0osVtrW0NwJg==} resolution: {integrity: sha512-Q9n0W07uUD/u0c/b03E4iogeXOAMjZnE3P7B5Yi8sPaZAx6TYWwjurGBja+Pg2yILN2iMaliEobfVyAKss33cA==}
engines: {node: '>=16.13'} engines: {node: '>=16.13'}
hasBin: true hasBin: true
mri@1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
ms@2.1.2: ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@@ -386,6 +427,15 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
node-fetch@2.7.0:
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
engines: {node: 4.x || >=6.0.0}
peerDependencies:
encoding: ^0.1.0
peerDependenciesMeta:
encoding:
optional: true
node-forge@1.3.1: node-forge@1.3.1:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'} engines: {node: '>= 6.13.0'}
@@ -394,6 +444,10 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
p-timeout@4.1.0:
resolution: {integrity: sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==}
engines: {node: '>=10'}
path-parse@1.0.7: path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
@@ -404,6 +458,9 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
postal-mime@2.2.5:
resolution: {integrity: sha512-6eTJf+B47JMdDuLF/4MBiGpTinxl0W8bA9CzrSoiQrNVRqK8Vhe59VrS6sXh2lG/lgo0bxpZFcWOF4Dv1FaSfg==}
printable-characters@1.0.42: printable-characters@1.0.42:
resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==}
@@ -432,6 +489,13 @@ packages:
rollup-pluginutils@2.8.2: rollup-pluginutils@2.8.2:
resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==}
safe-compare@1.1.4:
resolution: {integrity: sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==}
sandwich-stream@2.0.2:
resolution: {integrity: sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==}
engines: {node: '>= 0.10'}
selfsigned@2.4.1: selfsigned@2.4.1:
resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -455,10 +519,18 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
telegraf@4.16.3:
resolution: {integrity: sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==}
engines: {node: ^12.20.0 || >=14.13.1}
hasBin: true
to-regex-range@5.0.1: to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
tslib@2.6.2: tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
@@ -469,13 +541,19 @@ packages:
resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==}
engines: {node: '>=14.0'} engines: {node: '>=14.0'}
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
workerd@1.20240419.0: workerd@1.20240419.0:
resolution: {integrity: sha512-9yV98KpkQgG+bdEsKEW8i1AYZgxns6NVSfdOVEB2Ue1pTMtIEYfUyqUE+O2amisRrfaC3Pw4EvjtTmVaoetfeg==} resolution: {integrity: sha512-9yV98KpkQgG+bdEsKEW8i1AYZgxns6NVSfdOVEB2Ue1pTMtIEYfUyqUE+O2amisRrfaC3Pw4EvjtTmVaoetfeg==}
engines: {node: '>=16'} engines: {node: '>=16'}
hasBin: true hasBin: true
wrangler@3.53.1: wrangler@3.55.0:
resolution: {integrity: sha512-bdMRQdHYdvowIwOhEMFkARIZUh56aDw7HLUZ/2JreBjj760osXE4Fc4L1TCkfRRBWgB6/LKF5LA4OcvORMYmHg==} resolution: {integrity: sha512-VhtCioKxOdVqkHa8jQ6C6bX3by2Ko0uM0DKzrA+6lBZvfDUlGDWSOPiG+1fOHBHj2JTVBntxWCztXP6L+Udr8w==}
engines: {node: '>=16.17.0'} engines: {node: '>=16.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -502,14 +580,14 @@ packages:
youch@3.3.3: youch@3.3.3:
resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==} resolution: {integrity: sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==}
zod@3.23.6: zod@3.23.8:
resolution: {integrity: sha512-RTHJlZhsRbuA8Hmp/iNL7jnfc4nZishjsanDAfEY1QpDQZCahUp3xDzl+zfweE9BklxMUcgBgS1b7Lvie/ZVwA==} resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
snapshots: snapshots:
'@babel/runtime-corejs3@7.24.5': '@babel/runtime-corejs3@7.24.5':
dependencies: dependencies:
core-js-pure: 3.37.0 core-js-pure: 3.37.1
regenerator-runtime: 0.14.1 regenerator-runtime: 0.14.1
'@babel/runtime@7.24.5': '@babel/runtime@7.24.5':
@@ -535,6 +613,8 @@ snapshots:
'@cloudflare/workerd-windows-64@1.20240419.0': '@cloudflare/workerd-windows-64@1.20240419.0':
optional: true optional: true
'@cloudflare/workers-types@4.20240512.0': {}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
@@ -626,14 +706,20 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
'@telegraf/types@7.1.0': {}
'@types/node-forge@1.3.11': '@types/node-forge@1.3.11':
dependencies: dependencies:
'@types/node': 20.12.8 '@types/node': 20.12.12
'@types/node@20.12.8': '@types/node@20.12.12':
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
acorn-walk@8.3.2: {} acorn-walk@8.3.2: {}
acorn@8.11.3: {} acorn@8.11.3: {}
@@ -655,6 +741,15 @@ snapshots:
dependencies: dependencies:
fill-range: 7.0.1 fill-range: 7.0.1
buffer-alloc-unsafe@1.1.0: {}
buffer-alloc@1.2.0:
dependencies:
buffer-alloc-unsafe: 1.1.0
buffer-fill: 1.0.0
buffer-fill@1.0.0: {}
capnp-ts@0.7.0: capnp-ts@0.7.0:
dependencies: dependencies:
debug: 4.3.4 debug: 4.3.4
@@ -676,7 +771,7 @@ snapshots:
cookie@0.5.0: {} cookie@0.5.0: {}
core-js-pure@3.37.0: {} core-js-pure@3.37.1: {}
data-uri-to-buffer@2.0.2: {} data-uri-to-buffer@2.0.2: {}
@@ -713,6 +808,8 @@ snapshots:
estree-walker@0.6.1: {} estree-walker@0.6.1: {}
event-target-shim@5.0.1: {}
exit-hook@2.2.1: {} exit-hook@2.2.1: {}
fill-range@7.0.1: fill-range@7.0.1:
@@ -739,7 +836,7 @@ snapshots:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
hono@4.3.0: {} hono@4.3.6: {}
is-binary-path@2.1.0: is-binary-path@2.1.0:
dependencies: dependencies:
@@ -778,7 +875,7 @@ snapshots:
js-base64: 3.7.7 js-base64: 3.7.7
mime-types: 2.1.35 mime-types: 2.1.35
miniflare@3.20240419.0: miniflare@3.20240419.1:
dependencies: dependencies:
'@cspotcode/source-map-support': 0.8.1 '@cspotcode/source-map-support': 0.8.1
acorn: 8.11.3 acorn: 8.11.3
@@ -791,28 +888,38 @@ snapshots:
workerd: 1.20240419.0 workerd: 1.20240419.0
ws: 8.17.0 ws: 8.17.0
youch: 3.3.3 youch: 3.3.3
zod: 3.23.6 zod: 3.23.8
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- supports-color - supports-color
- utf-8-validate - utf-8-validate
mri@1.2.0: {}
ms@2.1.2: {} ms@2.1.2: {}
mustache@4.2.0: {} mustache@4.2.0: {}
nanoid@3.3.7: {} nanoid@3.3.7: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
node-forge@1.3.1: {} node-forge@1.3.1: {}
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
p-timeout@4.1.0: {}
path-parse@1.0.7: {} path-parse@1.0.7: {}
path-to-regexp@6.2.2: {} path-to-regexp@6.2.2: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
postal-mime@2.2.5: {}
printable-characters@1.0.42: {} printable-characters@1.0.42: {}
readdirp@3.6.0: readdirp@3.6.0:
@@ -843,6 +950,12 @@ snapshots:
dependencies: dependencies:
estree-walker: 0.6.1 estree-walker: 0.6.1
safe-compare@1.1.4:
dependencies:
buffer-alloc: 1.2.0
sandwich-stream@2.0.2: {}
selfsigned@2.4.1: selfsigned@2.4.1:
dependencies: dependencies:
'@types/node-forge': 1.3.11 '@types/node-forge': 1.3.11
@@ -861,10 +974,26 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
telegraf@4.16.3(patch_hash=pnqp5pf7vetkijrc5uah4r5w6m):
dependencies:
'@telegraf/types': 7.1.0
abort-controller: 3.0.0
debug: 4.3.4
mri: 1.2.0
node-fetch: 2.7.0
p-timeout: 4.1.0
safe-compare: 1.1.4
sandwich-stream: 2.0.2
transitivePeerDependencies:
- encoding
- supports-color
to-regex-range@5.0.1: to-regex-range@5.0.1:
dependencies: dependencies:
is-number: 7.0.0 is-number: 7.0.0
tr46@0.0.3: {}
tslib@2.6.2: {} tslib@2.6.2: {}
undici-types@5.26.5: {} undici-types@5.26.5: {}
@@ -873,6 +1002,13 @@ snapshots:
dependencies: dependencies:
'@fastify/busboy': 2.1.1 '@fastify/busboy': 2.1.1
webidl-conversions@3.0.1: {}
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
webidl-conversions: 3.0.1
workerd@1.20240419.0: workerd@1.20240419.0:
optionalDependencies: optionalDependencies:
'@cloudflare/workerd-darwin-64': 1.20240419.0 '@cloudflare/workerd-darwin-64': 1.20240419.0
@@ -881,7 +1017,7 @@ snapshots:
'@cloudflare/workerd-linux-arm64': 1.20240419.0 '@cloudflare/workerd-linux-arm64': 1.20240419.0
'@cloudflare/workerd-windows-64': 1.20240419.0 '@cloudflare/workerd-windows-64': 1.20240419.0
wrangler@3.53.1: wrangler@3.55.0(@cloudflare/workers-types@4.20240512.0):
dependencies: dependencies:
'@cloudflare/kv-asset-handler': 0.3.2 '@cloudflare/kv-asset-handler': 0.3.2
'@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19)
@@ -889,7 +1025,7 @@ snapshots:
blake3-wasm: 2.1.5 blake3-wasm: 2.1.5
chokidar: 3.6.0 chokidar: 3.6.0
esbuild: 0.17.19 esbuild: 0.17.19
miniflare: 3.20240419.0 miniflare: 3.20240419.1
nanoid: 3.3.7 nanoid: 3.3.7
path-to-regexp: 6.2.2 path-to-regexp: 6.2.2
resolve: 1.22.8 resolve: 1.22.8
@@ -898,6 +1034,7 @@ snapshots:
source-map: 0.6.1 source-map: 0.6.1
xxhash-wasm: 1.0.2 xxhash-wasm: 1.0.2
optionalDependencies: optionalDependencies:
'@cloudflare/workers-types': 4.20240512.0
fsevents: 2.3.3 fsevents: 2.3.3
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@@ -914,4 +1051,4 @@ snapshots:
mustache: 4.2.0 mustache: 4.2.0
stacktracey: 2.1.8 stacktracey: 2.1.8
zod@3.23.6: {} zod@3.23.8: {}
+6 -1
View File
@@ -36,7 +36,12 @@ api.post('/admin/new_address', async (c) => {
if (!name) { if (!name) {
return c.text("Please provide a name", 400) return c.text("Please provide a name", 400)
} }
return newAddress(c, name, domain, enablePrefix); try {
const res = await newAddress(c, name, domain, enablePrefix);
return c.json(res);
} catch (e) {
return c.text(`Failed create address: ${e.message}`, 400)
}
}) })
api.delete('/admin/delete_address/:id', async (c) => { api.delete('/admin/delete_address/:id', async (c) => {
+9 -12
View File
@@ -7,14 +7,14 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
name = name.replace(/[^a-zA-Z0-9.]/g, '') name = name.replace(/[^a-zA-Z0-9.]/g, '')
// check name length // check name length
if (name.length < 0) { if (name.length < 0) {
return c.text("Name too short", 400) throw new Error("Name too short")
} }
// create address // create address
if (enablePrefix) { if (enablePrefix) {
name = getStringValue(c.env.PREFIX) + name; name = getStringValue(c.env.PREFIX) + name;
} }
if (name.length >= 30) { if (name.length >= 30) {
return c.text("Name too long (max 30)", 400) throw new Error("Name too long (max 30)")
} }
// check domain, generate random domain // check domain, generate random domain
const domains = getDomains(c); const domains = getDomains(c);
@@ -28,30 +28,27 @@ export const newAddress = async (c, name, domain, enablePrefix) => {
`INSERT INTO address(name) VALUES(?)` `INSERT INTO address(name) VALUES(?)`
).bind(name).run(); ).bind(name).run();
if (!success) { if (!success) {
return c.text("Failed to create address", 500) throw new Error("Failed to create address")
} }
} catch (e) { } catch (e) {
if (e.message && e.message.includes("UNIQUE")) { if (e.message && e.message.includes("UNIQUE")) {
return c.text("Address already exists, please retry a new address", 400) throw new Error("Address already exists")
} }
return c.text("Failed to create address", 500) throw new Error("Failed to create address")
} }
let address_id = 0; let address_id = 0;
try {
address_id = await c.env.DB.prepare( address_id = await c.env.DB.prepare(
`SELECT id FROM address where name = ?` `SELECT id FROM address where name = ?`
).bind(name).first("id"); ).bind(name).first("id");
} catch (error) {
console.log(error);
}
// create jwt // create jwt
const jwt = await Jwt.sign({ const jwt = await Jwt.sign({
address: name, address: name,
address_id: address_id address_id: address_id
}, c.env.JWT_SECRET, "HS256") }, c.env.JWT_SECRET, "HS256")
return c.json({ return {
jwt: jwt jwt: jwt,
}) address: name,
}
} }
export const cleanup = async (c, cleanType, cleanDays) => { export const cleanup = async (c, cleanType, cleanDays) => {
@@ -1,7 +1,12 @@
export const CONSTANTS = { export const CONSTANTS = {
VERSION: 'v0.4.2', VERSION: 'v0.4.2',
// DB settings
ADDRESS_BLOCK_LIST_KEY: 'address_block_list', ADDRESS_BLOCK_LIST_KEY: 'address_block_list',
SEND_BLOCK_LIST_KEY: 'send_block_list', SEND_BLOCK_LIST_KEY: 'send_block_list',
AUTO_CLEANUP_KEY: 'auto_cleanup', AUTO_CLEANUP_KEY: 'auto_cleanup',
USER_SETTINGS_KEY: 'user_settings', USER_SETTINGS_KEY: 'user_settings',
// KV
TG_KV_PREFIX: "temp-mail-telegram"
} }
+10
View File
@@ -1,5 +1,6 @@
import { createMimeMessage } from "mimetext"; import { createMimeMessage } from "mimetext";
import { getBooleanValue } from "./utils"; import { getBooleanValue } from "./utils";
import { sendMailToTelegram } from "./telegram_api";
async function email(message, env, ctx) { async function email(message, env, ctx) {
if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => message.from.includes(word))) { if (env.BLACK_LIST && env.BLACK_LIST.split(",").some(word => message.from.includes(word))) {
@@ -20,6 +21,15 @@ async function email(message, env, ctx) {
console.log(`Failed save message from ${message.from} to ${message.to}`); console.log(`Failed save message from ${message.from} to ${message.to}`);
} }
// send email to telegram
try {
await sendMailToTelegram({
env: env,
}, message.to, rawEmail);
} catch (error) {
console.log("send mail to telegram error", error);
}
// auto reply email // auto reply email
if (getBooleanValue(env.ENABLE_AUTO_REPLY)) { if (getBooleanValue(env.ENABLE_AUTO_REPLY)) {
try { try {
+6 -1
View File
@@ -106,7 +106,12 @@ api.post('/api/new_address', async (c) => {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
return newAddress(c, name, domain, true); try {
const res = await newAddress(c, name, domain, true);
return c.json(res);
} catch (e) {
return c.text(`Failed create address: ${e.message}`, 400)
}
}) })
api.delete('/api/delete_address', async (c) => { api.delete('/api/delete_address', async (c) => {
+49
View File
@@ -0,0 +1,49 @@
import { Hono, Context } from 'hono'
import { ServerResponse } from 'node:http'
import { Writable } from 'node:stream'
import { newTelegramBot, initTelegramBotCommands, sendMailToTelegram } from './telegram'
export const api = new Hono()
export { sendMailToTelegram }
api.post("/telegram/webhook", async (c: Context) => {
const token = c.env.TELEGRAM_BOT_TOKEN;
const bot = newTelegramBot(c, token);
let body = null;
const res = new Writable();
Object.assign(res, {
headersSent: false,
setHeader: (name: string, value: string) => c.header(name, value),
end: (data: any) => body = data,
});
const reqJson = await c.req.json();
await bot.handleUpdate(reqJson, res as ServerResponse);
return c.body(body);
});
api.post("/admin/telegram/init", async (c: Context) => {
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
return c.text("TELEGRAM_BOT_TOKEN and KV are required", 400);
}
const domain = new URL(c.req.url).host;
const token = c.env.TELEGRAM_BOT_TOKEN;
const webhookUrl = `https://${domain}/telegram/webhook`;
console.log(`setting webhook to ${webhookUrl}`);
const bot = newTelegramBot(c, token);
await bot.telegram.setWebhook(webhookUrl)
await initTelegramBotCommands(bot);
return c.json({
message: "webhook set successfully",
});
});
api.get("/admin/telegram/status", async (c: Context) => {
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
return c.text("TELEGRAM_BOT_TOKEN and KV are required", 400);
}
const token = c.env.TELEGRAM_BOT_TOKEN;
const bot = newTelegramBot(c, token);
const info = await bot.telegram.getWebhookInfo()
const commands = await bot.telegram.getMyCommands()
return c.json({ info, commands });
});
+269
View File
@@ -0,0 +1,269 @@
import { Context } from "hono";
import { Jwt } from 'hono/utils/jwt'
import { Telegraf, Context as TgContext, Markup } from "telegraf";
import { callbackQuery } from "telegraf/filters";
import PostalMime from 'postal-mime';
import { CONSTANTS } from "../constants";
// @ts-ignore
import { getIntValue, getDomains, getStringValue } from '../utils';
// @ts-ignore
import { newAddress } from '../common'
const COMMANDS = [
{
command: "start",
description: "开始使用"
},
{
command: "new",
description: "新建邮箱地址, 如果要自定义邮箱地址, 请输入 /new <name>@<domain>, name [a-zA-Z0-9.] 有效"
},
{
command: "address",
description: "查看邮箱地址列表"
},
{
command: "bind",
description: "绑定邮箱地址, 请输入 /bind <邮箱地址凭证>"
},
{
command: "mails",
description: "查看邮件, 请输入 /mails <邮箱地址>, 不输入地址默认查看第一个地址"
},
]
export function newTelegramBot(c: Context, token: string): Telegraf {
const bot = new Telegraf(token);
bot.command("start", async (ctx: TgContext) => {
if (ctx.chat?.type !== "private") {
return await ctx.reply("请在私聊中使用");
}
const prefix = getStringValue(c.env.PREFIX)
const domains = getDomains(c);
return await ctx.reply(
"欢迎使用本机器人\n\n"
+ (prefix ? `当前已启用前缀: ${prefix}\n` : '')
+ "新建邮箱地址, 如果要自定义邮箱地址, "
+ "请输入 /new <name>@<domain>, name [a-zA-Z0-9.] 有效\n"
+ `当前可用域名: ${JSON.stringify(domains)}\n`
+ "请使用以下命令:\n"
+ COMMANDS.map(c => `/${c.command}: ${c.description}`).join("\n")
);
});
bot.command("new", async (ctx: TgContext) => {
if (ctx.chat?.type !== "private") {
return await ctx.reply("请在私聊中使用");
}
const userId = ctx?.message?.from?.id;
if (!userId) {
return await ctx.reply("无法获取用户信息");
}
try {
// @ts-ignore
const address = ctx?.message?.text.slice("/new".length).trim();
if (!address) {
return await ctx.reply("请输入邮箱地址");
}
const [name, domain] = address.includes("@") ? address.split("@") : [address, null];
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
return await ctx.reply("绑定地址数量已达上限");
}
const res = await newAddress(c, name, domain, true);
// for mail push to telegram
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, res.jwt]));
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${res.address}`, userId);
return await ctx.reply(`创建地址成功:\n`
+ `地址: ${res.address}\n`
+ `凭证: ${res.jwt}\n`
);
} catch (e) {
return await ctx.reply(`创建地址失败: ${(e as Error).message}`);
}
});
bot.command("bind", async (ctx: TgContext) => {
if (ctx.chat?.type !== "private") {
return await ctx.reply("请在私聊中使用");
}
const userId = ctx?.message?.from?.id;
if (!userId) {
return await ctx.reply("无法获取用户信息");
}
try {
// @ts-ignore
const jwt = ctx?.message?.text.slice("/bind".length).trim();
if (!jwt) {
return await ctx.reply("请输入凭证");
}
const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
if (!address) {
return await ctx.reply("凭证无效");
}
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
if (jwtList.length >= getIntValue(c.env.TG_MAX_ADDRESS, 5)) {
return await ctx.reply("绑定地址数量已达上限");
}
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, JSON.stringify([...jwtList, jwt]));
// for mail push to telegram
await c.env.KV.put(`${CONSTANTS.TG_KV_PREFIX}:${address}`, userId);
return await ctx.reply(`绑定成功:\n`
+ `地址: ${address}`
);
}
catch (e) {
return await ctx.reply(`绑定失败: ${(e as Error).message}`);
}
});
bot.command("address", async (ctx: TgContext) => {
if (ctx.chat?.type !== "private") {
return await ctx.reply("请在私聊中使用");
}
const userId = ctx?.message?.from?.id;
if (!userId) {
return await ctx.reply("无法获取用户信息");
}
try {
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
const addressList = [];
for (const jwt of jwtList) {
try {
const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
addressList.push(address);
} catch (e) {
addressList.push("此凭证无效");
continue;
}
}
return await ctx.reply(`地址列表:\n\n`
+ addressList.map(a => `地址: ${a}`).join("\n")
);
} catch (e) {
return await ctx.reply(`获取地址列表失败: ${(e as Error).message}`);
}
});
const queryMail = async (ctx: TgContext, queryAddress: string, mailIndex: number, edit: boolean) => {
const userId = ctx?.message?.from?.id || ctx.callbackQuery?.message?.chat?.id;
if (!userId) {
return await ctx.reply("无法获取用户信息");
}
const jwtList = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${userId}`, { type: 'json' }) || [];
const addressList = [];
for (const jwt of jwtList) {
try {
const { address } = await Jwt.verify(jwt, c.env.JWT_SECRET, "HS256");
addressList.push(address);
} catch (e) {
addressList.push("此凭证无效");
continue;
}
}
if (!queryAddress && addressList.length > 0) {
queryAddress = addressList[0];
}
if (!addressList.includes(queryAddress)) {
return await ctx.reply(`未绑定此地址 ${queryAddress}`);
}
const raw = await c.env.DB.prepare(
`SELECT * FROM raw_mails where address = ? `
+ ` order by id desc limit 1 offset ?`
).bind(
queryAddress, mailIndex
).first("raw");
const { mail } = await parseMail(raw);
if (edit) {
return await ctx.editMessageText(mail || "无邮件",
{
...Markup.inlineKeyboard([
Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
])
},
);
}
return await ctx.reply(mail || "无邮件",
{
...Markup.inlineKeyboard([
Markup.button.callback("上一条", `mail_${queryAddress}_${mailIndex - 1}`, mailIndex <= 0),
Markup.button.callback("下一条", `mail_${queryAddress}_${mailIndex + 1}`, !raw),
])
},
);
}
bot.command("mails", async ctx => {
try {
const queryAddress = ctx?.message?.text.slice("/mails".length).trim();
return await queryMail(ctx, queryAddress, 0, false);
} catch (e) {
return await ctx.reply(`获取邮件失败: ${(e as Error).message}`);
}
});
bot.on(callbackQuery("data"), async ctx => {
// Use ctx.callbackQuery.data
try {
const data = ctx.callbackQuery.data;
if (data && data.startsWith("mail_") && data.split("_").length === 3) {
const [_, queryAddress, mailIndex] = data.split("_");
await queryMail(ctx, queryAddress, parseInt(mailIndex), true);
}
}
catch (e) {
console.log(`获取邮件失败: ${(e as Error).message}`, e);
return await ctx.answerCbQuery(`获取邮件失败: ${(e as Error).message}`);
}
await ctx.answerCbQuery();
});
return bot;
}
export async function initTelegramBotCommands(bot: Telegraf) {
bot.telegram.sendMessage
await bot.telegram.setMyCommands(COMMANDS);
}
const parseMail = async (raw_mail: string) => {
if (!raw_mail) {
return {};
}
try {
const parsedEmail = await PostalMime.parse(raw_mail);
return {
isHtml: false,
mail: `From: ${parsedEmail.from ? `${parsedEmail.from.name}[${parsedEmail.from.address}]` : "无发件人"}\n`
+ `To: ${parsedEmail.to?.map(t => `${t.name}[${t.address}]`).join(" ")}\n`
+ `Subject: ${parsedEmail.subject}\n`
+ `Date: ${parsedEmail.date}\n`
+ `Content:\n${parsedEmail.text?.substring(0, 100) || "解析失败"}`
};
} catch (e) {
return {
isHtml: false,
mail: `解析邮件失败: ${(e as Error).message}`
};
}
}
export async function sendMailToTelegram(c: Context, address: string, raw_mail: string) {
if (!c.env.TELEGRAM_BOT_TOKEN || !c.env.KV) {
return;
}
const userId = await c.env.KV.get(`${CONSTANTS.TG_KV_PREFIX}:${address}`);
if (!userId) {
return;
}
const { mail } = await parseMail(raw_mail);
if (!mail) {
return;
}
const bot = newTelegramBot(c, c.env.TELEGRAM_BOT_TOKEN);
await bot.telegram.sendMessage(userId, mail);
}
+15 -1
View File
@@ -47,10 +47,24 @@ export const getBooleanValue = (value) => {
if (typeof value === "string") { if (typeof value === "string") {
return value === "true"; return value === "true";
} }
console.error("Invalid boolean value", value); console.error(`Failed to parse boolean value: ${value}`);
return false; return false;
} }
export const getIntValue = (value, defaultValue = 0) => {
if (typeof value === "number") {
return value;
}
if (typeof value === "string") {
try {
return parseInt(value);
} catch (e) {
console.error(`Failed to parse int value: ${value}`);
}
}
return defaultValue;
}
export const getDomains = (c) => { export const getDomains = (c) => {
if (!c.env.DOMAINS) { if (!c.env.DOMAINS) {
return []; return [];
+2
View File
@@ -9,6 +9,7 @@ import { api as userApi } from './user_api';
import { api as adminApi } from './admin_api'; import { api as adminApi } from './admin_api';
import { api as apiV1 } from './deprecated'; import { api as apiV1 } from './deprecated';
import { api as apiSendMail } from './mails_api/send_mail_api' import { api as apiSendMail } from './mails_api/send_mail_api'
import { api as telegramApi } from './telegram_api'
import { email } from './email'; import { email } from './email';
import { scheduled } from './scheduled'; import { scheduled } from './scheduled';
@@ -107,6 +108,7 @@ app.route('/', userApi)
app.route('/', adminApi) app.route('/', adminApi)
app.route('/', apiV1) app.route('/', apiV1)
app.route('/', apiSendMail) app.route('/', apiSendMail)
app.route('/', telegramApi)
app.get('/', async c => c.text("OK")) app.get('/', async c => c.text("OK"))
app.get('/health_check', async c => c.text("OK")) app.get('/health_check', async c => c.text("OK"))
+15
View File
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"skipLibCheck": true,
"lib": [
"ESNext"
],
"types": [
"@cloudflare/workers-types"
]
},
}
+2
View File
@@ -38,6 +38,8 @@ ENABLE_AUTO_REPLY = false
# dkim config # dkim config
# DKIM_SELECTOR = "" # DKIM_SELECTOR = ""
# DKIM_PRIVATE_KEY = "" # DKIM_PRIVATE_KEY = ""
# telegram bot
# TG_MAX_ACCOUNTS = 5
[[d1_databases]] [[d1_databases]]
binding = "DB" binding = "DB"