mirror of
https://github.com/ershisan99/flashcards-admin-bot.git
synced 2025-12-17 20:59:26 +00:00
initial commit
This commit is contained in:
46
src/generate-groups.ts
Normal file
46
src/generate-groups.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import fs from 'fs';
|
||||
import { Data, UserData } from './types.js';
|
||||
|
||||
generateGroups();
|
||||
|
||||
function generateGroups() {
|
||||
const data = fs.readFileSync('./data.json', 'utf8');
|
||||
const dataObj = JSON.parse(data) as Data;
|
||||
const groupedUsers = groupByTime(dataObj);
|
||||
fs.writeFileSync('./groups.json', JSON.stringify(groupedUsers), 'utf8');
|
||||
generateJsFile(groupedUsers);
|
||||
}
|
||||
|
||||
function generateJsFile(users: UserData[][]) {
|
||||
const template = fs.readFileSync('./index.js.template', 'utf8');
|
||||
const usersString = JSON.stringify(users);
|
||||
const jsFile = template.replace('/* USERS */', usersString);
|
||||
|
||||
fs.writeFileSync('./index.js', jsFile, 'utf8');
|
||||
}
|
||||
function groupByTime(users: Record<string, UserData>): UserData[][] {
|
||||
const allUsers = Object.values(users);
|
||||
|
||||
// First, sort all users by their available time from low to high
|
||||
allUsers.sort((a, b) => a.availableTime.from - b.availableTime.from);
|
||||
|
||||
const groups: UserData[][] = [];
|
||||
let currentGroup: UserData[] = [];
|
||||
|
||||
allUsers.forEach((user) => {
|
||||
currentGroup.push(user);
|
||||
|
||||
// If the current group reaches 3 members, start a new group
|
||||
if (currentGroup.length === 3) {
|
||||
groups.push(currentGroup);
|
||||
currentGroup = []; // Reset for next group
|
||||
}
|
||||
});
|
||||
|
||||
// Add the last group if it has less than 3 members and is not empty
|
||||
if (currentGroup.length > 0) {
|
||||
groups.push(currentGroup);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
185
src/main.ts
Normal file
185
src/main.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { session, Telegraf } from 'telegraf';
|
||||
|
||||
import { Stage, WizardScene } from 'telegraf/scenes';
|
||||
import * as fs from 'fs';
|
||||
import { Data, UserData } from './types.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const BOT_TOKEN = process.env.BOT_TOKEN;
|
||||
|
||||
if (!BOT_TOKEN) {
|
||||
throw new Error('BOT_TOKEN is required!');
|
||||
}
|
||||
|
||||
const superWizard = new WizardScene<any>(
|
||||
'add_to_team_wizard',
|
||||
async (ctx) => {
|
||||
console.log('step 0');
|
||||
|
||||
await ctx.reply(
|
||||
'Введи свое имя и фамилию, пожалуйста. Например, Иван Иванов',
|
||||
);
|
||||
ctx.wizard.state.data = {};
|
||||
return ctx.wizard.next();
|
||||
},
|
||||
async (ctx) => {
|
||||
console.log('step 1');
|
||||
if (ctx.message?.text) {
|
||||
ctx.wizard.state.data.name = ctx.message.text;
|
||||
}
|
||||
await ctx.reply(
|
||||
'Сколько часов ты можешь уделять проекту в среднем в неделю?',
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'До 10',
|
||||
callback_data: JSON.stringify({ from: 0, to: 10 }),
|
||||
},
|
||||
{
|
||||
text: '10-20',
|
||||
callback_data: JSON.stringify({ from: 10, to: 20 }),
|
||||
},
|
||||
{
|
||||
text: '20-30',
|
||||
callback_data: JSON.stringify({ from: 20, to: 30 }),
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
text: '30-40',
|
||||
callback_data: JSON.stringify({ from: 30, to: 40 }),
|
||||
},
|
||||
{
|
||||
text: 'Более 40',
|
||||
callback_data: JSON.stringify({ from: 40, to: null }),
|
||||
},
|
||||
{
|
||||
text: '< Назад',
|
||||
callback_data: 'back',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
return ctx.wizard.next();
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.callbackQuery) {
|
||||
await ctx.reply(
|
||||
'Что-то пошло не так, попробуй выбрать один из предложенных вариантов',
|
||||
);
|
||||
return ctx.wizard.selectStep(2);
|
||||
}
|
||||
if (ctx.callbackQuery.data === 'back') {
|
||||
console.log('step 2');
|
||||
await ctx.answerCbQuery('back');
|
||||
await ctx.wizard.selectStep(ctx.wizard.cursor - 2);
|
||||
return ctx.wizard.steps[ctx.wizard.cursor](ctx);
|
||||
}
|
||||
const data = JSON.parse(ctx.callbackQuery.data);
|
||||
|
||||
ctx.wizard.state.data.availableTime = data;
|
||||
await ctx.answerCbQuery(
|
||||
`You are willing to dedicate from ${data.from} to ${
|
||||
data.to ?? 'infinity'
|
||||
} hours per week`,
|
||||
);
|
||||
await ctx.reply(
|
||||
`Перепроверь, пожалуйста, все ли верно. Если все верно, нажми "Да", если нет, нажми "Нет" или "< Назад"
|
||||
Имя: ${ctx.wizard.state.data.name}
|
||||
Время: ${data.from} - ${data.to ?? 'infinity'}
|
||||
`,
|
||||
{
|
||||
reply_markup: {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{
|
||||
text: 'Да',
|
||||
callback_data: 'yes',
|
||||
},
|
||||
{
|
||||
text: 'Нет',
|
||||
callback_data: 'no',
|
||||
},
|
||||
{
|
||||
text: '< Назад',
|
||||
callback_data: 'back',
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
return ctx.wizard.next();
|
||||
},
|
||||
async (ctx) => {
|
||||
if (!ctx.callbackQuery) {
|
||||
await ctx.reply(
|
||||
'Что-то пошло не так, попробуй выбрать один из предложенных вариантов',
|
||||
);
|
||||
return ctx.wizard.selectStep(3);
|
||||
}
|
||||
if (ctx.callbackQuery.data === 'back') {
|
||||
console.log('step 3');
|
||||
await ctx.answerCbQuery('back');
|
||||
await ctx.wizard.selectStep(ctx.wizard.cursor - 2);
|
||||
return ctx.wizard.steps[ctx.wizard.cursor](ctx);
|
||||
}
|
||||
if (ctx.callbackQuery.data === 'no') {
|
||||
console.log('step 0');
|
||||
await ctx.answerCbQuery('no');
|
||||
await ctx.wizard.selectStep(0);
|
||||
return ctx.wizard.steps[ctx.wizard.cursor](ctx);
|
||||
}
|
||||
if (ctx.callbackQuery.data === 'yes') {
|
||||
console.log('step 4');
|
||||
await ctx.answerCbQuery('yes');
|
||||
const data = ctx.wizard.state.data;
|
||||
const userId = ctx.from?.id;
|
||||
const chatId = ctx.callbackQuery.message.chat.id;
|
||||
const dataWithTgUsername = {
|
||||
...data,
|
||||
tgUsername: ctx.from?.username,
|
||||
chatId,
|
||||
};
|
||||
|
||||
updateData({ userId: userId?.toString() ?? '', ...dataWithTgUsername });
|
||||
await ctx.reply(
|
||||
`Спасибо за регистрацию! Мы свяжемся с тобой в ближайшее время.`,
|
||||
);
|
||||
return ctx.scene.leave();
|
||||
}
|
||||
await ctx.reply(
|
||||
'Что-то пошло не так, попробуй выбрать один из предложенных вариантов',
|
||||
);
|
||||
return ctx.wizard.selectStep(3);
|
||||
},
|
||||
);
|
||||
|
||||
const stage = new Stage([superWizard]);
|
||||
|
||||
const bot = new Telegraf(BOT_TOKEN);
|
||||
bot.use(session());
|
||||
bot.use(stage.middleware());
|
||||
bot.command('add_to_team', (ctx) => {
|
||||
// @ts-expect-error wtf
|
||||
ctx.scene.enter('add_to_team_wizard');
|
||||
});
|
||||
|
||||
void bot.launch();
|
||||
|
||||
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||||
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
||||
|
||||
function updateData({ ...rest }: UserData) {
|
||||
const data = fs.readFileSync('./data.json', 'utf8');
|
||||
const dataObj = JSON.parse(data) as Data;
|
||||
dataObj[rest.userId] = rest;
|
||||
const newDataStr = JSON.stringify(dataObj);
|
||||
fs.writeFileSync('./data.json', newDataStr, 'utf8');
|
||||
}
|
||||
60
src/send-group-info.ts
Normal file
60
src/send-group-info.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import dotenv from 'dotenv';
|
||||
import { Telegraf } from 'telegraf';
|
||||
import * as fs from 'fs';
|
||||
import { UserData } from './types.js';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const BOT_TOKEN = process.env.BOT_TOKEN;
|
||||
|
||||
if (!BOT_TOKEN) {
|
||||
throw new Error('BOT_TOKEN is required!');
|
||||
}
|
||||
|
||||
const bot = new Telegraf(BOT_TOKEN);
|
||||
|
||||
const data = fs.readFileSync('./groups.json', 'utf8');
|
||||
const userData = JSON.parse(data) as UserData[][];
|
||||
userData.forEach((group) => {
|
||||
group.forEach((user, i, arr) => {
|
||||
const otherUsers = group.filter((u) => u.userId !== user.userId);
|
||||
if (otherUsers.length === 0) {
|
||||
bot.telegram.sendMessage(
|
||||
user.chatId,
|
||||
'К сожалению, в твою группу не попал никто.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
let message = `Привет, ${user.name}! Твои напарники:\n\n${otherUsers
|
||||
.map(
|
||||
(u) =>
|
||||
`- <a href="tg://user?id=${u.userId}">${u.name}</a> @${
|
||||
u.tgUsername
|
||||
}. Время: ${getAvailableTime(
|
||||
u.availableTime.from,
|
||||
u.availableTime.to,
|
||||
)}`,
|
||||
)
|
||||
.join(' \n')}`;
|
||||
|
||||
if (i === 0) {
|
||||
message +=
|
||||
'\n\nТебя выбрали ответственным(ой) за первоначальную коммуникацию. Создай, пожалуйста, группу в телеграме и пригласи в нее своих напарников.';
|
||||
} else {
|
||||
message += `\n\n${arr[0].name} был(а) выбран(а) ответственным за первоначальную коммуникацию. Он(а) создаст группу в телеграме и пригласит в нее всех участников.`;
|
||||
}
|
||||
bot.telegram.sendMessage(user.chatId, message, { parse_mode: 'HTML' });
|
||||
});
|
||||
});
|
||||
|
||||
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||||
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
||||
|
||||
function getAvailableTime(from: number, to: number | null) {
|
||||
if (from === 0 && to === 10) return 'До 10 часов';
|
||||
if (from === 10 && to === 20) return '10-20 часов';
|
||||
if (from === 20 && to === 30) return '20-30 часов';
|
||||
if (from === 30 && to === 40) return '30-40 часов';
|
||||
if (from === 40) return 'Более 40 часов';
|
||||
return 'Unknown time';
|
||||
}
|
||||
9
src/types.ts
Normal file
9
src/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type UserData = {
|
||||
name: string;
|
||||
availableTime: { from: number; to: number | null };
|
||||
tgUsername: string;
|
||||
userId: string;
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
export type Data = Record<string, UserData>;
|
||||
Reference in New Issue
Block a user