add fastify server, add prepare-data endpoint (working)

This commit is contained in:
2024-05-25 13:22:55 +02:00
parent 4715cb25dc
commit d2763be27b
11 changed files with 3032 additions and 2065 deletions

View File

@@ -0,0 +1,49 @@
import { UserData } from '../types.js';
import { FmtString } from 'telegraf/format';
export function sendGroupInfo(
data: UserData[][],
sendMessage: (
chatId: number | string,
text: string | FmtString,
extra?: any,
) => void,
) {
data.forEach((group) => {
group.forEach((user, i, arr) => {
const otherUsers = group.filter((u) => u.userId !== user.userId);
if (otherUsers.length === 0) {
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} был(а) выбран(а) ответственным за первоначальную коммуникацию. Он(а) создаст группу в телеграме и пригласит в нее всех участников.`;
}
sendMessage(user.chatId, message, { parse_mode: 'HTML' });
});
});
}
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';
}

View File

@@ -0,0 +1,161 @@
import { Stage, WizardScene } from 'telegraf/scenes';
import { Data, UserData } from '../types.js';
import fs from 'fs';
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);
},
);
export const stage = new Stage([superWizard]);
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');
}

View File

@@ -1,9 +1,7 @@
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';
import { stage } from './bot/setup-wizard-stage.js';
dotenv.config();
@@ -13,156 +11,6 @@ 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());
@@ -175,11 +23,3 @@ 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');
}

View File

@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
import { Telegraf } from 'telegraf';
import * as fs from 'fs';
import { UserData } from './types.js';
import { sendGroupInfo } from './bot/send-group-info.js';
dotenv.config();
@@ -15,46 +16,7 @@ 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' });
});
});
sendGroupInfo(userData, bot.telegram.sendMessage);
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';
}

38
src/server.ts Normal file
View File

@@ -0,0 +1,38 @@
// Import the framework and instantiate it
import Fastify from 'fastify';
import fs from 'fs';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import path from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = path.join(__dirname, '../..');
const dataDir = path.join(rootDir, 'data');
const fastify = Fastify({
logger: true,
});
// Declare a route
fastify.get('/', async function handler(request, reply) {
return { hello: 'world' };
});
// Declare a route
fastify.get('/prepare-db', async function handler(request, reply) {
try {
fs.writeFileSync(path.join(dataDir, 'data.json'), '{}', 'utf8');
return reply.code(200).send('ok');
} catch (err) {
console.error(err);
return reply.code(500).send(err);
}
});
// Run the server!
try {
await fastify.listen({ port: 3000 });
} catch (err) {
fastify.log.error(err);
process.exit(1);
}