Skip to content

Commit

Permalink
Merge pull request #3 from beilunyang/dev
Browse files Browse the repository at this point in the history
refactor: migrate to grammy and hono frameworks
  • Loading branch information
beilunyang authored Aug 11, 2024
2 parents 2b44db2 + 857d562 commit ee8fd8d
Show file tree
Hide file tree
Showing 13 changed files with 2,048 additions and 4,253 deletions.
1 change: 0 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ root = true

[*]
indent_style = tab
tab_width = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
Expand Down
7 changes: 2 additions & 5 deletions .github/workflows/deploy-CloudflareWorkers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ on:
push:
branches:
- master
pull_request:
repository_dispatch:


jobs:
deploy_worker:
runs-on: ubuntu-latest
Expand All @@ -30,8 +28,7 @@ jobs:
with:
node-version-file: package.json
- run: npm install
- run: npm run build
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy --var VERSION:${{ github.sha }}
command: deploy --var VERSION:${{ github.sha }} --minify src/index.ts
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,3 @@ dist

.dev.vars
.wrangler/
wrangler.toml
5,924 changes: 1,921 additions & 4,003 deletions package-lock.json

Large diffs are not rendered by default.

28 changes: 11 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
{
"name": "img-mom",
"version": "0.0.0",
"version": "0.1.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"build": "webpack --progress"
"dev": "wrangler dev src/index.ts",
"tunnel": "cloudflared tunnel --url http://127.0.0.1:8787",
"deploy": "wrangler deploy --minify src/index.ts"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230419.0",
"itty-router": "^3.0.12",
"node-polyfill-webpack-plugin": "^2.0.1",
"ts-loader": "^9.5.1",
"typescript": "^5.2.2",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"wrangler": "^3.28.1"
"@cloudflare/workers-types": "^4.20240806.0",
"cloudflared": "^0.5.3",
"typescript": "^5.5.2",
"wrangler": "^3.60.3"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.504.0",
"@cfworker/web": "^1.14.0",
"cfworker-middleware-telegraf": "^2.0.2",
"telegraf": "^4.15.0"
"@aws-sdk/client-s3": "^3.627.0",
"grammy": "^1.28.0",
"hono": "^4.5.4"
}
}
77 changes: 38 additions & 39 deletions src/bot.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,58 @@
import { Telegraf, Markup } from 'telegraf';
import { Bot, InlineKeyboard } from 'grammy/web';
import BackblazeB2 from './oss/backblazeB2';
import CloudflareR2 from './oss/cloudflareR2';
import { isAt, isInGroup, isOwner } from './utils';
import { isInPrivateChat, isOwner } from './utils';
import { OSSProvider } from './oss/interface';


const supportProviders: Record<string, new () => OSSProvider> = {
BackblazeB2,
CloudflareR2,
};

const bot = new Telegraf(self.TG_BOT_TOKEN);

bot.use(Telegraf.log());
const bot = new Bot(self.TG_BOT_TOKEN);

bot.use((ctx, next) => {
console.log(JSON.stringify(ctx.update, null, 2));
return next();
});

bot.command('start', (ctx) => ctx.reply('Welcome to use ImgMom'));

bot.command('help', async (ctx) => {
const commands = await ctx.api.getMyCommands();
const info = commands.reduce((acc, val) => `${acc}/${val.command} - ${val.description}\n`, '');
return ctx.reply(info);
});

bot.use(async (ctx, next) => {
if (['true', true].includes(self.TG_BOT_ALLOW_ANYONE)) {
// allow anyone reply photo message
// @ts-ignore
if (ctx.message?.photo) {
// allow anyone upload image
if (ctx.message?.photo || ctx.message?.document) {
return next();
}
}
const userName = ctx.from?.username;
if (!isOwner(userName)) {
console.log("You don't have permission");
return;
return ctx.reply("You don't have the relevant permissions, the img-mom bot code is open source and you can self-deploy it. \nVisit: https://github.com/beilunyang/img-mom");
}
return next();
await next();
})

bot.start((ctx) => ctx.reply('Welcome to use ImgMom'));

bot.help(async (ctx) => {
const commands = await ctx.getMyCommands();
const info = commands.reduce((acc, val) => `${acc}/${val.command} - ${val.description}\n`, '');
return ctx.reply(info);
});

bot.command('settings', async (ctx) => {
const keyboard = Markup.inlineKeyboard(
[
...Object.keys(supportProviders).map(provider => Markup.button.callback(provider, provider)),
Markup.button.callback('None', 'None')
]
);
const buttons = [
...Object.keys(supportProviders).map(provider => InlineKeyboard.text(provider, provider)),
InlineKeyboard.text('None', 'None')
]

const keyboard = InlineKeyboard.from([buttons]);

return ctx.reply('Setting up additional OSS provider', {
reply_markup: keyboard.reply_markup,
reply_markup: keyboard,
});
});

bot.on('callback_query', async (ctx) => {
bot.on('callback_query:data', async (ctx) => {
const provider = (ctx.callbackQuery as any).data;
const key = `oss_provider_${ctx.callbackQuery.from.username}`;

Expand All @@ -59,22 +61,19 @@ bot.on('callback_query', async (ctx) => {
} else {
self.KV_IMG_MOM.put(key, provider);
}

return ctx.reply(`Ok. Successfully set oss provider (${provider})`);
})

bot.on('photo', async (ctx) => {
if (isInGroup(ctx.message) && !isAt(ctx.message, ctx.botInfo.username)) {
console.log('You have to be @bot in group');
bot.on(['message:photo', 'message:document'], async (ctx) => {
if (!isInPrivateChat(ctx.message)) {
console.log('Can only be used in private chat');
return;
}

const photo = ctx.message.photo;
const uploadFile = photo[photo.length - 1];

const link = await ctx.telegram.getFileLink(uploadFile.file_id);
const file = await ctx.getFile();

const fileName = link.pathname.split('photos/').pop();
const tgImgUrl = `https://${self.host}/img/${fileName}`
const tgImgUrl = `https://${self.host}/img/${file.file_id}`;

if (!isOwner(ctx.message.from.username)) {
return ctx.reply(
Expand All @@ -95,16 +94,16 @@ bot.on('photo', async (ctx) => {
);
}

const res = await fetch(link);
const res = await fetch(`https://api.telegram.org/file/bot${self.TG_BOT_TOKEN}/${file.file_path}`);

if (!res.ok) {
return ctx.reply('Failed to upload file');
}

const fileType = link.pathname.split('.').pop() || '';
const fileType = file.file_path?.split('.').pop() || '';

try {
const filePath = await provider.uploadImage(await res.arrayBuffer(), uploadFile.file_unique_id, fileType);
const filePath = await provider.uploadImage(await res.arrayBuffer(), file.file_unique_id, fileType);
const fullUrl = provider.getURL(filePath);
return ctx.reply(
`Successfully uploaded image!\nTelegram:\n${tgImgUrl}\n${providerName}:\n${fullUrl}`
Expand Down
60 changes: 26 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,49 @@
import { Application, Router } from '@cfworker/web';
import createTelegrafMiddleware from 'cfworker-middleware-telegraf';
import { Hono } from 'hono';
import { webhookCallback } from 'grammy/web';
import bot from './bot';

const router = new Router();
const app = new Hono();

router.post('/bot', (ctx, next) => {
const webhookSecretToken = ctx.req.headers.get('X-Telegram-Bot-Api-Secret-Token');
if (self.TG_WEBHOOK_SECRET_TOKEN && webhookSecretToken !== self.TG_WEBHOOK_SECRET_TOKEN) {
ctx.res.status = 401;
return;
}
self.host = ctx.req.url.host;
app.post('/bot', async (ctx, next) => {
self.host = new URL(ctx.req.url).host;
return next();
}, createTelegrafMiddleware(bot));
}, webhookCallback(bot, 'hono', {
secretToken: self.TG_WEBHOOK_SECRET_TOKEN
}));

router.get('/setup', async (ctx) => {
app.get('/setup', async (ctx) => {
const webhookHost = await self.KV_IMG_MOM.get('webhookHost');
if (!webhookHost) {
const host = ctx.req.url.host;
const host = new URL(ctx.req.url).host;
const botUrl = `https://${host}/bot`;
await bot.telegram.setWebhook(botUrl, {
await bot.api.setWebhook(botUrl, {
secret_token: self.TG_WEBHOOK_SECRET_TOKEN,
});
await bot.telegram.setMyCommands([{
await bot.api.setMyCommands([{
command: '/settings',
description: 'Setting up the bot',
}]);
await self.KV_IMG_MOM.put('webhookHost', host);
ctx.res.body = `Webhook(${botUrl}) setup successful`;
return;
return ctx.text(`Webhook(${botUrl}) setup successful`);
}
ctx.res.status = 401;
return ctx.text('401 Unauthorized. Please visit ImgMom docs (https://github.com/beilunyang/img-mom)', 401)
});

router.get('/', (ctx) => {
ctx.res.body = "Hello ImgMom (https://github.com/beilunyang/img-mom)";
})

router.get('/img/:fileName', async (ctx) => {
const fileName = ctx.req.params.fileName;

const res = await fetch(`https://api.telegram.org/file/bot${self.TG_BOT_TOKEN}/photos/${fileName}`);
app.get('/img/:fileId', async (ctx) => {
const fileId = ctx.req.param('fileId');
const file = await bot.api.getFile(fileId)
const res = await fetch(`https://api.telegram.org/file/bot${self.TG_BOT_TOKEN}/${file.file_path}`);
if (!res.ok) {
ctx.res.status = 404;
return;
return ctx.text('404 Not Found. Please visit ImgMom docs (https://github.com/beilunyang/img-mom)', 404);
}

const fileType = fileName.split('.').pop();
ctx.res.type = `image/${fileType}`;
ctx.res.body = await res.arrayBuffer();
})

const fileType = file.file_path?.split('.').pop() ?? '';

return ctx.body(await res.arrayBuffer(), 200, {
'Content-Type': `image/${fileType}`
});
});

new Application().use(router.middleware).listen();
app.get('/', (ctx) => ctx.text('Hello ImgMom (https://github.com/beilunyang/img-mom)'));

app.fire()
17 changes: 5 additions & 12 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Message } from "grammy/types";

export const genDateDirName = () => {
const date = new Date();
const t = (val: number) => {
Expand All @@ -13,16 +15,7 @@ export const isOwner = (username?: string) => {
return username === self.TG_BOT_OWNER_USERNAME;
}

export const isInGroup = (message: any) => {
const chatType = message?.chat?.type;
return ['group', 'supergroup'].includes(chatType)
}

export const isAt = (message: any, botName: string) => {
const mentions = message?.caption_entities?.filter?.((entity: any) => entity?.type === 'mention');
const caption = message?.caption;
return !!mentions?.some?.((mention: any) => {
const atUserName = caption.slice(mention.offset, mention.length + mention.offset);
return atUserName === `@${botName}`;
})
export const isInPrivateChat = (message: Message) => {
const chatType = message.chat.type;
return chatType === 'private'
}
Loading

0 comments on commit ee8fd8d

Please sign in to comment.