added mongoose, debug-level
bot now saves chat_member tg event to db
This commit is contained in:
parent
806828b30e
commit
c94cb4e795
|
|
@ -9,6 +9,7 @@
|
|||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"debug-level": "^3.1.2",
|
||||
"input": "^1.0.1",
|
||||
"mongodb": "^6.3.0",
|
||||
"mongoose": "^8.1.1",
|
||||
|
|
@ -3046,7 +3047,6 @@
|
|||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
|
|
@ -3297,12 +3297,28 @@
|
|||
"tslib": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/asyncc": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asyncc/-/asyncc-2.0.6.tgz",
|
||||
"integrity": "sha512-m3nkCP6CKuLubt2vwqoOio8NmOJPjUL6dcaNNxqc9q4H2Rq9wNs+2UsIzBegiJzUtoyh9X9iBe4GIhqu1uOvqA==",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
|
||||
|
|
@ -3860,7 +3876,6 @@
|
|||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
|
|
@ -4103,7 +4118,6 @@
|
|||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
|
|
@ -4114,8 +4128,7 @@
|
|||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
|
|
@ -4491,6 +4504,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/debug-level": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/debug-level/-/debug-level-3.1.2.tgz",
|
||||
"integrity": "sha512-YyD8cnCVnTlN7la4/cKUWJq15clsrff8ZXbj7RND8dd0YAl+/SdBhtfemZUY3oU8oxXPfGirrPfPBPFaulLlGA==",
|
||||
"dependencies": {
|
||||
"asyncc": "^2.0.6",
|
||||
"chalk": "^4.1.2",
|
||||
"fast-safe-stringify": "^2.1.1",
|
||||
"flatstr": "^1.0.12",
|
||||
"map-lru": "^2.0.0",
|
||||
"ms": "^2.1.3",
|
||||
"sonic-boom": "^3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"debug": "^4.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/debug-level/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
|
|
@ -5703,6 +5741,11 @@
|
|||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz",
|
||||
|
|
@ -5885,6 +5928,11 @@
|
|||
"node": "^10.12.0 || >=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flatstr": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.12.tgz",
|
||||
"integrity": "sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw=="
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.2.9",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
|
||||
|
|
@ -6442,7 +6490,6 @@
|
|||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
|
|
@ -8565,6 +8612,14 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/map-lru": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/map-lru/-/map-lru-2.0.0.tgz",
|
||||
"integrity": "sha512-a5TlnsxvczXMY7U/U4P0b7GI3KSAonc+u2MQtWQS5L21K9UV4fYQbbgktj3eqP5ch04XtCOcoqR0OlILo906nA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/map-obj": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
|
||||
|
|
@ -12777,6 +12832,14 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/sonic-boom": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.0.tgz",
|
||||
"integrity": "sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA==",
|
||||
"dependencies": {
|
||||
"atomic-sleep": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sort-keys": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz",
|
||||
|
|
@ -13136,7 +13199,6 @@
|
|||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@
|
|||
"typescript": "4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug-level": "^3.1.2",
|
||||
"input": "^1.0.1",
|
||||
"mongodb": "^6.3.0",
|
||||
"mongoose": "^8.1.1",
|
||||
|
|
|
|||
68
src/bot.ts
68
src/bot.ts
|
|
@ -1,6 +1,9 @@
|
|||
import { Telegraf } from 'telegraf';
|
||||
import { Context, NarrowedContext, Telegraf } from 'telegraf';
|
||||
import { message } from 'telegraf/filters';
|
||||
import { ChatFromGetChat } from 'telegraf/typings/core/types/typegram';
|
||||
import { ChatFromGetChat, Update } from 'telegraf/typings/core/types/typegram';
|
||||
import { ioc } from './utils/ioc';
|
||||
import { AudienceChangeController, audienceChangeSymbol } from './controllers/audienceChange';
|
||||
import { log } from './utils/log';
|
||||
|
||||
export type ChannelInfo = {
|
||||
chatId: number,
|
||||
|
|
@ -9,7 +12,7 @@ export type ChannelInfo = {
|
|||
|
||||
export type MessageInfo = {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export class ChannelBot {
|
||||
private readonly bot: Telegraf;
|
||||
|
|
@ -29,6 +32,8 @@ export class ChannelBot {
|
|||
process.once('SIGINT', () => this.bot.stop('SIGINT'));
|
||||
process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
|
||||
|
||||
this.bot.on('chat_member', this.handleChatMemberUpdate.bind(this));
|
||||
|
||||
this.bot.launch(
|
||||
{
|
||||
allowedUpdates: [
|
||||
|
|
@ -43,6 +48,45 @@ export class ChannelBot {
|
|||
);
|
||||
}
|
||||
|
||||
private async handleChatMemberUpdate(ctx: NarrowedContext<Context<Update>, Update.ChatMemberUpdate>): Promise<void> {
|
||||
try {
|
||||
log.info('chat member change', JSON.stringify(ctx.chatMember));
|
||||
|
||||
const change = ctx.chatMember;
|
||||
const status = change.new_chat_member.status;
|
||||
const statusBefore = change.old_chat_member.status;
|
||||
const role = ['restricted', 'left', 'kicked'].includes(status)
|
||||
? statusBefore
|
||||
: status;
|
||||
|
||||
const ctrl = ioc.get<AudienceChangeController>(audienceChangeSymbol);
|
||||
|
||||
const chatMemberCount = await this.bot.telegram.getChatMembersCount(change.chat.id);
|
||||
|
||||
ctrl.add({
|
||||
channelId: change.chat.id,
|
||||
user: {
|
||||
id: change.new_chat_member.user.id,
|
||||
username: change.new_chat_member.user.username,
|
||||
firstName: change.new_chat_member.user.first_name,
|
||||
lastName: change.new_chat_member.user.last_name,
|
||||
isBot: change.new_chat_member.user.is_bot,
|
||||
isPremium: change.new_chat_member.user.is_premium ? true : false,
|
||||
role,
|
||||
},
|
||||
timestamp: new Date(change.date * 1000),
|
||||
status,
|
||||
statusBefore,
|
||||
invitedByLink: change.invite_link?.invite_link ?? undefined,
|
||||
|
||||
memberCountAfter: chatMemberCount,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
log.error('chat member change processing error', err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getChannelInfo(channelId: number): Promise<ChannelInfo> {
|
||||
|
||||
const memberCount = await this.bot.telegram.getChatMembersCount(channelId);
|
||||
|
|
@ -63,21 +107,3 @@ export class ChannelBot {
|
|||
// await sendChannelStats(ctx.chat.id);
|
||||
// });
|
||||
|
||||
// bot.on('new_chat_members', async (ctx) => {
|
||||
// console.log('new_chat_members');
|
||||
// console.dir(ctx.chatMember);
|
||||
|
||||
// await sendChannelStats(ctx.chat.id);
|
||||
// });
|
||||
|
||||
// bot.on('chat_member', async (ctx) => {
|
||||
// console.log('chat_member');
|
||||
// console.dir(ctx.chatMember);
|
||||
|
||||
// await sendChannelStats(ctx.chat.id);
|
||||
// });
|
||||
|
||||
// // bot.start((ctx) => ctx.reply('Welcome'));
|
||||
// // bot.help((ctx) => ctx.reply('Send me a sticker'));
|
||||
// // bot.on(message('sticker'), (ctx) => ctx.reply('👍'));
|
||||
// // bot.hears('hi', (ctx) => ctx.reply('Hey there'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
import { AudienceChangeModel, IAudienceChange } from '../model/audienceChange';
|
||||
import { IUser } from '../model/user';
|
||||
import { ioc } from '../utils/ioc';
|
||||
|
||||
export const audienceChangeSymbol = Symbol('audienceChange');
|
||||
|
||||
export class AudienceChangeController {
|
||||
public static register() {
|
||||
ioc.set(audienceChangeSymbol, new AudienceChangeController());
|
||||
}
|
||||
|
||||
public async add(change: IAudienceChange): Promise<void> {
|
||||
await AudienceChangeModel.create(change);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import mongoose from "mongoose";
|
||||
|
||||
export async function connect() {
|
||||
const dbUri = process.env.MONGO_URI ?? '';
|
||||
|
||||
await mongoose.connect(dbUri);
|
||||
console.log("Connected to db");
|
||||
}
|
||||
20
src/index.ts
20
src/index.ts
|
|
@ -1,8 +1,16 @@
|
|||
import { ChannelClient } from './client';
|
||||
import { ChannelBot } from './bot';
|
||||
import { connect } from './database';
|
||||
import { ioc } from './utils/ioc';
|
||||
import { AudienceChangeController } from './controllers/audienceChange';
|
||||
|
||||
(async () => {
|
||||
|
||||
try {
|
||||
await connect();
|
||||
|
||||
AudienceChangeController.register();
|
||||
|
||||
const apiId = Number(process.env.TELEGRAM_API_ID ?? '0');
|
||||
const apiHash = process.env.TELEGRAM_API_HASH ?? '';
|
||||
const apiSession = process.env.TELEGRAM_API_SESSION ?? '';
|
||||
|
|
@ -24,17 +32,27 @@ import { ChannelBot } from './bot';
|
|||
await bot.connect();
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
const messages = await client.getChannelMessages(channelId, { statsOnly: true });
|
||||
console.dir(messages);
|
||||
|
||||
const users = await client.getChannelUsers(channelId);
|
||||
|
||||
console.dir(users);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
console.dir(await bot.getChannelInfo(channelId));
|
||||
|
||||
// for (const message of messages)
|
||||
// console.dir(await client.getChannelMessage(channelId, message.id));
|
||||
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
})()
|
||||
.catch((err) => console.error);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import mongoose from "mongoose";
|
||||
|
||||
import { IUser, UserSchema } from './user';
|
||||
|
||||
export type IAudienceChange = {
|
||||
channelId: number,
|
||||
|
||||
user: IUser,
|
||||
|
||||
timestamp: Date,
|
||||
status: string,
|
||||
statusBefore: string,
|
||||
invitedByLink: string | undefined,
|
||||
|
||||
memberCountAfter: number;
|
||||
};
|
||||
|
||||
const AudienceChangeSchema = new mongoose.Schema<IAudienceChange>(
|
||||
{
|
||||
channelId: { type: Number, required: true },
|
||||
|
||||
user: UserSchema,
|
||||
|
||||
timestamp: Date,
|
||||
status: { type: String },
|
||||
statusBefore: { type: String },
|
||||
invitedByLink: { type: String },
|
||||
|
||||
memberCountAfter: { type: Number, required: true },
|
||||
},
|
||||
{
|
||||
collection: 'audienceChange',
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
export const AudienceChangeModel = mongoose.model<IAudienceChange>(
|
||||
'audienceChange',
|
||||
AudienceChangeSchema,
|
||||
);
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import mongoose from 'mongoose';
|
||||
|
||||
export type IUser = {
|
||||
id: number;
|
||||
username: string | undefined;
|
||||
firstName: string;
|
||||
lastName: string | undefined;
|
||||
isBot: boolean;
|
||||
isPremium: boolean;
|
||||
role: string; // 'member' | 'administrator' | 'creator';
|
||||
}
|
||||
|
||||
export const UserSchema = new mongoose.Schema<IUser>(
|
||||
{
|
||||
id: { type: Number, required: true },
|
||||
username: { type: String, required: true },
|
||||
firstName: { type: String },
|
||||
lastName: { type: String },
|
||||
isBot: { type: Boolean, required: true },
|
||||
isPremium: { type: Boolean, required: true },
|
||||
role: { type: String, required: true },
|
||||
}
|
||||
);
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
class IOC {
|
||||
private readonly items = new Map<Symbol, any>();
|
||||
|
||||
public set<T>(key: Symbol, obj: T): void {
|
||||
this.items.set(key, obj);
|
||||
}
|
||||
|
||||
public get<T>(key: Symbol): T {
|
||||
return this.items.get(key) as T;
|
||||
}
|
||||
|
||||
public create<A extends any[], T extends { new(...args: A): T; }>(
|
||||
key: Symbol,
|
||||
...args: A
|
||||
): T {
|
||||
const cls = this.get(key) as { new(...args: A): T; };
|
||||
return new cls(...args);
|
||||
}
|
||||
}
|
||||
|
||||
export const ioc = new IOC();
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import Log from 'debug-level';
|
||||
|
||||
export const log = new Log('');
|
||||
Loading…
Reference in New Issue