mic-bot/bot/normal_mode/user_managment.ts

215 lines
6.4 KiB
TypeScript
Raw Normal View History

2024-10-23 16:09:10 +00:00
import { CommandContext, Filter } from "https://deno.land/x/grammy/mod.ts";
2024-10-24 08:35:13 +00:00
import { Kysely, Transaction } from 'npm:kysely';
2024-10-23 16:09:10 +00:00
import { Ctx } from "../ctx.ts";
import { Database } from "../../repo/exports.ts";
import { checkUserRestrictions } from "../../core/users.ts";
import { UserCheckedRestrictions } from "../../core/entities.ts";
2024-10-24 08:35:13 +00:00
import { BotConfig } from "../../cfg/config.ts";
2024-10-23 16:09:10 +00:00
export interface CheckUserOut extends UserCheckedRestrictions {
isNewUser: boolean
isChatParticipant: boolean
isCaptchaSolved: boolean
}
// Checks user if it's banned, timeout and if it exists overall
// Creates new user if it doesn't exist yet
// Ignores the start command
export const checkUser = async (
2024-10-25 17:35:12 +00:00
ctx: Ctx,
db: Kysely<Database>,
cfg: BotConfig,
isMicChat?: boolean,
2024-10-23 16:09:10 +00:00
): Promise<CheckUserOut> => {
2024-10-25 17:35:12 +00:00
const out: CheckUserOut = {
isBlocked: false,
isTimeout: false,
isNewUser: false,
isChatParticipant: true,
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
2024-10-23 16:09:10 +00:00
}
2024-10-25 17:35:12 +00:00
})
2024-10-23 16:09:10 +00:00
2024-10-25 17:35:12 +00:00
return out
2024-10-23 16:09:10 +00:00
}
interface CheckUserOnNewChatMemberOut extends CheckUserOut {
2024-10-25 17:35:12 +00:00
isExpectedUserJoined: boolean
expectedUserTgId: number
2024-10-23 16:09:10 +00:00
}
// Extends checkUser function with checking invite link functionality
export const checkUserOnNewChatMember = async (
2024-10-25 17:35:12 +00:00
ctx: Filter<Ctx, "chat_member">,
db: Kysely<Database> | Transaction<Database>,
cfg: BotConfig
2024-10-23 16:09:10 +00:00
): Promise<CheckUserOnNewChatMemberOut> => {
2024-10-25 17:35:12 +00:00
const out: CheckUserOnNewChatMemberOut = {
isBlocked: false,
isTimeout: false,
isNewUser: false,
isCaptchaSolved: false,
isExpectedUserJoined: true,
isChatParticipant: true,
expectedUserTgId: -1,
}
let userInfo: CheckUserOut
await db.transaction().execute(async trx => {
userInfo = await checkUser(ctx, trx, cfg, true)
if (userInfo.isBlocked || userInfo.isNewUser) {
return
2024-10-23 16:09:10 +00:00
}
2024-10-25 17:35:12 +00:00
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
2024-10-23 16:09:10 +00:00
return out
2024-10-25 17:35:12 +00:00
}
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
2024-10-23 16:09:10 +00:00
}
2024-10-25 17:35:12 +00:00
export interface CheckUserOnStartOut extends CheckUserOut {
activeInviteLink: string | null
}
2024-10-23 16:09:10 +00:00
export const checkUserOnStart = async (
2024-10-25 17:35:12 +00:00
ctx: CommandContext<Ctx>,
db: Kysely<Database>,
): Promise<CheckUserOnStartOut> => {
let isBlocked: boolean = false
let isTimeout: boolean = false
let isNewUser: boolean = false
let isChatParticipant: boolean = false
let isCaptchaSolved: boolean = false
let activeInviteLink: string | null = null
await db.transaction().execute(async trx => {
const users = await trx.selectFrom('users')
.where('tg_id', '=', ctx.from!.id)
.leftJoin('invite_links as il', join => join.onRef(
'il.expect_user_tg_id', '=', 'users.tg_id'
)).selectAll().execute()
if (users.length < 1) {
// TODO -- referal links support
// const referal = ctx.match
await trx.insertInto('users').values({
tg_id: ctx.from!.id
}).execute()
isNewUser = true
} else {
const now = new Date()
const user = users[0]
const userRestrictions = await checkUserRestrictions(db, user)
isBlocked = userRestrictions.isBlocked
isTimeout = userRestrictions.isTimeout
isChatParticipant = user.is_chat_participant
isCaptchaSolved = user.is_captcha_passed
// TODO: move invite link related values
// (eg. valid_until and link) into it's own namespace
// if possible
if (user.valid_until && user.valid_until < now) {
await trx.deleteFrom('invite_links').where('link', '=', user.link).execute()
} else {
activeInviteLink = user.link
}
2024-10-23 16:09:10 +00:00
}
2024-10-25 17:35:12 +00:00
})
return {
isBlocked,
isTimeout,
isNewUser,
isChatParticipant,
isCaptchaSolved,
activeInviteLink
}
2024-10-23 16:09:10 +00:00
}
2024-10-24 13:19:25 +00:00
export const onMemberLeftChat = async (
ctx: Filter<Ctx, "chat_member">,
db: Kysely<Database>,
) => {
await db.updateTable('users')
.where('tg_id', '=', ctx.chatMember.from.id).set({
is_chat_participant: false
}).execute()
2024-10-25 17:35:12 +00:00
}