@softvisio/core
Version:
Softisio core
389 lines (291 loc) • 11.8 kB
JavaScript
import sql from "#lib/sql";
const NOT_IN_CHAT_MESSAGE_ID = "notInChatMessageId";
const SQL = {
"getGroup": sql`SELECT telegram_group_id FROM telegram_bot_forum_chat WHERE telegram_bot_id = ?`.prepare(),
"createUserTopic": sql`INSERT INTO telegram_bot_forum_chat_group_topic ( telegram_group_id, topic_id, telegram_user_id, name ) VALUES ( ?, ?, ?, ? ) RETURNING topic_id, telegram_user_id, name`.prepare(),
"updateUserTopicName": sql`UPDATE telegram_bot_forum_chat_group_topic SET name = ? WHERE telegram_group_id = ? AND telegram_user_id = ?`,
"getUserTopic": sql`SELECT topic_id, telegram_user_id, name FROM telegram_bot_forum_chat_group_topic WHERE telegram_group_id = ? AND telegram_user_id = ?`.prepare(),
"getTopicUser": sql`SELECT topic_id, telegram_user_id, name FROM telegram_bot_forum_chat_group_topic WHERE telegram_group_id = ? AND topic_id = ?`.prepare(),
};
export default class {
#bot;
#groupId;
#initialized;
#inviteLink;
#userTopic = {};
#topicUser = {};
#command;
constructor ( bot ) {
this.#bot = bot;
}
// proprties
get app () {
return this.#bot.app;
}
get bot () {
return this.#bot;
}
get dbh () {
return this.#bot.dbh;
}
get groupId () {
return this.#groupId;
}
get inviteLink () {
return this.#inviteLink;
}
// public
async init () {
this.bot.dbhEvents.on( "disconnect", this.#clear.bind( this ) );
const res = await this.dbh.selectRow( SQL.getGroup, [ this.bot.id ] );
if ( !res.ok ) return res;
if ( !res.data ) return result( [ 500, "Forum chat group is not set" ] );
this.#groupId = res.data.telegram_group_id;
const group = await this.getGroup();
if ( group ) {
const res = await this.#initGroup();
if ( !res.ok ) return res;
}
return result( 200 );
}
async getGroup ( { dbh } = {} ) {
return this.bot.groups.getTelegramGroupById( this.#groupId, { dbh } );
}
async getUserTopic ( user ) {
var topic = this.#userTopic[ user.id ];
GET_TOPIC: if ( !topic ) {
const group = await this.getGroup();
if ( !group ) break GET_TOPIC;
let res;
res = await this.dbh.selectRow( SQL.getUserTopic, [ group.id, user.id ] );
if ( !res.ok ) {
break GET_TOPIC;
}
else if ( res.data ) {
topic = this.#cacheUserTopic( group, res.data );
}
else {
res = await group.send( "createForumTopic", {
"name": user.username,
} );
if ( !res.ok ) break GET_TOPIC;
const topicId = res.data.message_thread_id;
res = await this.dbh.selectRow( SQL.createUserTopic, [ group.id, topicId, user.id, user.username ] );
if ( !res.ok ) break GET_TOPIC;
topic = this.#cacheUserTopic( group, res.data );
}
}
if ( !topic ) return;
if ( topic.name !== user.username ) await this.#updateUserTopicName( user );
return topic.topicId;
}
async getTopicUser ( topicId ) {
var topic = this.#topicUser[ topicId ];
GET_TOPIC: if ( !topic ) {
const group = await this.getGroup();
if ( !group ) break GET_TOPIC;
const res = await this.dbh.selectRow( SQL.getTopicUser, [ group.id, topicId ] );
if ( !res.data ) break GET_TOPIC;
topic = this.#cacheUserTopic( group, res.data );
}
if ( !topic ) return;
const user = this.bot.users.getTelegramUserById( topic.userId );
if ( !user ) return;
if ( topic.name !== user.username ) await this.#updateUserTopicName( user );
return user;
}
setCommand ( command ) {
if ( this.#command ) return result( [ 500, "Duplicate forum chat command" ] );
this.#command = command;
return result( 200 );
}
async runSupergroupRequest ( ctx, req ) {
if ( !this.#initialized ) await this.#initGroup();
// group join request
if ( req.isChatJoinRequest ) {
return this.#precessChatJoinRequest( ctx, req );
}
// message
else if ( req.isMessage ) {
return this.#forwardMessage( ctx, req );
}
}
async deleteNotInChatMessage ( user ) {
if ( !user.state.forumChat?.[ NOT_IN_CHAT_MESSAGE_ID ] ) return;
await user.send( "deleteMessage", {
"message_id": user.state.forumChat?.[ NOT_IN_CHAT_MESSAGE_ID ],
} );
await user.updateState( {
"forumChat": {
[ NOT_IN_CHAT_MESSAGE_ID ]: undefined,
},
} );
}
// private
async #initGroup () {
const group = await this.getGroup();
// group not found
if ( !group ) return result( 404 );
var res;
res = await this.dbh.selectRow( sql`SELECT * FROM telegram_bot_forum_chat_group WHERE telegram_group_id = ?`, [ this.#groupId ] );
if ( !res.ok ) return res;
if ( res.data ) {
this.#inviteLink = res.data.invile_link;
var generalTopicHidden = res.data.general_topic_hidden,
chatPermissionsSet = res.data.chat_permissions_set;
}
else {
res = await this.dbh.do( sql`INSERT INTO telegram_bot_forum_chat_group ( telegram_group_id ) VALUES ( ? )`, [ this.#groupId ] );
if ( !res.ok ) return res;
}
// hide general topic
if ( this.bot.config.forumChat.hideGeneralTopic && !generalTopicHidden ) {
res = await group.send( "hideGeneralForumTopic" );
if ( !res.ok ) return res;
res = await this.dbh.do( sql`UPDATE telegram_bot_forum_chat_group SET general_topic_hidden = TRUE WHERE telegram_group_id = ?`, [ this.#groupId ] );
if ( !res.ok ) return res;
}
// create invite link
if ( !this.#inviteLink ) {
res = await group.send( "createChatInviteLink", {
"creates_join_request": true,
} );
if ( !res.ok ) return res;
const inviteLink = res.data.invite_link;
res = await this.dbh.do( sql`UPDATE telegram_bot_forum_chat_group SET invile_link = ? WHERE telegram_group_id = ?`, [ inviteLink, this.#groupId ] );
if ( !res.ok ) return res;
this.#inviteLink = inviteLink;
}
// set default roup user permissions
if ( !chatPermissionsSet ) {
res = await group.send( "setChatPermissions", {
"use_independent_chat_permissions": false,
"permissions": {
"can_send_messages": true,
"can_send_audios": true,
"can_send_documents": true,
"can_send_photos": true,
"can_send_videos": true,
"can_send_video_notes": true,
"can_send_voice_notes": true,
"can_send_polls": true,
"can_send_other_messages": true,
"can_add_web_page_previews": true,
"can_change_info": false,
"can_invite_users": false,
"can_pin_messages": true,
"can_manage_topics": false,
},
} );
if ( !res.ok ) return res;
res = await this.dbh.do( sql`UPDATE telegram_bot_forum_chat_group SET chat_permissions_set = TRUE WHERE telegram_group_id = ?`, [ this.#groupId ] );
if ( !res.ok ) return res;
}
this.#initialized = true;
return result( 200 );
}
#clear () {
this.#userTopic = {};
this.#topicUser = {};
}
#cacheUserTopic ( group, fields ) {
const topic = {
"groupId": group.id,
"topicId": fields.topic_id,
"userId": fields.telegram_user_id,
"name": fields.name,
};
this.#topicUser[ topic.topicId ] = topic;
this.#userTopic[ topic.userId ] = topic;
return topic;
}
async #updateUserTopicName ( user ) {
const topic = this.#userTopic[ user.id ];
if ( !topic ) return;
const name = user.username;
if ( topic.name === name ) return;
const group = await this.getGroup();
if ( !group ) return;
var res;
res = await group.send( "editForumTopic", {
"message_thread_id": topic.topicId,
name,
} );
if ( !res.ok ) return;
res = await this.dbh.do( SQL.updateUserTopicName, [ name, group.id, user.id ] );
if ( !res.ok ) return;
if ( !res.meta.rows ) return;
if ( this.#userTopic[ user.id ]?.groupId === group.id ) {
this.#userTopic[ user.id ].name = name;
this.#topicUser[ topic.topicId ].name = name;
}
}
async #precessChatJoinRequest ( ctx, req ) {
const userId = req.from.id,
user = await this.bot.users.getTelegramUserById( userId );
const permissions = await user?.getPermissions();
var res;
// approve
if ( permissions?.has( "telegram/bot:read" ) ) {
res = await ctx.group.send( "approveChatJoinRequest", {
"user_id": user.id,
} );
if ( res.ok ) {
res = await user.sendMessage( {
"text": l10nt( "Your join request has been approved." ),
"reply_markup": {
"inline_keyboard": [
[
{
"text": l10nt( "Open chats" ),
"url": this.inviteLink,
},
],
],
},
} );
}
else {
res = await user.sendText( l10nt( "Some error occured. Please, try again." ) );
}
}
// decline
else {
res = await ctx.group.send( "declineChatJoinRequest", {
"user_id": userId,
} );
if ( user ) {
res = await user.sendText( l10nt( "Your group join request has been declined." ) );
}
}
}
async #forwardMessage ( ctx, req ) {
const user = await this.getTopicUser( req.data.message_thread_id );
if ( !user ) return;
await user.send( "copyMessage", {
"from_chat_id": ctx.group.id,
"message_id": req.data.message_id,
} );
if ( this.#command && this.#command !== user.state.command.id ) {
await this.deleteNotInChatMessage( user );
const res = await user.sendMessage( {
"text": l10nt( "To reply, please, go to the chat." ),
"reply_markup": {
"inline_keyboard": [
[
{
"text": l10nt( "Go to the chat" ),
"callback_data": this.#command.createCallbackData( "run", this.#command.id ),
},
],
],
},
} );
await user.updateState( {
"forumChat": {
[ NOT_IN_CHAT_MESSAGE_ID ]: res.data?.message_id,
},
} );
}
}
}