Fixes I guess?
This commit is contained in:
parent
d545318aed
commit
ce6802faff
26
bot/bot.ts
26
bot/bot.ts
@ -1,13 +1,8 @@
|
|||||||
import { Bot, Context, session } from "https://deno.land/x/grammy/mod.ts";
|
import { Bot, Context, session, BotError, GrammyError, HttpError } from "https://deno.land/x/grammy/mod.ts";
|
||||||
import { BotError } from "https://deno.land/x/grammy@v1.30.0/bot.ts";
|
|
||||||
import { Ctx, defaultSessionData } from "./ctx.ts";
|
import { Ctx, defaultSessionData } from "./ctx.ts";
|
||||||
import * as config from "../cfg/config.ts"
|
import * as config from "../cfg/config.ts"
|
||||||
import { ERR_CODES, Err } from "../utils/errors.ts";
|
import { ERR_CODES, Err } from "../utils/errors.ts";
|
||||||
|
|
||||||
class BotUnknownRuntimeErr extends Err {
|
|
||||||
code: ERR_CODES = ERR_CODES.UnknownErr;
|
|
||||||
override cause?: BotError<Context>;
|
|
||||||
}
|
|
||||||
class BotUnknownOnStartErr extends Err {
|
class BotUnknownOnStartErr extends Err {
|
||||||
code: ERR_CODES = ERR_CODES.UnknownErr;
|
code: ERR_CODES = ERR_CODES.UnknownErr;
|
||||||
}
|
}
|
||||||
@ -16,16 +11,23 @@ export const runBot = async (cfg: config.BotConfig, initMode: (bot: Bot<Ctx>) =>
|
|||||||
const bot = new Bot<Ctx>(cfg.bot_token)
|
const bot = new Bot<Ctx>(cfg.bot_token)
|
||||||
bot.use(session({ initial: defaultSessionData }))
|
bot.use(session({ initial: defaultSessionData }))
|
||||||
bot.catch((err: BotError<Context>) => {
|
bot.catch((err: BotError<Context>) => {
|
||||||
throw new BotUnknownRuntimeErr("Unknown error while running the bot",
|
const ctx = err.ctx;
|
||||||
{ cause: err })
|
console.error(`Error while handling update ${ctx.update.update_id}:`);
|
||||||
|
const e = err.error;
|
||||||
|
if (e instanceof GrammyError) {
|
||||||
|
if (e.error_code === 400 && e.method === "deleteMessage") return
|
||||||
|
console.error("Error in request:", e.description);
|
||||||
|
} else if (e instanceof HttpError) {
|
||||||
|
console.error("Could not contact Telegram:", e);
|
||||||
|
} else {
|
||||||
|
throw new BotUnknownOnStartErr("Unknown error while starting the bot",
|
||||||
|
{ cause: err })
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
initMode(bot)
|
initMode(bot)
|
||||||
await bot.init()
|
await bot.init()
|
||||||
|
|
||||||
console.log(`starting bot ${bot.botInfo.username}`)
|
console.log(`starting bot ${bot.botInfo.username}`)
|
||||||
await bot.start().catch(err => {
|
await bot.start().catch(err => console.log(err))
|
||||||
throw new BotUnknownOnStartErr("Unknown error while starting the bot",
|
|
||||||
{ cause: err })
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { Filter } from "https://deno.land/x/grammy@v1.30.0/mod.ts";
|
import { Filter } from "https://deno.land/x/grammy/mod.ts";
|
||||||
import { BotConfig } from "../../cfg/config.ts";
|
import { BotConfig } from "../../cfg/config.ts";
|
||||||
import { Ctx } from "../ctx.ts";
|
import { Ctx } from "../ctx.ts";
|
||||||
import { LangManager } from "../lang/export.ts";
|
import { LangManager } from "../lang/export.ts";
|
||||||
import { Kysely } from 'npm:kysely';
|
import { Kysely } from 'npm:kysely';
|
||||||
import { Database } from "../../repo/exports.ts";
|
import { Database } from "../../repo/exports.ts";
|
||||||
import { checkUserRestrictions, getActiveInviteLink } from "../../core/users.ts";
|
import { checkUserRestrictions, getActiveInviteLink } from "../../core/users.ts";
|
||||||
import { CheckUserOut } from "./user_managment.ts";
|
import { CheckUserOut, type CheckUserOnStartOut } from "./user_managment.ts";
|
||||||
|
|
||||||
const CAPTCHA_FAILS_LIMIT = 3
|
const CAPTCHA_FAILS_LIMIT = 3
|
||||||
const TIMEOUT_AFTER_FAIL_MINS = 5
|
const TIMEOUT_AFTER_FAIL_MINS = 5
|
||||||
@ -46,11 +46,10 @@ const isSolutionCorrect = (expectSolution: string, solution: string): boolean =>
|
|||||||
|
|
||||||
|
|
||||||
const captchaPassed = async (ctx: Ctx, db: Kysely<Database>, cfg: BotConfig) => {
|
const captchaPassed = async (ctx: Ctx, db: Kysely<Database>, cfg: BotConfig) => {
|
||||||
if (!ctx.from) return
|
|
||||||
if (ctx.chatId && ctx.session.captcha_data) {
|
if (ctx.chatId && ctx.session.captcha_data) {
|
||||||
ctx.api.deleteMessage(
|
await ctx.api.deleteMessage(
|
||||||
ctx.chatId, ctx.session.captcha_data.message_id
|
ctx.chatId, ctx.session.captcha_data.message_id
|
||||||
).catch()
|
)
|
||||||
}
|
}
|
||||||
ctx.session.captcha_data = undefined
|
ctx.session.captcha_data = undefined
|
||||||
|
|
||||||
@ -73,39 +72,29 @@ const captchaPassed = async (ctx: Ctx, db: Kysely<Database>, cfg: BotConfig) =>
|
|||||||
}).execute()
|
}).execute()
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.reply(LangManager.getLang(ctx.from.language_code)
|
ctx.reply(LangManager.getLang(ctx.from!.language_code)
|
||||||
.replies.captcha.passed(link.invite_link))
|
.replies.captcha.passed(link.invite_link))
|
||||||
ctx.session.captcha_data = undefined
|
ctx.session.captcha_data = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const initUserCaptcha = async (ctx: Ctx, db: Kysely<Database>, user: CheckUserOut, cfg: BotConfig) => {
|
const initUserCaptcha = async (ctx: Ctx, db: Kysely<Database>, user: CheckUserOnStartOut, cfg: BotConfig) => {
|
||||||
if (!ctx.msg || !ctx.msg.from) return
|
|
||||||
if (user.isChatParticipant) {
|
if (user.isChatParticipant) {
|
||||||
ctx.reply(LangManager.getLang(ctx.msg.from.language_code)
|
ctx.reply(LangManager.getLang(ctx.from!.language_code)
|
||||||
.replies.captcha.already_in_chat)
|
.replies.captcha.already_in_chat)
|
||||||
return
|
} else if (user.activeInviteLink) {
|
||||||
}
|
await ctx.reply(user.activeInviteLink)
|
||||||
if (user.isCaptchaSolved) {
|
} else if (user.isCaptchaSolved) {
|
||||||
let activeLink: string | undefined;
|
|
||||||
await db.transaction().execute(async trx => {
|
|
||||||
activeLink = await getActiveInviteLink(trx, ctx.msg!.from!.id)
|
|
||||||
})
|
|
||||||
if (activeLink) {
|
|
||||||
ctx.reply(activeLink)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await captchaPassed(ctx, db, cfg)
|
await captchaPassed(ctx, db, cfg)
|
||||||
return
|
} else {
|
||||||
}
|
const captcha = new Captcha()
|
||||||
|
const msg = await ctx.reply(captcha._text)
|
||||||
const captcha = new Captcha()
|
ctx.session.captcha_data = {
|
||||||
const msg = await ctx.reply(captcha._text)
|
message_id: msg.message_id,
|
||||||
ctx.session.captcha_data = {
|
tries_failed: 0,
|
||||||
message_id: msg.message_id,
|
generated_at: captcha._generated_at,
|
||||||
tries_failed: 0,
|
solution: captcha._solution.toString(),
|
||||||
generated_at: captcha._generated_at,
|
}
|
||||||
solution: captcha._solution.toString(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,18 +105,19 @@ const checkCaptchaSolution = async (ctx: Filter<Ctx, "message:text">, db: Kysely
|
|||||||
const users = await db.selectFrom('users').selectAll().where('tg_id', '=', ctx.msg.from.id).execute()
|
const users = await db.selectFrom('users').selectAll().where('tg_id', '=', ctx.msg.from.id).execute()
|
||||||
if (users.length < 1) return // TODO: Maybe create new user here, idk
|
if (users.length < 1) return // TODO: Maybe create new user here, idk
|
||||||
|
|
||||||
const userRestrictions = await checkUserRestrictions(db, users[0])
|
const user = users[0]
|
||||||
|
const userRestrictions = await checkUserRestrictions(db, user)
|
||||||
if (userRestrictions.isBlocked || userRestrictions.isTimeout) return
|
if (userRestrictions.isBlocked || userRestrictions.isTimeout) return
|
||||||
|
|
||||||
if (isSolutionCorrect(ctx.session.captcha_data!.solution, ctx.message.text)) {
|
if (isSolutionCorrect(ctx.session.captcha_data!.solution, ctx.message.text)) {
|
||||||
await captchaPassed(ctx, db, cfg)
|
await captchaPassed(ctx, db, cfg)
|
||||||
} else {
|
} else {
|
||||||
ctx.session.captcha_data!.tries_failed++
|
ctx.session.captcha_data!.tries_failed++
|
||||||
ctx.api.deleteMessage(ctx.chatId, ctx.msg.message_id).catch()
|
await ctx.api.deleteMessage(ctx.chatId, ctx.msg.message_id)
|
||||||
if (ctx.session.captcha_data!.tries_failed > CAPTCHA_FAILS_LIMIT) {
|
if (ctx.session.captcha_data!.tries_failed > CAPTCHA_FAILS_LIMIT) {
|
||||||
ctx.reply(LangManager.getLang(ctx.msg.from.language_code)
|
ctx.reply(LangManager.getLang(ctx.msg.from.language_code)
|
||||||
.replies.captcha.failed(TIMEOUT_AFTER_FAIL_MINS))
|
.replies.captcha.failed(TIMEOUT_AFTER_FAIL_MINS))
|
||||||
ctx.api.deleteMessage(ctx.chatId, ctx.session.captcha_data!.message_id).catch()
|
await ctx.api.deleteMessage(ctx.chatId, ctx.session.captcha_data!.message_id)
|
||||||
// TODO: Add timeout
|
// TODO: Add timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
|
import { ChatMember } from "https://deno.land/x/grammy_types/manage.ts";
|
||||||
import { Bot } from "https://deno.land/x/grammy/mod.ts";
|
import { Bot } from "https://deno.land/x/grammy/mod.ts";
|
||||||
import { Kysely } from 'npm:kysely';
|
import { Kysely } from 'npm:kysely';
|
||||||
import { Ctx } from "../ctx.ts";
|
import { Ctx } from "../ctx.ts";
|
||||||
import { CompiledConfig } from "../../cfg/config.ts";
|
|
||||||
import { Database } from "../../repo/exports.ts";
|
import { Database } from "../../repo/exports.ts";
|
||||||
import { checkUser, checkUserOnNewChatMember, onMemberLeftChat } from "./user_managment.ts";
|
import { CompiledConfig } from "../../cfg/config.ts";
|
||||||
|
import { checkUser, checkUserOnNewChatMember, onMemberLeftChat, checkUserOnStart } from "./user_managment.ts";
|
||||||
import { checkCaptchaSolution, initUserCaptcha } from "./captcha.ts";
|
import { checkCaptchaSolution, initUserCaptcha } from "./captcha.ts";
|
||||||
import { checkUserOnStart } from "./user_managment.ts";
|
|
||||||
import { ChatMember } from "https://deno.land/x/grammy_types@v3.14.0/manage.ts";
|
|
||||||
|
|
||||||
interface ChatMemberUpdateStatus {
|
interface ChatMemberUpdateStatus {
|
||||||
joined: boolean,
|
joined: boolean,
|
||||||
@ -17,68 +16,64 @@ interface ChatMemberUpdateStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getChatMemberUpdateStatus(oldMember: ChatMember, newMember: ChatMember): ChatMemberUpdateStatus {
|
function getChatMemberUpdateStatus(oldMember: ChatMember, newMember: ChatMember): ChatMemberUpdateStatus {
|
||||||
return {
|
return {
|
||||||
joined: oldMember.status === "left" &&
|
joined: oldMember.status === "left" &&
|
||||||
newMember.status !== "left" && newMember.status !== "kicked",
|
newMember.status !== "left" && newMember.status !== "kicked",
|
||||||
left: oldMember.status !== "left" && newMember.status === "left",
|
left: oldMember.status !== "left" && newMember.status === "left",
|
||||||
kicked: newMember.status === "kicked",
|
kicked: newMember.status === "kicked",
|
||||||
restricted: oldMember.status !== "restricted" && newMember.status === "restricted",
|
restricted: oldMember.status !== "restricted" && newMember.status === "restricted",
|
||||||
roleChanged: oldMember.status !== newMember.status &&
|
roleChanged: oldMember.status !== newMember.status &&
|
||||||
(newMember.status !== "left" && newMember.status !== "kicked")
|
(newMember.status !== "left" && newMember.status !== "kicked")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const init = (bot: Bot<Ctx>, db: Kysely<Database>, cfg: CompiledConfig) => {
|
export const init = (bot: Bot<Ctx>, db: Kysely<Database>, cfg: CompiledConfig) => {
|
||||||
console.log(`initializing normal mode`)
|
console.log(`initializing normal mode`)
|
||||||
const { botCfg } = cfg
|
const { botCfg } = cfg
|
||||||
|
|
||||||
bot.on('message', async (ctx, next) => {
|
bot.on('message', async (ctx, next) => {
|
||||||
if (ctx.from?.is_bot) return
|
if (ctx.from?.is_bot) return
|
||||||
console.log(`Chat ID: ${ctx.msg.chat.id} From: ${ctx.from.id} Message: ${ctx.msg.text}`)
|
console.log(`Chat ID: ${ctx.msg.chat.id} From: ${ctx.from.id} Message: ${ctx.msg.text}`)
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.on('message:text', async (ctx, next) => {
|
bot.on('message:text', async (ctx, next) => {
|
||||||
if (ctx.chat.id === botCfg.chat_id) {
|
if (ctx.chat.id === botCfg.chat_id) {
|
||||||
await checkUser(ctx, db, botCfg, true)
|
await checkUser(ctx, db, botCfg, true)
|
||||||
} else if (ctx.message.chat.type == "private" && ctx.session.captcha_data) {
|
} else if (ctx.message.chat.type == "private" && ctx.session.captcha_data) {
|
||||||
await checkCaptchaSolution(ctx, db, botCfg)
|
await checkCaptchaSolution(ctx, db, botCfg)
|
||||||
} else {
|
} else {
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.command('start', async ctx => {
|
bot.command('start', async ctx => {
|
||||||
const userInfo = await checkUserOnStart(ctx, db)
|
const userInfo = await checkUserOnStart(ctx, db)
|
||||||
if (userInfo.isBlocked || userInfo.isTimeout) return
|
if (userInfo.isBlocked || userInfo.isTimeout) return
|
||||||
|
// TODO: if (userInfo.isNewUser) { /* Some hello message? */ }
|
||||||
|
await initUserCaptcha(ctx, db, userInfo, botCfg)
|
||||||
|
})
|
||||||
|
|
||||||
// TODO: if (userInfo.isNewUser) { /* Some hello message? */ }
|
bot.on('chat_member', async (ctx, next) => {
|
||||||
if (!ctx.session.captcha_data) {
|
if (ctx.from.is_bot) return
|
||||||
await initUserCaptcha(ctx, db, userInfo, botCfg)
|
if (ctx.chat.id !== botCfg.chat_id) return
|
||||||
}
|
const { old_chat_member, new_chat_member } = ctx.chatMember
|
||||||
})
|
const chatMemberUpdateStatus = getChatMemberUpdateStatus(old_chat_member, new_chat_member)
|
||||||
|
|
||||||
bot.on('chat_member', async (ctx, next) => {
|
if (chatMemberUpdateStatus.joined) {
|
||||||
if (ctx.from.is_bot) return
|
console.log(`New Chat member ${ctx.chatMember.from.id}:'+
|
||||||
if (ctx.chat.id !== botCfg.chat_id) return
|
'${ctx.from.first_name} ${ctx.from.last_name}`)
|
||||||
const chatMemberUpdateStatus = getChatMemberUpdateStatus(
|
await checkUserOnNewChatMember(ctx, db, botCfg)
|
||||||
ctx.chatMember.old_chat_member, ctx.chatMember.new_chat_member
|
// TODO:
|
||||||
)
|
// const userInfo = await checkUserOnNewChatMember(ctx, db, botCfg)
|
||||||
|
//if (userInfo.isNewUser) { /* Some hello message? */ }
|
||||||
if (chatMemberUpdateStatus.joined) {
|
} else if (chatMemberUpdateStatus.left) {
|
||||||
console.log(`New Chat member ${ctx.chatMember.from.id}:'+
|
console.log(`Chat member left ${ctx.chatMember.from.id}:'+
|
||||||
'${ctx.from.first_name} ${ctx.from.last_name}`)
|
'${ctx.from.first_name} ${ctx.from.last_name}`)
|
||||||
await checkUserOnNewChatMember(ctx, db, botCfg)
|
await onMemberLeftChat(ctx, db)
|
||||||
// TODO:
|
// TODO: /* POBEG S DURKI! */
|
||||||
// const userInfo = await checkUserOnNewChatMember(ctx, db, botCfg)
|
}
|
||||||
//if (userInfo.isNewUser) { /* Some hello message? */ }
|
await next()
|
||||||
} else if (chatMemberUpdateStatus.left) {
|
})
|
||||||
console.log(`Chat member left ${ctx.chatMember.from.id}:'+
|
}
|
||||||
'${ctx.from.first_name} ${ctx.from.last_name}`)
|
|
||||||
await onMemberLeftChat(ctx, db)
|
|
||||||
// TODO: /* POBEG S DURKI! */
|
|
||||||
}
|
|
||||||
await next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -18,169 +18,188 @@ export interface CheckUserOut extends UserCheckedRestrictions {
|
|||||||
// Creates new user if it doesn't exist yet
|
// Creates new user if it doesn't exist yet
|
||||||
// Ignores the start command
|
// Ignores the start command
|
||||||
export const checkUser = async (
|
export const checkUser = async (
|
||||||
ctx: Ctx,
|
ctx: Ctx,
|
||||||
db: Kysely<Database>,
|
db: Kysely<Database>,
|
||||||
cfg: BotConfig,
|
cfg: BotConfig,
|
||||||
isMicChat?: boolean,
|
isMicChat?: boolean,
|
||||||
): Promise<CheckUserOut> => {
|
): Promise<CheckUserOut> => {
|
||||||
const out: CheckUserOut = {
|
const out: CheckUserOut = {
|
||||||
isBlocked: false,
|
isBlocked: false,
|
||||||
isTimeout: false,
|
isTimeout: false,
|
||||||
isNewUser: false,
|
isNewUser: false,
|
||||||
isChatParticipant: true,
|
isChatParticipant: true,
|
||||||
isCaptchaSolved: false,
|
isCaptchaSolved: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.chat || !ctx.from) return out
|
||||||
|
isMicChat ??= ctx.chat.id === cfg.chat_id
|
||||||
|
|
||||||
|
await db.transaction().execute(async trx => {
|
||||||
|
const users = await trx.selectFrom('users')
|
||||||
|
.selectAll().where('tg_id', '=', ctx.from!.id).execute()
|
||||||
|
if (users.length < 1) {
|
||||||
|
await trx.insertInto('users').values({
|
||||||
|
tg_id: ctx.from!.id,
|
||||||
|
joined_by_referal_from_user_id: null,
|
||||||
|
is_chat_participant: isMicChat,
|
||||||
|
is_captcha_passed: isMicChat,
|
||||||
|
joined_chat_at: new Date,
|
||||||
|
timeout_until: null
|
||||||
|
}).execute()
|
||||||
|
out.isNewUser = true
|
||||||
|
out.isChatParticipant = isMicChat
|
||||||
|
out.isCaptchaSolved = out.isChatParticipant
|
||||||
|
} else {
|
||||||
|
const user = users[0]
|
||||||
|
const userRestrictions = await checkUserRestrictions(db, user)
|
||||||
|
|
||||||
|
if (!user.is_chat_participant && isMicChat) {
|
||||||
|
await trx.updateTable('users').set({
|
||||||
|
is_chat_participant: true,
|
||||||
|
is_captcha_passed: true,
|
||||||
|
}).execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
out.isBlocked = userRestrictions.isBlocked
|
||||||
|
out.isTimeout = userRestrictions.isTimeout
|
||||||
|
out.isChatParticipant = user.is_chat_participant || isMicChat
|
||||||
|
out.isCaptchaSolved = user.is_captcha_passed
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (!ctx.chat || !ctx.from) return out
|
return out
|
||||||
isMicChat ??= ctx.chat.id === cfg.chat_id
|
|
||||||
|
|
||||||
await db.transaction().execute(async trx => {
|
|
||||||
const users = await trx.selectFrom('users')
|
|
||||||
.selectAll().where('tg_id', '=', ctx.from!.id).execute()
|
|
||||||
if (users.length < 1) {
|
|
||||||
await trx.insertInto('users').values({
|
|
||||||
tg_id: ctx.from!.id,
|
|
||||||
joined_by_referal_from_user_id: null,
|
|
||||||
is_chat_participant: isMicChat,
|
|
||||||
is_captcha_passed: isMicChat,
|
|
||||||
joined_chat_at: new Date,
|
|
||||||
timeout_until: null
|
|
||||||
}).execute()
|
|
||||||
out.isNewUser = true
|
|
||||||
out.isChatParticipant = isMicChat
|
|
||||||
out.isCaptchaSolved = out.isChatParticipant
|
|
||||||
} else {
|
|
||||||
const user = users[0]
|
|
||||||
const userRestrictions = await checkUserRestrictions(db, user)
|
|
||||||
|
|
||||||
if (!user.is_chat_participant && isMicChat) {
|
|
||||||
await trx.updateTable('users').set({
|
|
||||||
is_chat_participant: true,
|
|
||||||
is_captcha_passed: true,
|
|
||||||
}).execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
out.isBlocked = userRestrictions.isBlocked
|
|
||||||
out.isTimeout = userRestrictions.isTimeout
|
|
||||||
out.isChatParticipant = user.is_chat_participant || isMicChat
|
|
||||||
out.isCaptchaSolved = user.is_captcha_passed
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface CheckUserOnNewChatMemberOut extends CheckUserOut {
|
interface CheckUserOnNewChatMemberOut extends CheckUserOut {
|
||||||
isExpectedUserJoined: boolean
|
isExpectedUserJoined: boolean
|
||||||
expectedUserTgId: number
|
expectedUserTgId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extends checkUser function with checking invite link functionality
|
// Extends checkUser function with checking invite link functionality
|
||||||
export const checkUserOnNewChatMember = async (
|
export const checkUserOnNewChatMember = async (
|
||||||
ctx: Filter<Ctx, "chat_member">,
|
ctx: Filter<Ctx, "chat_member">,
|
||||||
db: Kysely<Database> | Transaction<Database>,
|
db: Kysely<Database> | Transaction<Database>,
|
||||||
cfg: BotConfig
|
cfg: BotConfig
|
||||||
): Promise<CheckUserOnNewChatMemberOut> => {
|
): Promise<CheckUserOnNewChatMemberOut> => {
|
||||||
const out: CheckUserOnNewChatMemberOut = {
|
const out: CheckUserOnNewChatMemberOut = {
|
||||||
isBlocked: false,
|
isBlocked: false,
|
||||||
isTimeout: false,
|
isTimeout: false,
|
||||||
isNewUser: false,
|
isNewUser: false,
|
||||||
isCaptchaSolved: false,
|
isCaptchaSolved: false,
|
||||||
isExpectedUserJoined: true,
|
isExpectedUserJoined: true,
|
||||||
isChatParticipant: true,
|
isChatParticipant: true,
|
||||||
expectedUserTgId: -1,
|
expectedUserTgId: -1,
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInfo = await checkUser(ctx, db, cfg, true)
|
let userInfo: CheckUserOut
|
||||||
if (userInfo.isBlocked) {
|
await db.transaction().execute(async trx => {
|
||||||
ctx.banChatMember(ctx.chatMember.from.id).catch(
|
userInfo = await checkUser(ctx, trx, cfg, true)
|
||||||
err => console.log(err)
|
if (userInfo.isBlocked || userInfo.isNewUser) {
|
||||||
)
|
return
|
||||||
out.isBlocked = true
|
|
||||||
out.isChatParticipant = false
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
out.isTimeout = userInfo.isTimeout
|
|
||||||
out.isCaptchaSolved = userInfo.isCaptchaSolved
|
|
||||||
out.isNewUser = userInfo.isNewUser
|
|
||||||
|
|
||||||
if (!ctx.chatMember.invite_link) return out
|
|
||||||
const { invite_link } = ctx.chatMember.invite_link
|
|
||||||
|
|
||||||
const inviteLinksData = await db.selectFrom('invite_links')
|
|
||||||
.select('expect_user_tg_id')
|
|
||||||
.where('link', '=', invite_link).execute()
|
|
||||||
if (inviteLinksData.length === 0) {
|
|
||||||
out.expectedUserTgId = -1
|
|
||||||
} else {
|
|
||||||
const { expect_user_tg_id } = inviteLinksData[0]
|
|
||||||
out.isExpectedUserJoined = expect_user_tg_id === ctx.chatMember.from.id
|
|
||||||
out.expectedUserTgId = expect_user_tg_id
|
|
||||||
db.deleteFrom('invite_links').where('link', '=', invite_link).execute()
|
|
||||||
ctx.revokeChatInviteLink(invite_link)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!out.isExpectedUserJoined) {
|
|
||||||
db.updateTable('users').where(eb => eb.or([
|
|
||||||
eb('tg_id', '=', out.expectedUserTgId),
|
|
||||||
eb('tg_id', '=', ctx.chatMember.from.id),
|
|
||||||
])).execute()
|
|
||||||
ctx.banChatMember(ctx.chatMember.from.id).catch()
|
|
||||||
}
|
}
|
||||||
|
await trx.updateTable('users').where('tg_id', '=', ctx.from.id).set({
|
||||||
|
is_chat_participant: true
|
||||||
|
}).execute()
|
||||||
|
})
|
||||||
|
userInfo = userInfo!
|
||||||
|
|
||||||
|
if (userInfo.isBlocked) {
|
||||||
|
await ctx.banChatMember(ctx.chatMember.from.id).catch(
|
||||||
|
err => console.log(err)
|
||||||
|
)
|
||||||
|
out.isBlocked = true
|
||||||
|
out.isChatParticipant = false
|
||||||
return out
|
return out
|
||||||
|
}
|
||||||
|
out.isTimeout = userInfo.isTimeout
|
||||||
|
out.isCaptchaSolved = userInfo.isCaptchaSolved
|
||||||
|
out.isNewUser = userInfo.isNewUser
|
||||||
|
|
||||||
|
if (!ctx.chatMember.invite_link) return out
|
||||||
|
const { invite_link } = ctx.chatMember.invite_link
|
||||||
|
|
||||||
|
const inviteLinksData = await db.selectFrom('invite_links')
|
||||||
|
.select('expect_user_tg_id')
|
||||||
|
.where('link', '=', invite_link).execute()
|
||||||
|
if (inviteLinksData.length === 0) {
|
||||||
|
out.expectedUserTgId = -1
|
||||||
|
} else {
|
||||||
|
const { expect_user_tg_id } = inviteLinksData[0]
|
||||||
|
out.isExpectedUserJoined = expect_user_tg_id === ctx.chatMember.from.id
|
||||||
|
out.expectedUserTgId = expect_user_tg_id
|
||||||
|
db.deleteFrom('invite_links').where('link', '=', invite_link).execute()
|
||||||
|
ctx.revokeChatInviteLink(invite_link)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.isExpectedUserJoined) {
|
||||||
|
db.updateTable('users').where(eb => eb.or([
|
||||||
|
eb('tg_id', '=', out.expectedUserTgId),
|
||||||
|
eb('tg_id', '=', ctx.chatMember.from.id),
|
||||||
|
])).execute()
|
||||||
|
ctx.banChatMember(ctx.chatMember.from.id).catch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export interface CheckUserOnStartOut extends CheckUserOut {
|
||||||
|
activeInviteLink: string | null
|
||||||
|
}
|
||||||
export const checkUserOnStart = async (
|
export const checkUserOnStart = async (
|
||||||
ctx: CommandContext<Ctx>,
|
ctx: CommandContext<Ctx>,
|
||||||
db: Kysely<Database>,
|
db: Kysely<Database>,
|
||||||
): Promise<CheckUserOut> => {
|
): Promise<CheckUserOnStartOut> => {
|
||||||
let isBlocked: boolean = false
|
let isBlocked: boolean = false
|
||||||
let isTimeout: boolean = false
|
let isTimeout: boolean = false
|
||||||
let isNewUser: boolean = false
|
let isNewUser: boolean = false
|
||||||
let isChatParticipant: boolean = false
|
let isChatParticipant: boolean = false
|
||||||
let isCaptchaSolved: boolean = false
|
let isCaptchaSolved: boolean = false
|
||||||
|
let activeInviteLink: string | null = null
|
||||||
|
|
||||||
await db.transaction().execute(async trx => {
|
await db.transaction().execute(async trx => {
|
||||||
const users = await trx.selectFrom('users')
|
const users = await trx.selectFrom('users')
|
||||||
.select([
|
.where('tg_id', '=', ctx.from!.id)
|
||||||
'id',
|
.leftJoin('invite_links as il', join => join.onRef(
|
||||||
'tg_id',
|
'il.expect_user_tg_id', '=', 'users.tg_id'
|
||||||
'is_captcha_passed',
|
)).selectAll().execute()
|
||||||
'is_banned',
|
if (users.length < 1) {
|
||||||
'is_timeout',
|
// TODO -- referal links support
|
||||||
'is_chat_participant',
|
// const referal = ctx.match
|
||||||
'timeout_until'
|
|
||||||
])
|
|
||||||
.where('tg_id', '=', ctx.msg.from!.id).execute()
|
|
||||||
if (users.length < 1) {
|
|
||||||
// TODO -- referal links support
|
|
||||||
// const referal = ctx.match
|
|
||||||
|
|
||||||
await trx.insertInto('users').values({
|
await trx.insertInto('users').values({
|
||||||
tg_id: ctx.msg.from?.id
|
tg_id: ctx.from!.id
|
||||||
}).execute()
|
}).execute()
|
||||||
isNewUser = true
|
isNewUser = true
|
||||||
} else {
|
} else {
|
||||||
const user = users[0]
|
const now = new Date()
|
||||||
const userRestrictions = await checkUserRestrictions(db, user)
|
const user = users[0]
|
||||||
isBlocked = userRestrictions.isBlocked
|
const userRestrictions = await checkUserRestrictions(db, user)
|
||||||
isTimeout = userRestrictions.isTimeout
|
isBlocked = userRestrictions.isBlocked
|
||||||
isChatParticipant = user.is_chat_participant
|
isTimeout = userRestrictions.isTimeout
|
||||||
isCaptchaSolved = user.is_captcha_passed
|
isChatParticipant = user.is_chat_participant
|
||||||
}
|
isCaptchaSolved = user.is_captcha_passed
|
||||||
})
|
// TODO: move invite link related values
|
||||||
return {
|
// (eg. valid_until and link) into it's own namespace
|
||||||
isBlocked,
|
// if possible
|
||||||
isTimeout,
|
if (user.valid_until && user.valid_until < now) {
|
||||||
isNewUser,
|
await trx.deleteFrom('invite_links').where('link', '=', user.link).execute()
|
||||||
isChatParticipant,
|
} else {
|
||||||
isCaptchaSolved
|
activeInviteLink = user.link
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
isBlocked,
|
||||||
|
isTimeout,
|
||||||
|
isNewUser,
|
||||||
|
isChatParticipant,
|
||||||
|
isCaptchaSolved,
|
||||||
|
activeInviteLink
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -192,4 +211,4 @@ export const onMemberLeftChat = async (
|
|||||||
.where('tg_id', '=', ctx.chatMember.from.id).set({
|
.where('tg_id', '=', ctx.chatMember.from.id).set({
|
||||||
is_chat_participant: false
|
is_chat_participant: false
|
||||||
}).execute()
|
}).execute()
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,11 @@ import { CompiledConfig } from "../../cfg/config.ts";
|
|||||||
import { Ctx } from "../ctx.ts";
|
import { Ctx } from "../ctx.ts";
|
||||||
|
|
||||||
export const init = (bot: Bot<Ctx>, _: CompiledConfig) => {
|
export const init = (bot: Bot<Ctx>, _: CompiledConfig) => {
|
||||||
bot.command("getchat", ctx => {
|
bot.command("getchat", async ctx => {
|
||||||
if (!ctx.from || !ctx.message) return
|
if (!ctx.from || !ctx.message) return
|
||||||
if (ctx.message.chat.type != "group"
|
if (ctx.message.chat.type != "group"
|
||||||
&& ctx.message.chat.type != "supergroup") return
|
&& ctx.message.chat.type != "supergroup") return
|
||||||
ctx.deleteMessage()
|
await ctx.deleteMessage()
|
||||||
console.log(`Chat ${ctx.message.chat.title} ID: ${ctx.message.chat.id}`)
|
console.log(`Chat ${ctx.message.chat.title} ID: ${ctx.message.chat.id}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -15,4 +15,4 @@ export const init = (bot: Bot<Ctx>, _: CompiledConfig) => {
|
|||||||
if (!ctx.from) return
|
if (!ctx.from) return
|
||||||
console.log(`User ${ctx.from.first_name} ${ctx.from.last_name} ID: ${ctx.from.id}`)
|
console.log(`User ${ctx.from.first_name} ${ctx.from.last_name} ID: ${ctx.from.id}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -39,4 +39,4 @@ export const loadConfig = (): CompiledConfig => {
|
|||||||
path.join(import.meta.dirname, 'migrations') : '/app/migrations',
|
path.join(import.meta.dirname, 'migrations') : '/app/migrations',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,4 +40,4 @@ export const getActiveInviteLink = async (
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
return link.link
|
return link.link
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"redirects": {
|
"redirects": {
|
||||||
"https://deno.land/x/grammy/mod.ts": "https://deno.land/x/grammy@v1.30.0/mod.ts"
|
"https://deno.land/x/grammy/mod.ts": "https://deno.land/x/grammy@v1.30.0/mod.ts",
|
||||||
|
"https://deno.land/x/grammy_types/manage.ts": "https://deno.land/x/grammy_types@v3.14.0/manage.ts"
|
||||||
},
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/dialect/database-introspector.js": "dce8b6ada28af6b75864ca3fcc2c61bbacb8976def6f715b2f96c5aa7a9331ce",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/dialect/database-introspector.js": "dce8b6ada28af6b75864ca3fcc2c61bbacb8976def6f715b2f96c5aa7a9331ce",
|
||||||
@ -193,7 +194,6 @@
|
|||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-builder.js": "da9c44985b130c4d9ebd2dabff36b66e748e5afa8a785313e0d9318967ef24d7",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-builder.js": "da9c44985b130c4d9ebd2dabff36b66e748e5afa8a785313e0d9318967ef24d7",
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-wrapper.js": "35742f42558a1cbb369ecf7b3cb863f4486fa0c2384fb23e278244440f3c12c9",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression-wrapper.js": "35742f42558a1cbb369ecf7b3cb863f4486fa0c2384fb23e278244440f3c12c9",
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression.js": "7c024b0c9f292bbd0aefe486c81328d7b18ece24cb569afc3171e06035f4f970",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/expression/expression.js": "7c024b0c9f292bbd0aefe486c81328d7b18ece24cb569afc3171e06035f4f970",
|
||||||
"npm:kysely": "ba2b09d8e5e4ae121ef77cfd971477858381650c26bcf28dfd3a8e5ed0535e8a",
|
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/kysely.js": "d6b10df0da4bb8d853d1ff946b8a1a5e4c254c7a2e26d3a5ecacce82c9e3c7eb",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/kysely.js": "d6b10df0da4bb8d853d1ff946b8a1a5e4c254c7a2e26d3a5ecacce82c9e3c7eb",
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/file-migration-provider.js": "04be6f4d0bb587f254b270875b529bbf30bbdf19af2d95b06272cd5e6b01e56e",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/file-migration-provider.js": "04be6f4d0bb587f254b270875b529bbf30bbdf19af2d95b06272cd5e6b01e56e",
|
||||||
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/migrator.js": "35c171e46ae9d18b9b9459148f626be5bc88f22ee5f7844b9b6818dd53ecb77a",
|
"https://cdn.jsdelivr.net/npm/kysely/dist/esm/migration/migrator.js": "35c171e46ae9d18b9b9459148f626be5bc88f22ee5f7844b9b6818dd53ecb77a",
|
||||||
@ -530,7 +530,8 @@
|
|||||||
"https://deno.land/x/postgres@v0.17.0/query/transaction.ts": "8e75c3ce0aca97da7fe126e68f8e6c08d640e5c8d2016e62cee5c254bebe7fe8",
|
"https://deno.land/x/postgres@v0.17.0/query/transaction.ts": "8e75c3ce0aca97da7fe126e68f8e6c08d640e5c8d2016e62cee5c254bebe7fe8",
|
||||||
"https://deno.land/x/postgres@v0.17.0/query/types.ts": "a6dc8024867fe7ccb0ba4b4fa403ee5d474c7742174128c8e689c3b5e5eaa933",
|
"https://deno.land/x/postgres@v0.17.0/query/types.ts": "a6dc8024867fe7ccb0ba4b4fa403ee5d474c7742174128c8e689c3b5e5eaa933",
|
||||||
"https://deno.land/x/postgres@v0.17.0/utils/deferred.ts": "dd94f2a57355355c47812b061a51b55263f72d24e9cb3fdb474c7519f4d61083",
|
"https://deno.land/x/postgres@v0.17.0/utils/deferred.ts": "dd94f2a57355355c47812b061a51b55263f72d24e9cb3fdb474c7519f4d61083",
|
||||||
"https://deno.land/x/postgres@v0.17.0/utils/utils.ts": "19c3527ddd5c6c4c49ae36397120274c7f41f9d3cbf479cb36065d23329e9f90"
|
"https://deno.land/x/postgres@v0.17.0/utils/utils.ts": "19c3527ddd5c6c4c49ae36397120274c7f41f9d3cbf479cb36065d23329e9f90",
|
||||||
|
"npm:kysely": "ba2b09d8e5e4ae121ef77cfd971477858381650c26bcf28dfd3a8e5ed0535e8a"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
Loading…
Reference in New Issue
Block a user