This commit is contained in:
2025-05-04 22:22:20 +03:00
parent cda54b1e95
commit ebd77a3e66
54 changed files with 1194 additions and 2580 deletions

View File

@@ -1,24 +1,45 @@
const crypto = require('crypto')
const express = require('express')
const multer = require('multer')
const db = require('../include/db')
const bot = require('./bot')
const fs = require('fs')
const cookieParser = require('cookie-parser')
const app = express.Router()
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 1_000_000 // 1mb
}
})
const sessions = {}
const emailCache = {} // key = email, value = code
const cache = {
// email -> code
register: {},
recovery: {},
'change-password': {},
'change-email': {},
'change-email2': {}
}
function checkEmail(email){
return String(email)
.toLowerCase()
.match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)
}
function sendEmail(email, subject, message) {
console.log(`${email} --> ${subject}: ${message}`)
}
function checkPassword(password) {
return password.length >= 8
}
app.use((req, res, next) => {
if (req.path == '/auth/email' || req.path == '/auth/telegram' || req.path == '/auth/register' || req.path == '/auth/logout')
const public = [
'/auth/email',
'/auth/telegram',
'/auth/email/register',
'/auth/email/recovery',
'/auth/logout'
]
if (public.includes(req.path))
return next()
const asid = req.query.asid || req.cookies.asid
@@ -59,10 +80,10 @@ app.post('/auth/email', (req, res, next) => {
app.post('/auth/telegram', (req, res, next) => {
let customer_id = db
.prepare(`select id from customers where is_blocked = 0 and telegram_id = :telegram_id`)
.prepare(`select id from customers where telegram_id = :telegram_id`)
.pluck(true)
.get(res.locals) || db
.prepare(`replace into customers (telegram_id, is_blocked) values (:telegram_id, 0) returning id`)
.prepare(`replace into customers (telegram_id) values (:telegram_id) returning id`)
.pluck(true)
.get(res.locals)
@@ -70,73 +91,198 @@ app.post('/auth/telegram', (req, res, next) => {
res.status(200).json({success: true})
})
app.get('/auth/logout', (req, res, next) => {
if (req.session?.asid)
delete sessions[req.session.asid]
res.setHeader('Set-Cookie', [`asid=; expired; httpOnly;path=/api/admin`])
res.status(200).json({success: true})
})
app.post('/auth/register', (req, res, next) => {
/*
Регистрация нового клиента выполняется за ТРИ последовательных вызова
1. Отравляется email. Если email корректный и уже неиспользуется, то сервер возвращает ОК и на указанный email отправляется код.
2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК.
3. Отправляется email + код из письма + желаемый пароль. Если все ОК, то сервер создает учетную запись и возвращает ОК.
*/
app.post('/auth/email/register', (req, res, next) => {
const email = String(req.body.email ?? '').trim()
const code = String(req.body.code ?? '').trim()
const password = String(req.body.password ?? '').trim()
if (email) {
const validateEmail = email => String(email).toLowerCase().match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/)
if (!validateEmail(email))
throw Error('INCORRECT_EMAIL::400')
const customer_id = db
.prepare('select id from customers where email = :email')
.pluck(true)
.get({email})
if (customer_id)
throw Error('USED_EMAIL::400')
}
if (email && !code) {
const code = Math.random().toString().substr(2, 4)
emailCache[email] = code
// To-Do: send email
console.log(`${email} => ${code}`)
}
if (email && code && !password) {
if (emailCache[email] != code)
throw Error('INCORRECT_CODE::400')
}
if (email && code && password) {
if (password.length < 8)
throw Error('INCORRECT_PASSWORD::400')
db
.prepare('insert into customers (email, password, is_blocked) values (:email, :password, 0)')
.run({email, password})
}
res.status(200).json({success: true})
})
const stepNo = email && !code ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1
if (stepNo == -1)
throw Error('BAD_STEP::400')
if (stepNo == 1) {
if (!checkEmail(email))
throw Error('INCORRECT_EMAIL::400')
const customer_id = db
.prepare('select id from customers where email = :email')
.pluck(true)
.get({email})
if (customer_id)
throw Error('USED_EMAIL::400')
const code = Math.random().toString().substr(2, 4)
cache.register[email] = code
sendEmail(email, 'REGISTER', `${email} => ${code}`)
}
if (stepNo == 2) {
if (cache.register[email] != code)
throw Error('INCORRECT_CODE::400')
}
if (stepNo == 3) {
if (!checkPassword(password))
throw Error('INCORRECT_PASSWORD::400')
db
.prepare('insert into customers (email, password) values (:email, :password)')
.run({email, password})
delete cache.register[email]
}
res.status(200).json({success: true})
})
/*
Смена email выполняется за ЧЕТЫРЕ последовательных вызовов
1. Отравляется пустой закпрос. Сервер на email пользователя из базы отправляет код.
2. Отправляется код из письма. Если указан корректный код, то сервер отвечает ОК.
3. Отправляется код из письма + новый email. Сервер отправляет код2 на новый email.
4. Отправлются оба кода и новый email. Если они проходят проверку, то сервер меняет email пользователя на новый и возвращает ОК.
*/
app.post('/auth/email/change-email', (req, res, next) => {
const email2 = String(req.body.email ?? '').trim()
const code = String(req.body.code ?? '').trim()
const code2 = String(req.body.code2 ?? '').trim()
const email = db
.prepare('select email from customers where id = :customer_id')
.pluck(true)
.get(res.locals)
const stepNo = !code ? 1 : code && !email ? 2 : code && email && !code2 ? 3 : code && email && code2 ? 4 : -1
if (stepNo == -1)
throw Error('BAD_STEP::400')
if (stepNo == 1) {
const code = Math.random().toString().substr(2, 4)
cache['change-email'][email] = code
sendEmail(email, 'CHANGE-EMAIL', `${email} => ${code}`)
}
if (stepNo == 2) {
if (cache['change-email'][email] != code)
throw Error('INCORRECT_CODE::400')
}
if (stepNo == 3) {
if (!checkEmail(email2))
throw Error('INCORRECT_EMAIL::400')
const code2 = Math.random().toString().substr(2, 4)
cache['change-email2'][email2] = code2
sendEmail(email2, 'CHANGE-EMAIL2', `${email2} => ${code2}`)
}
if (stepNo == 4) {
if (cache['change-email'][email] != code || cache['change-email2'][email2] != code2)
throw Error('INCORRECT_CODE::400')
const info = db
.prepare('update customers set email = :email where id = :customer_id')
.run(res.locals)
if (info.changes == 0)
throw Error('BAD_REQUEST::400')
delete cache['change-email'][email]
delete cache['change-email2'][email2]
}
res.status(200).json({success: true})
})
/*
Смена пароля/восстановление доступа выполняется за ТРИ последовательных вызова
1. Отравляется пустой закпрос для смены запоса и email, в случае восстановления доступа. Сервер на email отправляет код.
2. Отправляется email + код из письма. Если указан корректный код, то сервер отвечает ОК.
3. Отправляется email + код из письма + новый пароль. Сервер изменяет пароль и возвращает ОК.
*/
app.post('/auth/email/:action(change-password|recovery)', (req, res, next) => {
const code = String(req.body.code ?? '').trim()
const password = String(req.body.password)
const action = req.params.action
const email = action == 'change-password' ? db
.prepare('select email from customers where id = :customer_id')
.pluck(true)
.get(res.locals) :
String(req.body.email ?? '').trim()
const stepNo = action == 'change-password' ?
(!code && !password ? 1 : code && !password ? 2 : code && password ? 3 : -1) :
(!email && !code && !password ? 1 : email && code && !password ? 2 : email && code && password ? 3 : -1)
if (stepNo == -1)
throw Error('BAD_STEP::400')
if (stepNo == 1) {
if (!checkEmail(email))
throw Error('INCORRECT_EMAIL::400')
const code = Math.random().toString().substr(2, 4)
cache[action][email] = code
sendEmail(email, action.toUpperCase(), `${email} => ${code}`)
}
if (stepNo == 2) {
if (cache[action][email] != code)
throw Error('INCORRECT_CODE::400')
}
if (stepNo == 3) {
if (cache[action][email] != code)
throw Error('INCORRECT_CODE::400')
if (!checkPassword(password))
throw Error('INCORRECT_PASSWORD::400')
const info = db
.prepare('update customers set password = :password where email = :email')
.run({ email, password })
if (info.changes == 0)
throw Error('BAD_REQUEST::400')
delete cache[action][email]
}
res.status(200).json({success: true})
})
app.get('/auth/logout', (req, res, next) => {
if (req.session?.asid)
delete sessions[req.session.asid]
res.setHeader('Set-Cookie', [`asid=; expired; httpOnly;path=/api/admin`])
res.status(200).json({success: true})
})
// CUSTOMER
app.get('/customer/profile', (req, res, next) => {
const row = db
.prepare(`
select id, name, email, plan, coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, upload_group_id
select id, name, email, plan, coalesce(json_balance, '{}') json_balance, coalesce(json_company, '{}') json_company, upload_chat_id
from customers
where id = :customer_id
`)
.get(res.locals)
if (row?.upload_group_id) {
row.upload_group = db
.prepare(`select id, name, telegram_id from groups where id = :group_id and project_id is null`)
if (row?.upload_chat_id) {
row.upload_chat = db
.prepare(`select id, name, telegram_id from chats where id = :chat_id and project_id is null`)
.safeIntegers(true)
.get({ group_id: row.upload_group_id})
delete row.upload_group_id
.get({ chat_id: row.upload_chat_id})
delete row.upload_chat_id
}
for (const key in row) {
@@ -167,15 +313,36 @@ app.put('/customer/profile', (req, res, next) => {
res.status(200).json({success: true})
})
app.get('/customer/settings', (req, res, next) => {
const row = db
.prepare(`select coalesce(json_settings, '{}') from customers where id = :customer_id`)
.pluck(true)
.get(res.locals)
res.status(200).json({success: true, data: JSON.parse(row)})
})
app.put('/customer/settings', (req, res, next) => {
res.locals.json_settings = JSON.stringify(req.body || {})
db
.prepare(`update customers set json_settings = :json_settings where id = :customer_id`)
.run(res.locals)
res.status(200).json({success: true})
})
// PROJECT
app.get('/project', (req, res, next) => {
const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''
const rows = db
.prepare(`
select id, name, description, logo
from projects
where customer_id = :customer_id ${where} and is_deleted <> 1
select id, name, description, logo, is_logo_bg, is_archived,
(select count(*) from chats where project_id = p.id) chat_count,
(select count(distinct user_id) from chat_users where chat_id in (select id from chats where project_id = p.id)) user_count
from projects p
where customer_id = :customer_id ${where}
order by name
`)
.all(res.locals)
@@ -204,7 +371,7 @@ app.post('/project', (req, res, next) => {
.pluck(true)
.get(res.locals)
res.status(200).json({success: true, data: id})
res.redirect(req.baseUrl + `/project?id=${id}`)
})
app.put('/project/:pid(\\d+)', (req, res, next) => {
@@ -212,11 +379,12 @@ app.put('/project/:pid(\\d+)', (req, res, next) => {
res.locals.name = req.body?.name
res.locals.description = req.body?.description
res.locals.logo = req.body?.logo
res.locals.is_logo_bg = req.body?.is_logo_bg
const info = db
.prepareUpdate(
'projects',
['name', 'description', 'logo'],
['name', 'description', 'logo', 'is_logo_bg'],
res.locals,
['id', 'customer_id'])
.run(res.locals)
@@ -224,39 +392,41 @@ app.put('/project/:pid(\\d+)', (req, res, next) => {
if (info.changes == 0)
throw Error('NOT_FOUND::404')
res.status(200).json({success: true})
res.redirect(req.baseUrl + `/project?id=${req.params.pid}`)
})
app.delete('/project/:pid(\\d+)', async (req, res, next) => {
app.put('/project/:pid(\\d+)/:action(archive|restore)', async (req, res, next) => {
res.locals.id = req.params.pid
res.locals.is_archived = +(req.params.action == 'archive')
const info = db
.prepare('update projects set id_deleted = 1 where id = :id and customer_id = :customer_id')
.prepare(`
update projects
set is_archived = :is_archived
where id = :id and customer_id = :customer_id and coalesce(is_archived, 0) = not :is_archived
`)
.run(res.locals)
if (info.changes == 0)
throw Error('NOT_FOUND::404')
throw Error('BAD_REQUEST::400')
const groupIds = db
.prepare(`select id from groups where project_id = :id`)
const chatIds = db
.prepare(`select id from chats where project_id = :id`)
.pluck(true)
.all(res.locals)
for (const groupId of groupIds) {
await bot.sendMessage(groupId, 'Проект удален')
await bot.leaveGroup(groupId)
for (const chatId of chatIds) {
await bot.sendMessage(chatId, res.locals.is_archived ? 'Проект помещен в архив. Отслеживание сообщений прекращено.' : 'Проект восстановлен из архива.')
}
db.prepare(`updates groups set project_id = null where id in (${ groupIds.join(', ')})`).run()
res.status(200).json({success: true})
res.redirect(req.baseUrl + `/project?id=${req.params.pid}`)
})
app.use ('/project/:pid(\\d+)/*', (req, res, next) => {
res.locals.project_id = parseInt(req.params.pid)
const row = db
.prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_deleted <> 1')
.prepare('select 1 from projects where id = :project_id and customer_id = :customer_id and is_archived <> 1')
.get(res.locals)
if (!row)
@@ -277,8 +447,8 @@ app.get('/project/:pid(\\d+)/user', (req, res, next) => {
left join user_details ud on u.id = ud.user_id and ud.project_id = :project_id
where id in (
select user_id
from group_users
where group_id in (select id from groups where project_id = :project_id)
from chat_users
where chat_id in (select id from chats where project_id = :project_id)
) ${where}
`)
.safeIntegers(true)
@@ -337,7 +507,7 @@ app.get('/project/:pid(\\d+)/company', (req, res, next) => {
const rows = db
.prepare(`
select id, name, email, phone, description, logo,
(select json_group_array(user_id) from company_users where company_id = c.id) users
(select json_chat_array(user_id) from company_users where company_id = c.id) users
from companies c
where project_id = :project_id ${where}
order by name
@@ -373,7 +543,7 @@ app.post('/project/:pid(\\d+)/company', (req, res, next) => {
.pluck(res.locals)
.get(res.locals)
res.status(200).json({success: true, data: id})
res.redirect(req.baseUrl + `/project/${req.params.pid}/company?id=${id}`)
})
app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
@@ -395,7 +565,7 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
if (info.changes == 0)
throw Error('NOT_FOUND::404')
res.status(200).json({success: true})
res.redirect(req.baseUrl + `/project/${req.params.pid}/company?id=${req.params.cid}`)
})
app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
@@ -411,13 +581,13 @@ app.delete('/project/:pid(\\d+)/company/:cid(\\d+)', (req, res, next) => {
res.status(200).json({success: true})
})
app.get('/project/:pid(\\d+)/group', (req, res, next) => {
app.get('/project/:pid(\\d+)/chat', (req, res, next) => {
const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''
const rows = db
.prepare(`
select id, name, telegram_id, is_channel, user_count, bot_can_ban
from groups
from chats
where project_id = :project_id ${where}
`)
.all(res.locals)
@@ -428,20 +598,20 @@ app.get('/project/:pid(\\d+)/group', (req, res, next) => {
res.status(200).json({success: true, data: where ? rows[0] : rows})
})
app.get('/project/:pid(\\d+)/group/:gid(\\d+)', (req, res, next) => {
res.redirect(req.baseUrl + `/project/${req.params.pid}/group?id=${req.params.uid}`)
app.get('/project/:pid(\\d+)/chat/:gid(\\d+)', (req, res, next) => {
res.redirect(req.baseUrl + `/project/${req.params.pid}/chat?id=${req.params.uid}`)
})
app.delete('/project/:pid(\\d+)/group/:gid(\\d+)', async (req, res, next) => {
res.locals.group_id = parseInt(req.params.gid)
app.delete('/project/:pid(\\d+)/chat/:gid(\\d+)', async (req, res, next) => {
res.locals.chat_id = parseInt(req.params.gid)
const info = db
.prepare(`update groups set project_id = null where id = :group_id and project_id = :project_id`)
.prepare(`update chats set project_id = null where id = :chat_id and project_id = :project_id`)
.run(res.locals)
if (info.changes == 0)
throw Error('NOT_FOUND::404')
await bot.sendMessage(res.locals.group_id, 'Группа удалена из проекта')
await bot.sendMessage(res.locals.chat_id, 'Чат удален из проекта')
res.status(200).json({success: true})
})
@@ -463,8 +633,8 @@ app.put('/project/:pid(\\d+)/company/:cid(\\d+)/user', (req, res, next) => {
let rows = db
.prepare(`
select user_id
from group_users
where group_id in (select id from groups where project_id = :project_id)
from chat_users
where chat_id in (select id from chats where project_id = :project_id)
`)
.pluck(true) // .raw?
.get(res.locals)

View File

@@ -10,9 +10,7 @@ const { NewMessage } = require('telegram/events')
const { Button } = require('telegram/tl/custom/button')
const { CustomFile } = require('telegram/client/uploads')
//const session = new StringSession('1AgAOMTQ5LjE1NC4xNjcuNTABu2OaFuD5Oyi5wGck+n5ldAfshzYfwlWee+OUxYBvFzlKAdW11Hsndu1SJBLUnKjP8sTJEPbLwdqANBhBXmQMghLVAblwK6TxLfsWxy2zf/HGLeNXohhrsep0hBxu9imyHV6OI6gQG+c5qaGkzjZrz0AcS4ut0xy99XrXgjiNfnjeMX7a0mOk6IK9iKdwbX9kXTfclFLVppiBGXolYJjVb2E57tk4+7RncIVyw+Fxn0NZfnhEfHJZly6j03arZOeM5VYl9ul8+3lJDD+KJJHeMgImmYjmcFcF3CbtkhPuTSPnWKtCnm2sRzepn5VFfoG6zgYff04fBdKGvHAai+wQSOY=')
const session = new StringSession('1AgAOMTQ5LjE1NC4xNjcuNTEBuzSgmBQR5/m8M8cyOnsLCIOkYQJTizJoJRZiPKK+eBjMuodc0JuKQwzeWBRJI/c6YxaBHvokpngf5kr57uly+meSPPlFq6MyoSSQDbEJ3VAAWJu+/ALN0ickE92RjRfM5Kw6DimC9FXuMgJJsoUHtk/i+ZGXy9JB+q67G0yy8NvFIuWpFHJDkwmi0qTlTgJ5UOm4PYkV01iNUcV5siaWFVTTLsetHtBUdMOzg5WjjvuOyYV/MIx+z7ynhvF3DxLPCugxqhCvZ/RW+0vldrTX5TZ0BzIDk2eNFQjRORJcZo6upwvH7aZYStV4DxhIi1dEYu5gyvnt4vkbR5kuvE/GqO0=')
const session = new StringSession(process.env.BOT_SID || '')
let client
@@ -82,38 +80,38 @@ async function updateUserPhoto (userId, data) {
.run({ user_id: userId, photo_id: photoId, photo: file.toString('base64') })
}
async function registerGroup (telegramId, isChannel) {
async function registerChat (telegramId, isChannel) {
db
.prepare(`insert or ignore into groups (telegram_id, is_channel) values (:telegram_id, :is_channel)`)
.prepare(`insert or ignore into chats (telegram_id, is_channel) values (:telegram_id, :is_channel)`)
.safeIntegers(true)
.run({ telegram_id: telegramId, is_channel: +isChannel })
const row = db
.prepare(`select id, name from groups where telegram_id = :telegram_id`)
.prepare(`select id, name from chats where telegram_id = :telegram_id`)
.safeIntegers(true)
.get({telegram_id: telegramId})
if (!row?.name) {
const entity = isChannel ? { channelId: telegramId } : { chatId: telegramId }
const group = await client.getEntity(isChannel ? new Api.PeerChannel(entity) : new Api.PeerChat(entity))
const chat = await client.getEntity(isChannel ? new Api.PeerChannel(entity) : new Api.PeerChat(entity))
db
.prepare(`update groups set name = :name where id = :group_id`)
.run({ group_id: row.id, name: group.title })
.prepare(`update chats set name = :name where id = :chat_id`)
.run({ chat_id: row.id, name: chat.title })
}
return row.id
}
async function attachGroup(groupId, isChannel, projectId) {
async function attachChat(chatId, isChannel, projectId) {
const info = db
.prepare(`update groups set project_id = :project_id where id = :group_id and coalesce(project_id, 1) = 1`)
.run({ group_id: groupId, project_id: projectId })
.prepare(`update chats set project_id = :project_id where id = :chat_id and coalesce(project_id, 1) = 1`)
.run({ chat_id: chatId, project_id: projectId })
if (info.changes == 1) {
const inputPeer = isChannel ?
new Api.InputPeerChannel({ channelId: tgGroupId }) :
new Api.InputPeerChat({ chatlId: tgGroupId })
new Api.InputPeerChannel({ channelId: tgChatId }) :
new Api.InputPeerChat({ chatId: tgChatId })
const query = `select (select name from customers where id = p.customer_id) || ' >> ' || p.name from projects p where id = :project_id`
const message = db
@@ -127,14 +125,14 @@ async function attachGroup(groupId, isChannel, projectId) {
return info.changes == 1
}
async function onGroupAttach (tgGroupId, isChannel) {
async function onChatAttach (tgChatId, isChannel) {
const projectId = db
.prepare(`select project_id from groups where telegram_id = :telegram_id`)
.prepare(`select project_id from chats where telegram_id = :telegram_id`)
.safeIntegers(true)
.pluck(true)
.get({ telegram_id: tgGroupId })
.get({ telegram_id: tgChatId })
const entity = isChannel ? { channelId: tgGroupId } : { chatId: tgGroupId }
const entity = isChannel ? { channelId: tgChatId } : { chatId: tgChatId }
const inputPeer = await client.getEntity( isChannel ?
new Api.InputPeerChannel(entity) :
new Api.InputPeerChat(entity)
@@ -151,48 +149,44 @@ async function onGroupAttach (tgGroupId, isChannel) {
unpin: false
}))
//fs.appendFileSync('./1.log', '\n>' + tgGroupId + ':' + isChannel + '<\n')
//fs.appendFileSync('./1.log', '\n>' + tgChatId + ':' + isChannel + '<\n')
}
async function reloadGroupUsers(groupId, onlyReset) {
async function reloadChatUsers(chatId, onlyReset) {
db
.prepare(`delete from group_users where group_id = :group_id`)
.run({ group_id: groupId })
.prepare(`delete from chat_users where chat_id = :chat_id`)
.run({ chat_id: chatId })
if (onlyReset)
return
const group = db
.prepare(`select telegram_id, is_channel, access_hash from groups where id = :group_id`)
.get({ group_id: groupId})
const chat = db
.prepare(`select telegram_id, is_channel, access_hash from chats where id = :chat_id`)
.get({ chat_id: chatId})
console.log (123, group)
if (!group)
if (!chat)
return
const tgGroupId = group.telegram_id
const isChannel = group.is_channel
let accessHash = group.access_hash
console.log ('HERE')
const tgChatId = chat.telegram_id
const isChannel = chat.is_channel
let accessHash = chat.access_hash
db
.prepare(`update groups set access_hash = :access_hash where id = :group_id`)
.prepare(`update chats set access_hash = :access_hash where id = :chat_id`)
.safeIntegers(true)
.run({
group_id: groupId,
chat_id: chatId,
access_hash: accessHash,
})
const result = isChannel ?
await client.invoke(new Api.channels.GetParticipants({
channel: new Api.PeerChannel({ channelId: tgGroupId }),
channel: new Api.PeerChannel({ channelId: tgChatId }),
filter: new Api.ChannelParticipantsRecent(),
limit: 999999,
offset: 0
})) : await client.invoke(new Api.messages.GetFullChat({
chatId: tgGroupId,
chatId: tgChatId,
}))
const users = result.users.filter(user => !user.bot)
@@ -202,8 +196,8 @@ async function reloadGroupUsers(groupId, onlyReset) {
if (updateUser(userId, user)) {
await updateUserPhoto (userId, user)
const query = `insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)`
db.prepare(query).run({ group_id: groupId, user_id: userId })
const query = `insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)`
db.prepare(query).run({ chat_id: chatId, user_id: userId })
}
}
}
@@ -212,11 +206,11 @@ async function registerUpload(data) {
if (!data.projectId || !data.media)
return false
const uploadGroup = db
const uploadChat = db
.prepare(`
select id, telegram_id, project_id, is_channel, access_hash
from groups
where id = (select upload_group_id
from chats
where id = (select upload_chat_id
from customers
where id = (select customer_id from projects where id = :project_id limit 1)
limit 1)
@@ -225,14 +219,14 @@ async function registerUpload(data) {
.safeIntegers(true)
.get({project_id: data.projectId})
if (!uploadGroup || !uploadGroup.telegram_id || uploadGroup.id == data.originGroupId)
if (!uploadChat || !uploadChat.telegram_id || uploadChat.id == data.originchatId)
return false
const tgUploadGroupId = uploadGroup.telegram_id
const tgUploadChatId = uploadChat.telegram_id
const peer = uploadGroup.is_channel ?
new Api.PeerChannel({ channelId: tgUploadGroupId }) :
new Api.PeerChat({ chatlId: tgUploadGroupId })
const peer = uploadChat.is_channel ?
new Api.PeerChannel({ channelId: tgUploadChatId }) :
new Api.PeerChat({ chatlId: tgUploadChatId })
let resultId = 0
@@ -248,16 +242,16 @@ async function registerUpload(data) {
const update = result.updates.find(u =>
(u.className == 'UpdateNewMessage' || u.className == 'UpdateNewChannelMessage') &&
u.message.className == 'Message' &&
(u.message.peerId.channelId?.value == tgUploadGroupId || u.message.peerId.chatId?.value == tgUploadGroupId) &&
(u.message.peerId.channelId?.value == tgUploadChatId || u.message.peerId.chatId?.value == tgUploadChatId) &&
u.message.media)
const udoc = update?.message?.media?.document
if (udoc) {
resultId = db
.prepare(`
insert into documents (project_id, origin_group_id, origin_message_id, group_id, message_id,
insert into documents (project_id, origin_chat_id, origin_message_id, chat_id, message_id,
file_id, access_hash, filename, mime, caption, size, published_by, parent_type, parent_id)
values (:project_id, :origin_group_id, :origin_message_id, :group_id, :message_id,
values (:project_id, :origin_chat_id, :origin_message_id, :chat_id, :message_id,
:file_id, :access_hash, :filename, :mime, :caption, :size, :published_by, :parent_type, :parent_id)
returning id
`)
@@ -265,9 +259,9 @@ async function registerUpload(data) {
.pluck(true)
.get({
project_id: data.projectId,
origin_group_id: data.originGroupId,
origin_chat_id: data.originchatId,
origin_message_id: data.originMessageId,
group_id: uploadGroup.id,
chat_id: uploadChat.id,
message_id: update.message.id,
file_id: udoc.id.value,
filename: udoc.attributes.find(attr => attr.className == 'DocumentAttributeFilename')?.fileName,
@@ -291,14 +285,14 @@ async function registerUpload(data) {
async function onNewServiceMessage (msg, isChannel) {
const action = msg.action || {}
const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
const groupId = await registerGroup(tgGroupId, isChannel)
const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
const chatId = await registerChat(tgChatId, isChannel)
// Group/Channel rename
// Ghat rename
if (action.className == 'MessageActionChatEditTitle') {
const info = db
.prepare(`
update groups
update chats
set name = :name, is_channel = :is_channel, last_update_time = :last_update_time
where telegram_id = :telegram_id
`)
@@ -307,7 +301,7 @@ async function onNewServiceMessage (msg, isChannel) {
name: action.title,
is_channel: +isChannel,
last_update_time: Math.floor (Date.now() / 1000),
telegram_id: tgGroupId
telegram_id: tgChatId
})
}
@@ -315,7 +309,7 @@ async function onNewServiceMessage (msg, isChannel) {
if (action.className == 'MessageActionChatMigrateTo') {
const info = db
.prepare(`
update groups
update chats
set telegram_id = :new_telegram_id, name = :name, is_channel = 1, last_update_time = :last_update_time
where telegram_id = :old_telegram_id
`)
@@ -323,7 +317,7 @@ async function onNewServiceMessage (msg, isChannel) {
.run({
name: action.title,
last_update_time: Date.now() / 1000,
old_telegram_id: tgGroupId,
old_telegram_id: tgChatId,
new_telegram_id: action.channelId.value
})
}
@@ -352,27 +346,27 @@ async function onNewServiceMessage (msg, isChannel) {
}
const query = isAdd ?
`insert or ignore into group_users (group_id, user_id) values (:group_id, :user_id)` :
`delete from group_users where group_id = :group_id and user_id = :user_id`
db.prepare(query).run({ group_id: groupId, user_id: userId })
`insert or ignore into chat_users (chat_id, user_id) values (:chat_id, :user_id)` :
`delete from chat_users where chat_id = :chat_id and user_id = :user_id`
db.prepare(query).run({ chat_id: chatId, user_id: userId })
}
}
}
}
async function onNewMessage (msg, isChannel) {
const tgGroupId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
const groupId = await registerGroup(tgGroupId, isChannel)
const tgChatId = isChannel ? msg.peerId?.channelId?.value : msg.peerId?.chatId?.value
const chatId = await registerChat(tgChatId, isChannel)
// Document is detected
if (msg.media?.document) {
const doc = msg.media.document
const projectId = db
.prepare(`select project_id from groups where telegram_id = :telegram_id`)
.prepare(`select project_id from chats where telegram_id = :telegram_id`)
.safeIntegers(true)
.pluck(true)
.get({telegram_id: tgGroupId})
.get({telegram_id: tgChatId})
const media = new Api.InputMediaDocument({
id: new Api.InputDocument({
@@ -386,7 +380,7 @@ async function onNewMessage (msg, isChannel) {
projectId,
media,
caption: msg.message,
originGroupId: groupId,
originchatId: chatId,
originMessageId: msg.id,
parentType: 0,
publishedBy: registerUser (msg.fromId?.userId?.value)
@@ -399,22 +393,22 @@ async function onNewMessage (msg, isChannel) {
select name
from projects
where id in (
select project_id from groups where id = :group_id
select project_id from chats where id = :chat_id
union
select id from projects where upload_group_id = :group_id)
select id from projects where upload_chat_id = :chat_id)
`)
.pluck(true)
.get({ group_id: groupId })
.get({ chat_id: chatId })
if (projectName)
return await bot.sendMessage(groupId, 'Группа уже используется на проекте ' + projectName)
return await bot.sendMessage(chatId, 'Группа уже используется на проекте ' + projectName)
const [_, time64, key] = msg.message.substr(3).split('-')
const now = Math.floor(Date.now() / 1000)
const time = Buffer.from(time64, 'base64')
if (now - 3600 >= time && time >= now)
return await bot.sendMessage(groupId, 'Время действия ключа для привязки истекло')
return await bot.sendMessage(chatId, 'Время действия ключа для привязки истекло')
const projectId = db
.prepare(`select id from projects where generate_key(id, :time) = :key`)
@@ -422,8 +416,8 @@ async function onNewMessage (msg, isChannel) {
.get({ key: msg.message.trim(), time })
if (projectId) {
await attachGroup(groupId, isChannel, projectId)
await onGroupAttach(tgGroupId, isChannel)
await attachChat(chatId, isChannel, projectId)
await onChatAttach(tgChatId, isChannel)
}
}
@@ -440,12 +434,12 @@ async function onNewMessage (msg, isChannel) {
db
.prepare(`
update customers
set upload_group_id = :group_id
set upload_chat_id = :chat_id
where id = :customer_id and telegram_user_id = :telegram_user_id
`)
.safeIntegers(true)
.run({
group_id: groupId,
chat_id: chatId,
customer_id: customerId,
telegram_user_id: tgUserId
})
@@ -462,9 +456,9 @@ async function onNewMessage (msg, isChannel) {
db
.prepare(`
update groups
update chats
set project_id = :project_id
where id = :group_id and exists(
where id = :chat_id and exists(
select 1
from customers
where id = :customer_id and telegram_user_id = :telegram_user_id)
@@ -472,13 +466,13 @@ async function onNewMessage (msg, isChannel) {
.safeIntegers(true)
.run({
project_id: projectId,
group_id: groupId,
chat_id: chatId,
customer_id: customerId,
telegram_user_id: tgUserId
})
await reloadGroupUsers(groupId, false)
await onGroupAttach(tgGroupId, isChannel)
await reloadChatUsers(chatId, false)
await onChatAttach(tgChatId, isChannel)
}
}
}
@@ -516,34 +510,34 @@ async function onNewUserMessage (msg) {
}
async function onUpdatePaticipant (update, isChannel) {
const tgGroupId = isChannel ? update.channelId?.value : update.chatlId?.value
if (!tgGroupId || update.userId?.value != bot.id)
const tgChatId = isChannel ? update.channelId?.value : update.chatlId?.value
if (!tgChatId || update.userId?.value != bot.id)
return
const groupId = await registerGroup (tgGroupId, isChannel)
const chatId = await registerChat (tgChatId, isChannel)
const isBan = update.prevParticipant && !update.newParticipant
const isAdd = (!update.prevParticipant || update.prevParticipant?.className == 'ChannelParticipantBanned') && update.newParticipant
if (isBan || isAdd)
await reloadGroupUsers(groupId, isBan)
await reloadChatUsers(chatId, isBan)
if (isBan) {
db
.prepare(`update groups set project_id = null where id = :group_id`)
.run({group_id: groupId})
.prepare(`update chats set project_id = null where id = :chat_id`)
.run({chat_id: chatId})
}
const botCanBan = update.newParticipant?.adminRights?.banUsers || 0
db
.prepare(`update groups set bot_can_ban = :bot_can_ban where id = :group_id`)
.run({group_id: groupId, bot_can_ban: +botCanBan})
.prepare(`update chats set bot_can_ban = :bot_can_ban where id = :chat_id`)
.run({chat_id: chatId, bot_can_ban: +botCanBan})
}
class Bot extends EventEmitter {
async start (apiId, apiHash, botAuthToken) {
this.id = 7236504417n
this.id = BigInt(botAuthToken.split(':')[0])
client = new TelegramClient(session, apiId, apiHash, {})
@@ -568,7 +562,7 @@ class Bot extends EventEmitter {
})
await client.start({botAuthToken})
console.log('SID: ', session.save())
console.log('BOT_SID: ', session.save())
}
async uploadDocument(projectId, fileName, mime, data, parentType, parentId, publishedBy) {
@@ -616,20 +610,20 @@ class Bot extends EventEmitter {
}
}
async reloadGroupUsers(groupId, onlyReset) {
return reloadGroupUsers(groupId, onlyReset)
async reloadChatUsers(chatId, onlyReset) {
return reloadChatUsers(chatId, onlyReset)
}
async sendMessage (groupId, message) {
const group = db
.prepare(`select telegram_id, is_channel from groups where id = :group_id`)
.get({ group_id: groupId})
async sendMessage (chatId, message) {
const chat = db
.prepare(`select telegram_id, is_channel from chats where id = :chat_id`)
.get({ chat_id: chatId})
if (!group)
if (!chat)
return
const entity = group.is_channel ? { channelId: group.telegram_id } : { chatId: group.telegram_id }
const inputPeer = await client.getEntity( group.is_channel ?
const entity = chat.is_channel ? { channelId: chat.telegram_id } : { chatId: chat.telegram_id }
const inputPeer = await client.getEntity( chat.is_channel ?
new Api.InputPeerChannel(entity) :
new Api.InputPeerChat(entity)
)
@@ -640,19 +634,19 @@ class Bot extends EventEmitter {
await delay(1000)
}
async leaveGroup (groupId) {
const group = db
.prepare(`select telegram_id, is_channel from groups where id = :group_id`)
.get({ group_id: groupId})
async leaveChat (chatId) {
const chat = db
.prepare(`select telegram_id, is_channel from chats where id = :chat_id`)
.get({ chat_id: chatId})
if (!group)
if (!chat)
return
if (group.is_channel) {
const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: group.telegram_id }))
if (chat.is_channel) {
const inputPeer = await client.getEntity(new Api.InputPeerChannel({ channelId: chat.telegram_id }))
await client.invoke(new Api.channels.LeaveChannel({ channel: inputPeer }))
} else {
await client.invoke(new Api.messages.DeleteChatUser({ chatId: group.telegram_id, userId: this.id }))
await client.invoke(new Api.messages.DeleteChatUser({ chatId: chat.telegram_id, userId: this.id }))
}
}
}

View File

@@ -19,9 +19,9 @@ function hasAccess(project_id, user_id) {
return !!db
.prepare(`
select 1
from group_users
from chat_users
where user_id = :user_id and
group_id in (select id from groups where project_id = :project_id) and
chat_id in (select id from chats where project_id = :project_id) and
not exists(select 1 from user_details where user_id = :user_id and project_id = :project_id and is_blocked = 1) and
not exists(select 1 from projects where id = :project_id and is_deleted = 1)
`)
@@ -70,13 +70,13 @@ app.get('/project', (req, res, next) => {
const rows = db
.prepare(`
select p.id, p.name, p.description, p.logo,
c.name customer_name, c.upload_group_id <> 0 has_upload
c.name customer_name, c.upload_chat_id <> 0 has_upload
from projects p
inner join customers c on p.customer_id = c.id
where p.id in (
select project_id
from groups
where id in (select group_id from group_users where user_id = :user_id)
from chats
where id in (select chat_id from chat_users where user_id = :user_id)
) and not exists(select 1 from user_details where user_id = :user_id and project_id = p.id and is_blocked = 1)
${where} and is_deleted <> 1
`)
@@ -114,9 +114,9 @@ app.get('/project/:pid(\\d+)/user', (req, res, next) => {
.prepare(`
with actuals (user_id) as (
select distinct user_id
from group_users
where group_id in (select id from groups where project_id = :project_id)
and group_id in (select group_id from group_users where user_id = :user_id)
from chat_users
where chat_id in (select id from chats where project_id = :project_id)
and chat_id in (select chat_id from chat_users where user_id = :user_id)
),
contributors (user_id) as (
select created_by from tasks where project_id = :project_id
@@ -193,29 +193,29 @@ app.get('/project/:pid(\\d+)/user', (req, res, next) => {
})
app.get('/project/:pid(\\d+)/user/reload', async (req, res, next) => {
const groupIds = db
.prepare(`select id from groups where project_id = :project_id`)
const chatIds = db
.prepare(`select id from chats where project_id = :project_id`)
.all(res.locals)
.map(e => e.id)
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
for (const groupId of groupIds) {
await bot.reloadGroupUsers(groupId)
for (const chatId of chatIds) {
await bot.reloadGroupUsers(chatId)
await sleep(1000)
}
res.status(200).json({success: true})
})
app.get('/project/:pid(\\d+)/group', (req, res, next) => {
app.get('/project/:pid(\\d+)/chat', (req, res, next) => {
const where = req.query.id ? ' and id = ' + parseInt(req.query.id) : ''
const rows = db
.prepare(`
select id, name, telegram_id
from groups
where project_id = :project_id and id in (select group_id from group_users where user_id = :user_id)
from chats
where project_id = :project_id and id in (select chat_id from chat_users where user_id = :user_id)
${where}
`)
.all(res.locals)
@@ -226,8 +226,8 @@ app.get('/project/:pid(\\d+)/group', (req, res, next) => {
res.status(200).json({success: true, data: where ? rows[0] : rows})
})
app.get('/project/:pid(\\d+)/group/:gid(\\d+)', (req, res, next) => {
res.redirect(req.baseUrl + `/project/${req.params.pid}/group?id=${req.params.gid}`)
app.get('/project/:pid(\\d+)/chat/:gid(\\d+)', (req, res, next) => {
res.redirect(req.baseUrl + `/project/${req.params.pid}/chat?id=${req.params.gid}`)
})
// TASK
@@ -237,8 +237,8 @@ app.get('/project/:pid(\\d+)/task', (req, res, next) => {
const rows = db
.prepare(`
select id, name, created_by, assigned_to, priority, status, time_spent, create_date, plan_date, close_date,
(select json_group_array(user_id) from task_users where task_id = t.id) observers,
(select json_group_array(id) from documents where parent_type = 1 and parent_id = t.id) attachments
(select json_chat_array(user_id) from task_users where task_id = t.id) observers,
(select json_chat_array(id) from documents where parent_type = 1 and parent_id = t.id) attachments
from tasks t
where project_id = :project_id and
(created_by = :user_id or assigned_to = :user_id or exists(select 1 from task_users where task_id = t.id and user_id = :user_id))
@@ -351,8 +351,8 @@ app.put('/project/:pid(\\d+)/task/:tid(\\d+)/observer', (req, res, next) => {
let rows = db
.prepare(`
select user_id
from group_users
where group_id in (select id from groups where project_id = :project_id)
from chat_users
where chat_id in (select id from chats where project_id = :project_id)
`)
.pluck(true)
.all(res.locals)
@@ -379,8 +379,8 @@ app.get('/project/:pid(\\d+)/meeting', (req, res, next) => {
const rows = db
.prepare(`
select id, name, description, created_by, meet_date,
(select json_group_array(user_id) from meeting_users where meeting_id = m.id) participants,
(select json_group_array(id) from documents where parent_type = 2 and parent_id = m.id) attachments
(select json_chat_array(user_id) from meeting_users where meeting_id = m.id) participants,
(select json_chat_array(id) from documents where parent_type = 2 and parent_id = m.id) attachments
from meetings m
where project_id = :project_id and
(created_by = :user_id or exists(select 1 from meeting_users where meeting_id = m.id and user_id = :user_id))
@@ -483,8 +483,8 @@ app.put('/project/:pid(\\d+)/meeting/:mid(\\d+)/participants', (req, res, next)
let rows = db
.prepare(`
select user_id
from group_users
where group_id in (select id from groups where project_id = :project_id)
from chat_users
where chat_id in (select id from chats where project_id = :project_id)
`)
.pluck(true) // .raw?
.all(res.locals)
@@ -516,10 +516,10 @@ app.get('/project/:pid(\\d+)/document', (req, res, next) => {
// To-Do: отдавать готовую ссылку --> как минимум GROUP_ID надо заменить на tgGroupId
const rows = db
.prepare(`
select id, origin_group_id, origin_message_id, filename, mime, caption, size, published_by, parent_id, parent_type
select id, origin_chat_id, origin_message_id, filename, mime, caption, size, published_by, parent_id, parent_type
from documents d
where project_id = :project_id ${where} and (
origin_group_id in (select group_id from group_users where user_id = :user_id)
origin_chat_id in (select chat_id from chat_users where user_id = :user_id)
or
parent_type = 1 and parent_id in (
select id
@@ -566,9 +566,9 @@ app.use('/project/:pid(\\d+)/document/:did(\\d+)', (req, res, next) => {
throw Error('NOT_FOUND::404')
if (doc.parent_type == 0) {
res.locals.group_id = doc.group_id
res.locals.chat_id = doc.chat_id
const row = db
.prepare(`select 1 from group_users where group_id = :group_id and user_id = :user_id`)
.prepare(`select 1 from chat_users where chat_id = :chat_id and user_id = :user_id`)
.get(res.locals)
if (row) {