initial commit

This commit is contained in:
Anton 2024-02-01 21:34:53 +03:00
commit 806828b30e
7 changed files with 15158 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.vscode
node_modules
.env
.telegram
.tasks

14733
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

56
package.json Normal file
View File

@ -0,0 +1,56 @@
{
"name": "stats_bot",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "sh -ac '. ./.env; ts-node src/index'",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/chai": "4.2.11",
"@types/chai-as-promised": "7.1.2",
"@types/express": "^4.17.21",
"@types/mocha": "10.0.1",
"@types/node": "13.13.52",
"@types/prompt": "^1.1.8",
"@types/sinon": "9.0.4",
"@typescript-eslint/eslint-plugin": "5.60.1",
"@typescript-eslint/parser": "5.60.1",
"chai": "4.2.0",
"chai-as-promised": "7.1.1",
"clean-package": "2.2.0",
"eslint": "8.34.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-import-resolver-lerna": "2.0.0",
"eslint-import-resolver-typescript": "2.4.0",
"eslint-plugin-chai-friendly": "0.6.0",
"eslint-plugin-i18next": "6.0.0-2",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jest": "23.8.0",
"eslint-plugin-jsx-a11y": "6.7.1",
"eslint-plugin-mocha": "10.1.0",
"knip": "2.14.3",
"lerna": "^7.3.0",
"mocha": "10.2.0",
"mocha-each": "2.0.1",
"mocha-junit-reporter": "2.0.2",
"mocha-multi-reporters": "1.5.1",
"nyc": "15.1.0",
"sinon": "9.0.2",
"ts-mocha": "10.0.0",
"ts-node": "10.9.1",
"typescript": "4.9.5"
},
"dependencies": {
"input": "^1.0.1",
"mongodb": "^6.3.0",
"mongoose": "^8.1.1",
"prompt": "^1.3.0",
"telegraf": "^4.15.3",
"telegram": "^2.19.10"
}
}

83
src/bot.ts Normal file
View File

@ -0,0 +1,83 @@
import { Telegraf } from 'telegraf';
import { message } from 'telegraf/filters';
import { ChatFromGetChat } from 'telegraf/typings/core/types/typegram';
export type ChannelInfo = {
chatId: number,
memberCount: number,
};
export type MessageInfo = {
}
export class ChannelBot {
private readonly bot: Telegraf;
constructor(
private readonly token: string,
) {
this.bot = new Telegraf(
process.env.TELEGRAM_BOT_TOKEN ?? '',
);
}
public async connect(): Promise<void> {
process.once('SIGINT', () => this.bot.stop('SIGINT'));
process.once('SIGTERM', () => this.bot.stop('SIGTERM'));
this.bot.launch(
{
allowedUpdates: [
'chat_join_request',
'chat_member',
'channel_post',
'edited_channel_post',
'poll',
'poll_answer'
],
},
);
}
public async getChannelInfo(channelId: number): Promise<ChannelInfo> {
const memberCount = await this.bot.telegram.getChatMembersCount(channelId);
const info: ChatFromGetChat = await this.bot.telegram.getChat(channelId);
return {
chatId: info.id,
memberCount,
};
}
}
// bot.on('channel_post', async (ctx) => {
// console.log('channel_post');
// console.dir(ctx.channelPost.chat.id);
// 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'));

207
src/client.ts Normal file
View File

@ -0,0 +1,207 @@
import { Api, TelegramClient } from "telegram";
import { StringSession } from "telegram/sessions";
import prompt, { message } from 'prompt';
import { CustomMessage } from 'telegram/tl/custom/message';
export type MessageInfo = {
id: number;
type?: string;
text?: string;
link?: string;
date?: Date;
viewCount: number;
reactionCount: number;
action?: string;
forwardedFrom?: {
type: string;
chatId: number;
};
};
export type ParseMessageOptions = {
includeText: boolean;
includeLink: boolean;
statsOnly: boolean;
};
export class ChannelClient {
private client: TelegramClient | undefined;
constructor(
private readonly apiId: number,
private readonly apiHash: string,
private readonly session: string = '',
) {
}
public async connect(): Promise<void> {
this.client = new TelegramClient(
new StringSession(this.session),
this.apiId,
this.apiHash,
{
connectionRetries: 5,
}
);
prompt.start();
await this.client.start({
phoneNumber: async () => (await prompt.get(['phone'])).phone.toString(),
password: async () => (await prompt.get(['password'])).password.toString(),
phoneCode: async () =>
(await prompt.get(['code'])).code.toString(),
onError: (err) => console.log(err),
});
// const session = client.session.authKey?.;
// if (this.session !== session) {
// }
}
public async getChannelMessages(
channelId: number,
options: Partial<ParseMessageOptions> = {},
): Promise<MessageInfo[]> {
if (!this.client || !this.client.connected)
throw new Error('Client not connected');
const messages: MessageInfo[] = [];
// await client.sendMessage(channelId, { message: "Hello!" });
for await (const message of this.client.iterMessages(
channelId,
{
reverse: true,
}
)) {
messages.push(await this.parseMessage(message, options));
};
return messages;
}
public async getChannelUsers(
channelId: number,
): Promise<unknown> {
if (!this.client || !this.client.connected)
throw new Error('Client not connected');
const users: unknown[] = [];
let offset = 0;
try {
while (true) {
let cnt = 0;
for await (const user of this.client.iterParticipants(
channelId,
{
offset,
limit: 200,
}
)) {
users.push({
id: user.id.toJSNumber(),
username: user.username,
name: user.firstName + ' ' + user.lastName,
phone: user.phone ?? undefined,
isBot: user.bot,
status: user.status?.className.replace('UserStatus', '').toLowerCase(),
lastOnlineAt: (user.status as any)?.wasOnline
? new Date((user.status as any).wasOnline * 1000)
: undefined,
role: (user as any).participant?.className.replace('ChannelParticipant', '').toLowerCase(),
joinedAt: (user as any).participant?.date
? new Date((user as any).participant?.date * 1000)
: undefined,
});
cnt++;
};
offset += cnt;
if (cnt < 200)
break;
}
}
catch (err) {
console.error(err);
}
return users;
}
public async getChannelMessage(
channelId: number,
messageId: number,
options: Partial<ParseMessageOptions> = {},
): Promise<MessageInfo | undefined> {
if (!this.client || !this.client.connected)
throw new Error('Client not connected');
for await (const message of this.client.iterMessages(
channelId,
{
ids: messageId,
}
)) {
return await this.parseMessage(message, options);
};
}
private async parseMessage(
message: Api.Message,
options: Partial<ParseMessageOptions> = {},
): Promise<MessageInfo> {
const optionsFull: ParseMessageOptions = {
statsOnly: false,
includeText: false,
includeLink: false,
...options,
};
const msg: MessageInfo = {
id: message.id,
viewCount: message.views ?? 0,
reactionCount: message.reactions?.results.length ?? 0,
};
if (!optionsFull.statsOnly) {
msg.type = message.className;
msg.date = new Date(message.date * 1000);
if (optionsFull.includeText)
msg.text = message.text;
if (optionsFull.includeLink)
msg.link = `https://t.me/${(message.chat as Api.Channel)?.username}/${message.id}`;
if (msg.type === 'MessageService') {
msg.action = message.action?.className;
}
if (message.fwdFrom?.fromId) {
const origin = message.fwdFrom.fromId;
msg.forwardedFrom = {
type: origin.className,
chatId:
origin.className === 'PeerChannel'
? Number(`-100${origin.channelId.toString()}`) :
(
origin.className === 'PeerChat'
? -origin.chatId.toJSNumber() :
origin.userId.toJSNumber()
),
};
}
}
return msg;
}
}

40
src/index.ts Normal file
View File

@ -0,0 +1,40 @@
import { ChannelClient } from './client';
import { ChannelBot } from './bot';
(async () => {
const apiId = Number(process.env.TELEGRAM_API_ID ?? '0');
const apiHash = process.env.TELEGRAM_API_HASH ?? '';
const apiSession = process.env.TELEGRAM_API_SESSION ?? '';
const botToken = process.env.TELEGRAM_BOT_TOKEN ?? '';
const channelId = Number(process.env.TELEGRAM_CHANNEL_ID ?? '0');
const client = new ChannelClient(
apiId,
apiHash,
apiSession,
);
const bot = new ChannelBot(
botToken,
);
await bot.connect();
await client.connect();
const messages = await client.getChannelMessages(channelId, { statsOnly: true });
console.dir(messages);
const users = await client.getChannelUsers(channelId);
console.dir(users);
console.dir(await bot.getChannelInfo(channelId));
// for (const message of messages)
// console.dir(await client.getChannelMessage(channelId, message.id));
})()
.catch((err) => console.error);

34
src/storage.ts Normal file
View File

@ -0,0 +1,34 @@
import { Schema, model, connect } from 'mongoose';
// 1. Create an interface representing a document in MongoDB.
interface IUser {
name: string;
email: string;
avatar?: string;
}
// 2. Create a Schema corresponding to the document interface.
const userSchema = new Schema<IUser>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
// 3. Create a Model.
const User = model<IUser>('User', userSchema);
run().catch(err => console.log(err));
async function run() {
// 4. Connect to MongoDB
await connect('mongodb://127.0.0.1:27017/test');
const user = new User({
name: 'Bill',
email: 'bill@initech.com',
avatar: 'https://i.imgur.com/dM7Thhn.png'
});
await user.save();
console.log(user.email); // 'bill@initech.com'
}