2019-11-24 17:10:29 +00:00
package telegram
2019-11-26 00:04:11 +00:00
import (
2019-12-05 19:56:12 +00:00
"fmt"
2019-12-05 18:13:17 +00:00
"github.com/pkg/errors"
2019-12-04 23:10:08 +00:00
"strconv"
2019-11-26 00:04:11 +00:00
"strings"
2019-12-08 14:24:51 +00:00
"time"
2022-01-08 10:59:57 +00:00
"unicode"
2019-12-04 23:10:08 +00:00
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
2019-12-05 23:21:39 +00:00
log "github.com/sirupsen/logrus"
2022-01-17 20:45:40 +00:00
"github.com/zelenin/go-tdlib/client"
2019-11-26 00:04:11 +00:00
)
2024-02-10 18:46:02 +00:00
const unknownCommand string = "Unknown command"
2019-11-24 17:10:29 +00:00
const notEnoughArguments string = "Not enough arguments"
2023-08-28 14:16:57 +00:00
const TelegramNotInitialized string = "Telegram connection is not initialized yet"
const TelegramAuthDone string = "Authorization is done already"
2019-12-19 20:29:36 +00:00
const notOnline string = "Not online"
2022-02-08 20:25:58 +00:00
2023-08-01 01:25:24 +00:00
var permissionsAdmin = client . ChatAdministratorRights {
2022-02-03 18:51:27 +00:00
CanChangeInfo : true ,
CanPostMessages : true ,
CanEditMessages : true ,
CanDeleteMessages : true ,
CanInviteUsers : true ,
CanRestrictMembers : true ,
CanPinMessages : true ,
CanPromoteMembers : false ,
}
2022-02-01 04:57:17 +00:00
var permissionsMember = client . ChatPermissions {
2023-08-01 01:25:24 +00:00
CanSendBasicMessages : true ,
CanSendAudios : true ,
CanSendDocuments : true ,
CanSendPhotos : true ,
CanSendVideos : true ,
CanSendVideoNotes : true ,
CanSendVoiceNotes : true ,
2022-02-01 04:57:17 +00:00
CanSendPolls : true ,
CanSendOtherMessages : true ,
CanAddWebPagePreviews : true ,
CanChangeInfo : true ,
CanInviteUsers : true ,
CanPinMessages : true ,
2023-08-01 01:25:24 +00:00
CanManageTopics : true ,
2022-02-01 04:57:17 +00:00
}
var permissionsReadonly = client . ChatPermissions { }
2019-11-24 17:10:29 +00:00
2019-11-26 00:04:11 +00:00
var transportCommands = map [ string ] command {
2024-02-01 17:14:06 +00:00
"help" : command { 0 , [ ] string { } , "help" } ,
"login" : command { 1 , [ ] string { "phone" } , "sign in" } ,
"logout" : command { 0 , [ ] string { } , "sign out" } ,
"cancelauth" : command { 0 , [ ] string { } , "quit the signin wizard" } ,
"code" : command { 1 , [ ] string { "xxxxx" } , "check one-time code" } ,
"password" : command { 1 , [ ] string { "********" } , "check 2fa password" } ,
"setusername" : command { 0 , [ ] string { "@username" } , "update @username" } ,
"setname" : command { 1 , [ ] string { "first" , "last" } , "update name" } ,
"setbio" : command { 0 , [ ] string { "Lorem ipsum" } , "update about" } ,
"setpassword" : command { 0 , [ ] string { "old" , "new" } , "set or remove password" } ,
"config" : command { 0 , [ ] string { "param" , "value" } , "view or update configuration options" } ,
"report" : command { 2 , [ ] string { "chat" , "comment" } , "report a chat by id or @username" } ,
"add" : command { 1 , [ ] string { "@username" } , "add @username to your chat list" } ,
"join" : command { 1 , [ ] string { "https://t.me/invite_link" } , "join to chat via invite link or @publicname" } ,
"supergroup" : command { 1 , [ ] string { "title" , "description" } , "create new supergroup «title» with «description»" } ,
"channel" : command { 1 , [ ] string { "title" , "description" } , "create new channel «title» with «description»" } ,
2019-11-26 00:04:11 +00:00
}
var chatCommands = map [ string ] command {
2024-02-01 17:14:06 +00:00
"help" : command { 0 , [ ] string { } , "help" } ,
"d" : command { 0 , [ ] string { "n" } , "delete your last message(s)" } ,
"s" : command { 1 , [ ] string { "edited message" } , "edit your last message" } ,
"silent" : command { 1 , [ ] string { "message" } , "send a message without sound" } ,
"schedule" : command { 2 , [ ] string { "{online | 2006-01-02T15:04:05 | 15:04:05}" , "message" } , "schedules a message either to timestamp or to whenever the user goes online" } ,
"forward" : command { 2 , [ ] string { "message_id" , "target_chat" } , "forwards a message" } ,
"vcard" : command { 0 , [ ] string { } , "print vCard as text" } ,
"add" : command { 1 , [ ] string { "@username" } , "add @username to your chat list" } ,
"join" : command { 1 , [ ] string { "https://t.me/invite_link" } , "join to chat via invite link or @publicname" } ,
"group" : command { 1 , [ ] string { "title" } , "create groupchat «title» with current user" } ,
"supergroup" : command { 1 , [ ] string { "title" , "description" } , "create new supergroup «title» with «description»" } ,
"channel" : command { 1 , [ ] string { "title" , "description" } , "create new channel «title» with «description»" } ,
"secret" : command { 0 , [ ] string { } , "create secretchat with current user" } ,
"search" : command { 0 , [ ] string { "string" , "[limit]" } , "search <string> in current chat" } ,
"history" : command { 0 , [ ] string { "limit" } , "get last [limit] messages from current chat" } ,
"block" : command { 0 , [ ] string { } , "blacklist current user" } ,
"unblock" : command { 0 , [ ] string { } , "unblacklist current user" } ,
"invite" : command { 1 , [ ] string { "id or @username" } , "add user to current chat" } ,
"link" : command { 0 , [ ] string { } , "get invite link for current chat" } ,
"kick" : command { 1 , [ ] string { "id or @username" } , "remove user to current chat" } ,
"mute" : command { 1 , [ ] string { "id or @username" , "hours" } , "mute user in current chat" } ,
"unmute" : command { 1 , [ ] string { "id or @username" } , "unrestrict user from current chat" } ,
"ban" : command { 1 , [ ] string { "id or @username" , "hours" } , "restrict @username from current chat for [hours] or forever" } ,
"unban" : command { 1 , [ ] string { "id or @username" } , "unbans @username in current chat (and devotes from admins)" } ,
"promote" : command { 1 , [ ] string { "id or @username" , "title" } , "promote user to admin in current chat" } ,
"leave" : command { 0 , [ ] string { } , "leave current chat" } ,
"leave!" : command { 0 , [ ] string { } , "leave current chat (for owners)" } ,
"ttl" : command { 0 , [ ] string { "seconds" } , "set secret chat messages TTL before self-destroying" } ,
"close" : command { 0 , [ ] string { } , "close current secret chat" } ,
"delete" : command { 0 , [ ] string { } , "delete current chat from chat list" } ,
"members" : command { 0 , [ ] string { "query" } , "search members [by optional query] in current chat (requires admin rights)" } ,
2019-11-26 00:04:11 +00:00
}
var transportConfigurationOptions = map [ string ] configurationOption {
2022-02-08 20:25:58 +00:00
"timezone" : configurationOption { "<timezone>" , "adjust timezone for Telegram user statuses (example: +02:00)" } ,
"keeponline" : configurationOption { "<bool>" , "always keep telegram session online and rely on jabber offline messages (example: true)" } ,
2022-02-08 18:49:49 +00:00
"rawmessages" : configurationOption { "<bool>" , "do not add additional info (message id, origin etc.) to incoming messages (example: true)" } ,
2019-11-26 00:04:11 +00:00
}
type command struct {
2024-02-03 09:24:22 +00:00
RequiredArgs int
Arguments [ ] string
Description string
2024-02-01 17:14:06 +00:00
}
type configurationOption struct {
2019-11-26 00:04:11 +00:00
arguments string
description string
}
2024-01-31 02:38:46 +00:00
// CommandType disinguishes command sets by chat
type CommandType int
2019-11-26 00:04:11 +00:00
const (
2024-01-31 02:38:46 +00:00
CommandTypeTransport CommandType = iota
CommandTypeChat
2019-11-26 00:04:11 +00:00
)
2024-01-31 02:38:46 +00:00
// GetCommands exposes the set of commands
func GetCommands ( typ CommandType ) map [ string ] command {
2019-11-26 00:04:11 +00:00
var commandMap map [ string ] command
2024-01-31 02:38:46 +00:00
switch typ {
case CommandTypeTransport :
2019-11-26 00:04:11 +00:00
commandMap = transportCommands
2024-01-31 02:38:46 +00:00
case CommandTypeChat :
2019-11-26 00:04:11 +00:00
commandMap = chatCommands
}
2024-01-31 02:38:46 +00:00
return commandMap
}
2024-02-03 09:24:22 +00:00
// GetCommand obtains one command
func GetCommand ( typ CommandType , cmd string ) ( command , bool ) {
commands := GetCommands ( typ )
command , ok := commands [ cmd ]
return command , ok
}
2024-01-31 02:38:46 +00:00
// CommandToHelpString builds a text description of a command
func CommandToHelpString ( name string , cmd command ) string {
var str strings . Builder
str . WriteString ( "/" )
str . WriteString ( name )
2024-02-03 09:24:22 +00:00
for i , arg := range cmd . Arguments {
optional := i >= cmd . RequiredArgs
2024-01-31 02:38:46 +00:00
str . WriteString ( " " )
2024-02-01 17:14:06 +00:00
if optional {
str . WriteString ( "[" )
}
str . WriteString ( arg )
if optional {
str . WriteString ( "]" )
}
2024-01-31 02:38:46 +00:00
}
str . WriteString ( " — " )
2024-02-03 09:24:22 +00:00
str . WriteString ( cmd . Description )
2024-01-31 02:38:46 +00:00
return str . String ( )
}
func helpString ( typ CommandType ) string {
var str strings . Builder
commandMap := GetCommands ( typ )
2019-11-26 00:04:11 +00:00
str . WriteString ( "Available commands:\n" )
for name , command := range commandMap {
2024-01-31 02:38:46 +00:00
str . WriteString ( CommandToHelpString ( name , command ) )
2019-11-26 00:04:11 +00:00
str . WriteString ( "\n" )
}
2024-01-31 02:38:46 +00:00
if typ == CommandTypeTransport {
2019-11-26 00:04:11 +00:00
str . WriteString ( "Configuration options\n" )
for name , option := range transportConfigurationOptions {
str . WriteString ( name )
str . WriteString ( " " )
str . WriteString ( option . arguments )
str . WriteString ( " — " )
str . WriteString ( option . description )
str . WriteString ( "\n" )
}
}
2022-02-14 01:45:11 +00:00
str . WriteString ( "\nYou may use ! instead of / if it conflicts with internal commands of a client" )
2019-11-26 00:04:11 +00:00
return str . String ( )
}
2019-12-03 00:32:53 +00:00
func parseCommand ( cmdline string ) ( string , [ ] string ) {
bodyFields := strings . Fields ( cmdline )
return bodyFields [ 0 ] [ 1 : ] , bodyFields [ 1 : ]
}
2022-02-08 20:25:58 +00:00
func rawCmdArguments ( cmdline string , start uint8 ) string {
2022-01-08 10:59:57 +00:00
var state uint
// /cmd ababa galamaga
// 01 2 3 45
2022-02-08 20:25:58 +00:00
startState := uint ( 3 + 2 * start )
2022-01-08 10:59:57 +00:00
for i , r := range cmdline {
2022-02-08 20:25:58 +00:00
isOdd := state % 2 == 1
2022-01-08 10:59:57 +00:00
isSpace := unicode . IsSpace ( r )
if ( ! isOdd && ! isSpace ) || ( isOdd && isSpace ) {
state += 1
}
if state == startState {
return cmdline [ i : ]
}
}
return ""
}
2023-06-01 20:37:38 +00:00
func keyValueString ( key , value string ) string {
return fmt . Sprintf ( "%s: %s" , key , value )
}
2022-02-03 19:29:16 +00:00
func ( c * Client ) unsubscribe ( chatID int64 ) error {
2024-01-24 23:52:40 +00:00
args := gateway . SimplePresence ( chatID , "unsubscribed" )
return c . sendPresence ( args ... )
2019-12-08 15:35:27 +00:00
}
2019-12-08 16:19:35 +00:00
func ( c * Client ) sendMessagesReverse ( chatID int64 , messages [ ] * client . Message ) {
for i := len ( messages ) - 1 ; i >= 0 ; i -- {
2023-03-04 02:41:45 +00:00
message := messages [ i ]
2024-01-10 19:30:00 +00:00
reply , _ := c . getMessageReply ( message , false , true )
2023-03-04 02:41:45 +00:00
2019-12-08 16:19:35 +00:00
gateway . SendMessage (
c . jid ,
strconv . FormatInt ( chatID , 10 ) ,
2023-03-04 02:41:45 +00:00
c . formatMessage ( 0 , 0 , false , message ) ,
strconv . FormatInt ( message . Id , 10 ) ,
2019-12-08 16:19:35 +00:00
c . xmpp ,
2023-03-05 08:00:53 +00:00
reply ,
2024-01-29 09:28:15 +00:00
"" ,
2023-03-18 21:43:11 +00:00
false ,
2024-01-27 11:13:45 +00:00
false ,
2019-12-08 16:19:35 +00:00
)
}
}
2021-12-04 18:10:54 +00:00
func ( c * Client ) usernameOrIDToID ( username string ) ( int64 , error ) {
userID , err := strconv . ParseInt ( username , 10 , 64 )
2019-12-08 13:58:17 +00:00
// couldn't parse the id, try to lookup as a username
if err != nil {
chat , err := c . client . SearchPublicChat ( & client . SearchPublicChatRequest {
Username : username ,
} )
if err != nil {
return 0 , err
}
2022-01-17 20:45:40 +00:00
userID = chat . Id
2021-12-04 18:10:54 +00:00
if userID <= 0 {
2019-12-08 13:58:17 +00:00
return 0 , errors . New ( "Not a user" )
}
}
2021-12-04 18:10:54 +00:00
return userID , nil
2019-12-08 13:58:17 +00:00
}
2019-12-03 00:32:53 +00:00
// ProcessTransportCommand executes a command sent directly to the component
// and returns a response
2022-01-03 03:54:13 +00:00
func ( c * Client ) ProcessTransportCommand ( cmdline string , resource string ) string {
2019-12-03 00:32:53 +00:00
cmd , args := parseCommand ( cmdline )
2024-02-01 17:14:06 +00:00
command , ok := transportCommands [ cmd ]
if ! ok {
2024-02-10 18:46:02 +00:00
return unknownCommand
2024-02-01 17:14:06 +00:00
}
2024-02-03 09:24:22 +00:00
if len ( args ) < command . RequiredArgs {
2024-02-01 17:14:06 +00:00
return notEnoughArguments
}
2019-11-24 17:10:29 +00:00
switch cmd {
case "login" , "code" , "password" :
2019-12-19 20:29:36 +00:00
if cmd == "login" && c . Session . Login != "" {
2023-06-16 04:34:49 +00:00
return "Phone number already provided, use /cancelauth to start over"
2019-11-24 17:10:29 +00:00
}
2019-12-19 20:29:36 +00:00
if cmd == "login" {
2023-08-28 14:16:57 +00:00
err := c . TryLogin ( resource , args [ 0 ] )
if err != nil {
return err . Error ( )
2019-12-19 20:29:36 +00:00
}
2023-08-31 22:24:30 +00:00
c . locks . authorizerWriteLock . Lock ( )
defer c . locks . authorizerWriteLock . Unlock ( )
2023-08-28 14:16:57 +00:00
c . authorizer . PhoneNumber <- args [ 0 ]
} else {
2023-08-31 22:24:30 +00:00
c . locks . authorizerWriteLock . Lock ( )
defer c . locks . authorizerWriteLock . Unlock ( )
2023-08-28 14:16:57 +00:00
if c . authorizer == nil {
return TelegramNotInitialized
}
2019-11-24 17:10:29 +00:00
2023-08-28 14:16:57 +00:00
if c . authorizer . isClosed {
return TelegramAuthDone
}
2023-06-16 04:44:48 +00:00
2023-08-28 14:16:57 +00:00
switch cmd {
// check auth code
case "code" :
c . authorizer . Code <- args [ 0 ]
// check auth password
case "password" :
c . authorizer . Password <- args [ 0 ]
}
2019-11-24 17:10:29 +00:00
}
2019-12-04 23:10:08 +00:00
// sign out
case "logout" :
2019-12-19 20:29:36 +00:00
if ! c . Online ( ) {
return notOnline
}
2019-12-28 02:35:40 +00:00
for _ , id := range c . cache . ChatsKeys ( ) {
2019-12-18 21:00:23 +00:00
c . unsubscribe ( id )
}
2019-12-04 23:10:08 +00:00
_ , err := c . client . LogOut ( )
if err != nil {
2019-12-18 21:00:23 +00:00
c . forceClose ( )
2019-12-05 18:13:17 +00:00
return errors . Wrap ( err , "Logout error" ) . Error ( )
2019-12-04 23:10:08 +00:00
}
c . Session . Login = ""
2023-06-16 04:34:49 +00:00
// cancel auth
case "cancelauth" :
if c . Online ( ) {
2023-06-16 04:44:48 +00:00
return "Not allowed when online, use /logout instead"
2023-06-16 04:34:49 +00:00
}
c . cancelAuth ( )
return "Cancelled"
2019-12-05 18:13:17 +00:00
// set @username
case "setusername" :
2019-12-19 20:29:36 +00:00
if ! c . Online ( ) {
return notOnline
}
2019-12-05 18:13:17 +00:00
var username string
if len ( args ) > 0 {
username = args [ 0 ]
}
_ , err := c . client . SetUsername ( & client . SetUsernameRequest {
Username : username ,
} )
if err != nil {
return errors . Wrap ( err , "Couldn't set username" ) . Error ( )
}
// set My Name
case "setname" :
2024-02-01 17:14:06 +00:00
firstname := args [ 0 ]
2019-12-05 18:13:17 +00:00
var lastname string
2024-02-01 17:14:06 +00:00
2022-08-15 10:10:29 +00:00
if firstname == "" {
return "The name should contain at least one character"
}
2019-12-05 18:13:17 +00:00
if len ( args ) > 1 {
2022-08-15 10:10:29 +00:00
lastname = rawCmdArguments ( cmdline , 1 )
2019-12-05 18:13:17 +00:00
}
2023-08-31 21:26:35 +00:00
c . locks . authorizerWriteLock . Lock ( )
2022-08-15 10:10:29 +00:00
if c . authorizer != nil && ! c . authorizer . isClosed {
c . authorizer . FirstName <- firstname
c . authorizer . LastName <- lastname
2023-08-31 21:26:35 +00:00
c . locks . authorizerWriteLock . Unlock ( )
2022-08-15 10:10:29 +00:00
} else {
2023-08-31 21:26:35 +00:00
c . locks . authorizerWriteLock . Unlock ( )
2022-08-15 10:10:29 +00:00
if ! c . Online ( ) {
return notOnline
}
_ , err := c . client . SetName ( & client . SetNameRequest {
FirstName : firstname ,
LastName : lastname ,
} )
if err != nil {
return errors . Wrap ( err , "Couldn't set name" ) . Error ( )
}
2019-12-05 18:13:17 +00:00
}
// set About
case "setbio" :
2019-12-19 20:29:36 +00:00
if ! c . Online ( ) {
return notOnline
}
2019-12-05 18:13:17 +00:00
_ , err := c . client . SetBio ( & client . SetBioRequest {
2022-01-08 10:59:57 +00:00
Bio : rawCmdArguments ( cmdline , 0 ) ,
2019-12-05 18:13:17 +00:00
} )
if err != nil {
return errors . Wrap ( err , "Couldn't set bio" ) . Error ( )
}
// set password
case "setpassword" :
2019-12-19 20:29:36 +00:00
if ! c . Online ( ) {
return notOnline
}
2019-12-05 18:13:17 +00:00
var oldPassword string
var newPassword string
// 0 or 1 argument is ignored and the password is reset
if len ( args ) > 1 {
oldPassword = args [ 0 ]
newPassword = args [ 1 ]
}
_ , err := c . client . SetPassword ( & client . SetPasswordRequest {
OldPassword : oldPassword ,
NewPassword : newPassword ,
} )
if err != nil {
return errors . Wrap ( err , "Couldn't set password" ) . Error ( )
}
2019-12-05 19:56:12 +00:00
case "config" :
if len ( args ) > 1 {
2024-01-29 09:28:15 +00:00
var msg string
2023-08-02 21:08:06 +00:00
if gateway . MessageOutgoingPermissionVersion == 0 && args [ 0 ] == "carbons" && args [ 1 ] == "true" {
2023-03-18 21:43:11 +00:00
return "The server did not allow to enable carbons"
}
2024-01-29 09:28:15 +00:00
if ! c . Session . RawMessages && args [ 0 ] == "nativeedits" && args [ 1 ] == "true" {
return "nativeedits only works with rawmessages as of yet, enable it first"
}
if c . Session . NativeEdits && args [ 0 ] == "rawmessages" && args [ 1 ] == "false" {
_ , err := c . Session . Set ( "nativeedits" , "false" )
if err != nil {
return err . Error ( )
}
msg = "Automatically disabling nativeedits too...\n"
}
2023-03-18 21:43:11 +00:00
2019-12-05 19:56:12 +00:00
value , err := c . Session . Set ( args [ 0 ] , args [ 1 ] )
if err != nil {
return err . Error ( )
}
2022-01-05 21:04:22 +00:00
gateway . DirtySessions = true
2019-12-05 19:56:12 +00:00
2024-01-29 09:28:15 +00:00
return fmt . Sprintf ( "%s%s set to %s" , msg , args [ 0 ] , value )
2019-12-05 19:56:12 +00:00
} else if len ( args ) > 0 {
value , err := c . Session . Get ( args [ 0 ] )
if err != nil {
return err . Error ( )
}
return fmt . Sprintf ( "%s is set to %s" , args [ 0 ] , value )
}
var entries [ ] string
for key , value := range c . Session . ToMap ( ) {
entries = append ( entries , fmt . Sprintf ( "%s is set to %s" , key , value ) )
}
return strings . Join ( entries , "\n" )
2022-02-26 19:35:43 +00:00
case "report" :
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( )
}
text := rawCmdArguments ( cmdline , 1 )
_ , err = c . client . ReportChat ( & client . ReportChatRequest {
2022-03-09 19:16:13 +00:00
ChatId : contact . Id ,
2023-11-11 21:10:23 +00:00
Reason : & client . ReportReasonCustom { } ,
2022-03-09 19:16:13 +00:00
Text : text ,
2022-02-26 19:35:43 +00:00
} )
if err != nil {
return err . Error ( )
} else {
return "Reported"
}
2022-08-15 10:51:09 +00:00
case "add" :
return c . cmdAdd ( args )
case "join" :
return c . cmdJoin ( args )
case "supergroup" :
return c . cmdSupergroup ( args , cmdline )
case "channel" :
return c . cmdChannel ( args , cmdline )
2019-11-26 00:04:11 +00:00
case "help" :
2024-01-31 02:38:46 +00:00
return helpString ( CommandTypeTransport )
2019-11-24 17:10:29 +00:00
}
return ""
}
2019-12-03 00:32:53 +00:00
// ProcessChatCommand executes a command sent in a mapped chat
// and returns a response and the status of command support
2019-12-05 23:21:39 +00:00
func ( c * Client ) ProcessChatCommand ( chatID int64 , cmdline string ) ( string , bool ) {
2019-12-19 20:29:36 +00:00
if ! c . Online ( ) {
return notOnline , true
}
2019-12-05 23:21:39 +00:00
cmd , args := parseCommand ( cmdline )
2024-02-10 18:46:02 +00:00
command , ok := chatCommands [ cmd ]
if ! ok {
return unknownCommand , false
}
if len ( args ) < command . RequiredArgs {
return notEnoughArguments , true
}
2019-12-03 00:32:53 +00:00
switch cmd {
2022-01-06 12:13:57 +00:00
// delete message
2019-12-05 23:21:39 +00:00
case "d" :
if c . me == nil {
return "@me is not initialized" , true
}
2019-12-07 14:48:26 +00:00
2019-12-05 23:21:39 +00:00
var limit int32
if len ( args ) > 0 {
limit64 , err := strconv . ParseInt ( args [ 0 ] , 10 , 32 )
if err != nil {
return err . Error ( ) , true
}
limit = int32 ( limit64 )
} else {
limit = 1
}
2022-01-17 20:45:40 +00:00
messages , err := c . getLastMessages ( chatID , "" , c . me . Id , limit )
2019-12-05 23:21:39 +00:00
if err != nil {
return err . Error ( ) , true
}
log . Debugf ( "pre-deletion query: %#v %#v" , messages , messages . Messages )
var messageIds [ ] int64
for _ , message := range messages . Messages {
2019-12-07 14:48:26 +00:00
if message != nil {
2022-01-17 20:45:40 +00:00
messageIds = append ( messageIds , message . Id )
2019-12-07 14:48:26 +00:00
}
2019-12-05 23:21:39 +00:00
}
_ , err = c . client . DeleteMessages ( & client . DeleteMessagesRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
MessageIds : messageIds ,
2019-12-05 23:21:39 +00:00
Revoke : true ,
} )
if err != nil {
return err . Error ( ) , true
}
2022-01-06 12:13:57 +00:00
// edit message
2019-12-07 16:37:14 +00:00
case "s" :
if c . me == nil {
return "@me is not initialized" , true
}
2022-01-17 20:45:40 +00:00
messages , err := c . getLastMessages ( chatID , "" , c . me . Id , 1 )
2019-12-07 16:37:14 +00:00
if err != nil {
return err . Error ( ) , true
}
if len ( messages . Messages ) == 0 {
return "No last message" , true
}
message := messages . Messages [ 0 ]
if message == nil {
return "Last message is empty" , true
}
2023-06-04 00:25:00 +00:00
content := c . PrepareOutgoingMessageContent ( rawCmdArguments ( cmdline , 0 ) )
2019-12-07 16:37:14 +00:00
2022-01-08 10:59:57 +00:00
if content != nil {
2023-07-09 03:52:30 +00:00
_ , err = c . client . EditMessageText ( & client . EditMessageTextRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
MessageId : message . Id ,
2022-01-08 10:59:57 +00:00
InputMessageContent : content ,
} )
2023-07-09 03:52:30 +00:00
if err != nil {
return "Message editing error" , true
}
2022-01-08 10:59:57 +00:00
} else {
return "Message processing error" , true
}
2022-02-10 00:16:44 +00:00
// send without sound
case "silent" :
2023-06-04 00:25:00 +00:00
content := c . PrepareOutgoingMessageContent ( rawCmdArguments ( cmdline , 0 ) )
2022-02-10 00:16:44 +00:00
if content != nil {
_ , err := c . client . SendMessage ( & client . SendMessageRequest {
ChatId : chatID ,
InputMessageContent : content ,
Options : & client . MessageSendOptions {
DisableNotification : true ,
} ,
} )
if err != nil {
return err . Error ( ) , true
}
} else {
return "Message processing error" , true
}
2022-02-11 02:08:43 +00:00
// schedule a message to timestamp or to going online
case "schedule" :
var state client . MessageSchedulingState
var result string
due := args [ 0 ]
if due == "online" {
state = & client . MessageSchedulingStateSendWhenOnline { }
result = due
} else {
if c . Session . Timezone == "" {
due += "Z"
} else {
due += c . Session . Timezone
}
switch 0 {
default :
// try bare time first
timestamp , err := time . Parse ( "15:04:05Z07:00" , due )
if err == nil {
now := time . Now ( ) . In ( c . Session . TimezoneToLocation ( ) )
// combine timestamp's time with today's date
timestamp = time . Date (
now . Year ( ) ,
now . Month ( ) ,
now . Day ( ) ,
timestamp . Hour ( ) ,
timestamp . Minute ( ) ,
timestamp . Second ( ) ,
0 ,
timestamp . Location ( ) ,
)
diff := timestamp . Sub ( now )
if diff < 0 { // set to tomorrow
timestamp = timestamp . AddDate ( 0 , 0 , 1 )
}
state = & client . MessageSchedulingStateSendAtDate {
SendDate : int32 ( timestamp . Unix ( ) ) ,
}
result = timestamp . Format ( time . RFC3339 )
break
}
timestamp , err = time . Parse ( time . RFC3339 , due )
if err == nil {
// 2038 doomsday again
state = & client . MessageSchedulingStateSendAtDate {
SendDate : int32 ( timestamp . Unix ( ) ) ,
}
result = timestamp . Format ( time . RFC3339 )
break
}
return "Invalid schedule time specifier" , true
}
}
2023-06-04 00:25:00 +00:00
content := c . PrepareOutgoingMessageContent ( rawCmdArguments ( cmdline , 1 ) )
2022-02-11 02:08:43 +00:00
if content != nil {
_ , err := c . client . SendMessage ( & client . SendMessageRequest {
ChatId : chatID ,
InputMessageContent : content ,
Options : & client . MessageSendOptions {
SchedulingState : state ,
} ,
} )
if err != nil {
return err . Error ( ) , true
}
return "Scheduled to " + result , true
} else {
return "Message processing error" , true
}
2022-03-12 17:25:53 +00:00
// forward a message to chat
case "forward" :
messageId , err := strconv . ParseInt ( args [ 0 ] , 10 , 64 )
if err != nil {
return "Cannot parse message ID" , true
}
targetChatParts := strings . Split ( args [ 1 ] , "@" ) // full JIDs are supported too
targetChatId , err := strconv . ParseInt ( targetChatParts [ 0 ] , 10 , 64 )
if err != nil {
return "Cannot parse target chat ID" , true
}
messages , err := c . client . ForwardMessages ( & client . ForwardMessagesRequest {
ChatId : targetChatId ,
FromChatId : chatID ,
MessageIds : [ ] int64 { messageId } ,
} )
if err != nil {
return err . Error ( ) , true
}
if messages != nil && messages . Messages != nil {
for _ , message := range messages . Messages {
2023-08-02 20:41:18 +00:00
c . ProcessIncomingMessage ( targetChatId , message )
2022-03-12 17:25:53 +00:00
}
}
2023-06-01 20:37:38 +00:00
// print vCard
case "vcard" :
info , err := c . GetVcardInfo ( chatID )
if err != nil {
return err . Error ( ) , true
}
_ , link := c . PermastoreFile ( info . Photo , true )
entries := [ ] string {
keyValueString ( "Chat title" , info . Fn ) ,
keyValueString ( "Photo" , link ) ,
2023-08-01 01:25:24 +00:00
keyValueString ( "Usernames" , c . usernamesToString ( info . Nicknames ) ) ,
2023-06-08 17:33:22 +00:00
keyValueString ( "Full name" , info . Given + " " + info . Family ) ,
2023-06-01 20:37:38 +00:00
keyValueString ( "Phone number" , info . Tel ) ,
}
return strings . Join ( entries , "\n" ) , true
2019-12-07 17:56:53 +00:00
// add @contact
case "add" :
2022-08-15 10:51:09 +00:00
return c . cmdAdd ( args ) , true
2022-06-22 18:03:18 +00:00
// join https://t.me/publichat or @publicchat
2019-12-07 21:08:12 +00:00
case "join" :
2022-08-15 10:51:09 +00:00
return c . cmdJoin ( args ) , true
2019-12-07 21:26:58 +00:00
// create new supergroup
case "supergroup" :
2022-08-15 10:51:09 +00:00
return c . cmdSupergroup ( args , cmdline ) , true
2019-12-07 21:26:58 +00:00
// create new channel
case "channel" :
2022-08-15 10:51:09 +00:00
return c . cmdChannel ( args , cmdline ) , true
2019-12-07 23:36:29 +00:00
// create new secret chat with current user
case "secret" :
2022-01-17 19:58:16 +00:00
_ , err := c . client . CreateNewSecretChat ( & client . CreateNewSecretChatRequest {
2022-01-17 20:45:40 +00:00
UserId : chatID ,
2019-12-07 23:36:29 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
// create group chat with current user
case "group" :
2022-01-17 19:58:16 +00:00
_ , err := c . client . CreateNewBasicGroupChat ( & client . CreateNewBasicGroupChatRequest {
2022-01-17 20:45:40 +00:00
UserIds : [ ] int64 { chatID } ,
2022-01-17 19:58:16 +00:00
Title : args [ 0 ] ,
} )
if err != nil {
return err . Error ( ) , true
2019-12-07 23:36:29 +00:00
}
2019-12-08 01:54:09 +00:00
// blacklists current user
case "block" :
2023-11-11 21:10:23 +00:00
_ , err := c . client . SetMessageSenderBlockList ( & client . SetMessageSenderBlockListRequest {
2022-01-17 20:45:40 +00:00
SenderId : & client . MessageSenderUser { UserId : chatID } ,
2023-11-11 21:10:23 +00:00
BlockList : & client . BlockListMain { } ,
2022-01-17 19:58:16 +00:00
} )
if err != nil {
return err . Error ( ) , true
2019-12-08 01:54:09 +00:00
}
// unblacklists current user
case "unblock" :
2023-11-11 21:10:23 +00:00
_ , err := c . client . SetMessageSenderBlockList ( & client . SetMessageSenderBlockListRequest {
2022-01-17 20:45:40 +00:00
SenderId : & client . MessageSenderUser { UserId : chatID } ,
2023-11-11 21:10:23 +00:00
BlockList : nil ,
2022-01-17 19:58:16 +00:00
} )
if err != nil {
return err . Error ( ) , true
2019-12-08 01:54:09 +00:00
}
2019-12-08 13:32:43 +00:00
// invite @username to current groupchat
case "invite" :
2022-01-17 19:58:16 +00:00
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
2019-12-08 13:32:43 +00:00
2022-01-17 19:58:16 +00:00
_ , err = c . client . AddChatMember ( & client . AddChatMemberRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
UserId : contact . Id ,
2022-01-17 19:58:16 +00:00
ForwardLimit : 100 ,
} )
if err != nil {
return err . Error ( ) , true
2019-12-08 13:58:17 +00:00
}
2022-02-01 03:14:06 +00:00
// get link to current chat
case "link" :
link , err := c . client . CreateChatInviteLink ( & client . CreateChatInviteLinkRequest {
ChatId : chatID ,
} )
if err != nil {
return err . Error ( ) , true
}
return link . InviteLink , true
2019-12-08 13:58:17 +00:00
// kick @username from current group chat
case "kick" :
2022-01-17 19:58:16 +00:00
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
2019-12-08 13:58:17 +00:00
2022-01-17 19:58:16 +00:00
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-01 04:57:17 +00:00
Status : & client . ChatMemberStatusLeft { } ,
} )
if err != nil {
return err . Error ( ) , true
}
// mute @username [n hours]
case "mute" :
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
var hours int64
if len ( args ) > 1 {
hours , err = strconv . ParseInt ( args [ 1 ] , 10 , 32 )
if err != nil {
return "Invalid number of hours" , true
}
}
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
ChatId : chatID ,
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-08 20:25:58 +00:00
Status : & client . ChatMemberStatusRestricted {
2022-02-01 04:57:17 +00:00
IsMember : true ,
RestrictedUntilDate : c . formatBantime ( hours ) ,
Permissions : & permissionsReadonly ,
} ,
} )
if err != nil {
return err . Error ( ) , true
}
// unmute @username
case "unmute" :
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
ChatId : chatID ,
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-08 20:25:58 +00:00
Status : & client . ChatMemberStatusRestricted {
2022-02-01 04:57:17 +00:00
IsMember : true ,
RestrictedUntilDate : 0 ,
Permissions : & permissionsMember ,
} ,
2022-01-17 19:58:16 +00:00
} )
if err != nil {
return err . Error ( ) , true
2019-12-08 13:32:43 +00:00
}
2019-12-08 14:24:51 +00:00
// ban @username from current chat [for N hours]
case "ban" :
2022-01-17 19:58:16 +00:00
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
2019-12-08 14:24:51 +00:00
2022-01-17 19:58:16 +00:00
var hours int64
if len ( args ) > 1 {
hours , err = strconv . ParseInt ( args [ 1 ] , 10 , 32 )
2019-12-08 14:24:51 +00:00
if err != nil {
2022-01-17 19:58:16 +00:00
return "Invalid number of hours" , true
2019-12-08 14:24:51 +00:00
}
}
2022-01-17 19:58:16 +00:00
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
2022-02-08 20:25:58 +00:00
ChatId : chatID ,
2022-01-17 20:45:40 +00:00
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-01 04:57:17 +00:00
Status : & client . ChatMemberStatusBanned {
BannedUntilDate : c . formatBantime ( hours ) ,
} ,
2022-01-17 19:58:16 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
2022-02-03 18:51:27 +00:00
// unban @username
case "unban" :
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
2022-02-08 20:25:58 +00:00
ChatId : chatID ,
2022-02-03 18:51:27 +00:00
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-08 20:25:58 +00:00
Status : & client . ChatMemberStatusMember { } ,
2022-02-03 18:51:27 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
// promote @username to admin
case "promote" :
contact , _ , err := c . GetContactByUsername ( args [ 0 ] )
if err != nil {
return err . Error ( ) , true
}
// clone the permissions
2023-08-01 01:25:24 +00:00
status := client . ChatMemberStatusAdministrator {
CanBeEdited : true ,
Rights : & permissionsAdmin ,
}
2022-02-03 18:51:27 +00:00
if len ( args ) > 1 {
status . CustomTitle = args [ 1 ]
}
_ , err = c . client . SetChatMemberStatus ( & client . SetChatMemberStatusRequest {
2022-02-08 20:25:58 +00:00
ChatId : chatID ,
2022-02-03 18:51:27 +00:00
MemberId : & client . MessageSenderUser { UserId : contact . Id } ,
2022-02-08 20:25:58 +00:00
Status : & status ,
2022-02-03 18:51:27 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
2019-12-08 15:08:55 +00:00
// leave current chat
case "leave" :
2022-01-17 19:58:16 +00:00
_ , err := c . client . LeaveChat ( & client . LeaveChatRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
2022-01-17 19:58:16 +00:00
} )
2019-12-08 15:08:55 +00:00
if err != nil {
return err . Error ( ) , true
}
2022-02-03 19:29:16 +00:00
err = c . unsubscribe ( chatID )
if err != nil {
return err . Error ( ) , true
}
// leave current chat (for owners)
case "leave!" :
_ , err := c . client . DeleteChat ( & client . DeleteChatRequest {
ChatId : chatID ,
} )
if err != nil {
return err . Error ( ) , true
}
err = c . unsubscribe ( chatID )
2022-02-08 20:22:11 +00:00
if err != nil {
return err . Error ( ) , true
}
// set TTL
case "ttl" :
var ttl int64
var err error
if len ( args ) > 0 {
ttl , err = strconv . ParseInt ( args [ 0 ] , 10 , 32 )
if err != nil {
return "Invalid TTL" , true
}
}
2023-08-01 01:25:24 +00:00
_ , err = c . client . SetChatMessageAutoDeleteTime ( & client . SetChatMessageAutoDeleteTimeRequest {
2023-08-01 01:37:05 +00:00
ChatId : chatID ,
MessageAutoDeleteTime : int32 ( ttl ) ,
2022-02-08 20:22:11 +00:00
} )
2022-02-03 19:29:16 +00:00
if err != nil {
return err . Error ( ) , true
}
2019-12-08 15:25:29 +00:00
// close secret chat
case "close" :
chat , _ , err := c . GetContactByID ( chatID , nil )
if err != nil {
return err . Error ( ) , true
}
chatType := chat . Type . ChatTypeType ( )
if chatType == client . TypeChatTypeSecret {
chatTypeSecret , _ := chat . Type . ( * client . ChatTypeSecret )
_ , err = c . client . CloseSecretChat ( & client . CloseSecretChatRequest {
2022-01-17 20:45:40 +00:00
SecretChatId : chatTypeSecret . SecretChatId ,
2019-12-08 15:25:29 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
2022-02-03 19:29:16 +00:00
err = c . unsubscribe ( chatID )
if err != nil {
return err . Error ( ) , true
}
2019-12-08 15:08:55 +00:00
}
2019-12-08 15:35:27 +00:00
// delete current chat
case "delete" :
_ , err := c . client . DeleteChatHistory ( & client . DeleteChatHistoryRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
2019-12-08 15:35:27 +00:00
RemoveFromChatList : true ,
2022-01-17 19:58:16 +00:00
Revoke : true ,
2019-12-08 15:35:27 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
2022-02-03 19:29:16 +00:00
err = c . unsubscribe ( chatID )
if err != nil {
return err . Error ( ) , true
}
2022-01-06 12:13:57 +00:00
// message search
2019-12-08 16:04:26 +00:00
case "search" :
2022-01-17 19:58:16 +00:00
var limit int32 = 100
2019-12-08 16:04:26 +00:00
if len ( args ) > 1 {
newLimit , err := strconv . ParseInt ( args [ 1 ] , 10 , 32 )
if err == nil {
limit = int32 ( newLimit )
}
}
var query string
if len ( args ) > 0 {
query = args [ 0 ]
}
2022-01-06 12:13:57 +00:00
messages , err := c . getLastMessages ( chatID , query , 0 , limit )
2019-12-08 16:04:26 +00:00
if err != nil {
return err . Error ( ) , true
}
2019-12-08 16:19:35 +00:00
c . sendMessagesReverse ( chatID , messages . Messages )
// get latest entries from history
case "history" :
var limit int32 = 10
if len ( args ) > 0 {
newLimit , err := strconv . ParseInt ( args [ 0 ] , 10 , 32 )
if err == nil {
limit = int32 ( newLimit )
}
}
2022-05-18 14:18:47 +00:00
var newMessages * client . Messages
var messages [ ] * client . Message
2022-01-06 09:27:25 +00:00
var err error
2022-05-18 14:18:47 +00:00
var fromId int64
for _ = range make ( [ ] struct { } , limit ) { // safety limit
if len ( messages ) > 0 {
fromId = messages [ len ( messages ) - 1 ] . Id
}
newMessages , err = c . client . GetChatHistory ( & client . GetChatHistoryRequest {
ChatId : chatID ,
FromMessageId : fromId ,
Limit : limit ,
2022-01-06 09:27:25 +00:00
} )
if err != nil {
return err . Error ( ) , true
}
2022-05-18 14:18:47 +00:00
messages = append ( messages , newMessages . Messages ... )
if len ( newMessages . Messages ) == 0 || len ( messages ) >= int ( limit ) {
2022-01-06 09:27:25 +00:00
break
}
2019-12-08 16:04:26 +00:00
}
2019-12-08 16:19:35 +00:00
2022-05-18 14:18:47 +00:00
c . sendMessagesReverse ( chatID , messages )
2022-01-17 19:58:16 +00:00
// chat members
2019-12-08 18:44:17 +00:00
case "members" :
var query string
if len ( args ) > 0 {
query = args [ 0 ]
}
members , err := c . client . SearchChatMembers ( & client . SearchChatMembersRequest {
2022-01-17 20:45:40 +00:00
ChatId : chatID ,
2019-12-08 18:44:17 +00:00
Limit : 9999 ,
Query : query ,
Filter : & client . ChatMembersFilterMembers { } ,
} )
if err != nil {
return err . Error ( ) , true
}
var entries [ ] string
for _ , member := range members . Members {
2021-12-04 18:10:54 +00:00
var senderId int64
2022-01-17 20:45:40 +00:00
switch member . MemberId . MessageSenderType ( ) {
2021-12-04 18:10:54 +00:00
case client . TypeMessageSenderUser :
2022-01-17 20:45:40 +00:00
memberUser , _ := member . MemberId . ( * client . MessageSenderUser )
senderId = memberUser . UserId
2021-12-04 18:10:54 +00:00
case client . TypeMessageSenderChat :
2022-01-17 20:45:40 +00:00
memberChat , _ := member . MemberId . ( * client . MessageSenderChat )
senderId = memberChat . ChatId
2021-12-04 18:10:54 +00:00
}
2019-12-08 18:44:17 +00:00
entries = append ( entries , fmt . Sprintf (
"%v | role: %v" ,
2021-12-04 18:10:54 +00:00
c . formatContact ( senderId ) ,
2019-12-08 18:44:17 +00:00
member . Status . ChatMemberStatusType ( ) ,
) )
}
2022-02-01 03:23:37 +00:00
return strings . Join ( entries , "\n" ) , true
2019-12-03 00:32:53 +00:00
case "help" :
2024-01-31 02:38:46 +00:00
return helpString ( CommandTypeChat ) , true
2019-12-05 23:21:39 +00:00
default :
return "" , false
2019-12-03 00:32:53 +00:00
}
2019-12-05 23:21:39 +00:00
return "" , true
2019-12-03 00:32:53 +00:00
}
2022-08-15 10:51:09 +00:00
func ( c * Client ) cmdAdd ( args [ ] string ) string {
chat , err := c . client . SearchPublicChat ( & client . SearchPublicChatRequest {
Username : args [ 0 ] ,
} )
if err != nil {
return err . Error ( )
}
if chat == nil {
return "No error, but chat is nil"
}
c . subscribeToID ( chat . Id , chat )
return ""
}
func ( c * Client ) cmdJoin ( args [ ] string ) string {
if strings . HasPrefix ( args [ 0 ] , "@" ) {
chat , err := c . client . SearchPublicChat ( & client . SearchPublicChatRequest {
Username : args [ 0 ] ,
} )
if err != nil {
return err . Error ( )
}
if chat == nil {
return "No error, but chat is nil"
}
_ , err = c . client . JoinChat ( & client . JoinChatRequest {
ChatId : chat . Id ,
} )
if err != nil {
return err . Error ( )
}
} else {
_ , err := c . client . JoinChatByInviteLink ( & client . JoinChatByInviteLinkRequest {
InviteLink : args [ 0 ] ,
} )
if err != nil {
return err . Error ( )
}
}
return ""
}
func ( c * Client ) cmdSupergroup ( args [ ] string , cmdline string ) string {
_ , err := c . client . CreateNewSupergroupChat ( & client . CreateNewSupergroupChatRequest {
Title : args [ 0 ] ,
Description : rawCmdArguments ( cmdline , 1 ) ,
} )
if err != nil {
return err . Error ( )
}
return ""
}
func ( c * Client ) cmdChannel ( args [ ] string , cmdline string ) string {
_ , err := c . client . CreateNewSupergroupChat ( & client . CreateNewSupergroupChatRequest {
Title : args [ 0 ] ,
Description : rawCmdArguments ( cmdline , 1 ) ,
IsChannel : true ,
} )
if err != nil {
return err . Error ( )
}
return ""
}