mirror of
https://dev.narayana.im/narayana/telegabber.git
synced 2026-08-05 12:17:06 +00:00
1081 lines
28 KiB
Go
1081 lines
28 KiB
Go
package telegram
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/pkg/errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"dev.narayana.im/narayana/telegabber/persistence"
|
|
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/zelenin/go-tdlib/client"
|
|
)
|
|
|
|
const unknownCommand string = "Unknown command"
|
|
const notEnoughArguments string = "Not enough arguments"
|
|
const TelegramNotInitialized string = "Telegram connection is not initialized yet"
|
|
const TelegramAuthDone string = "Authorization is done already"
|
|
const notOnline string = "Not online"
|
|
|
|
var permissionsAdmin = client.ChatAdministratorRights{
|
|
CanChangeInfo: true,
|
|
CanPostMessages: true,
|
|
CanEditMessages: true,
|
|
CanDeleteMessages: true,
|
|
CanInviteUsers: true,
|
|
CanRestrictMembers: true,
|
|
CanPinMessages: true,
|
|
CanPromoteMembers: false,
|
|
}
|
|
var permissionsMember = client.ChatPermissions{
|
|
CanSendBasicMessages: true,
|
|
CanSendAudios: true,
|
|
CanSendDocuments: true,
|
|
CanSendPhotos: true,
|
|
CanSendVideos: true,
|
|
CanSendVideoNotes: true,
|
|
CanSendVoiceNotes: true,
|
|
CanSendPolls: true,
|
|
CanSendOtherMessages: true,
|
|
CanAddWebPagePreviews: true,
|
|
CanChangeInfo: true,
|
|
CanInviteUsers: true,
|
|
CanPinMessages: true,
|
|
CanManageTopics: true,
|
|
}
|
|
var permissionsReadonly = client.ChatPermissions{}
|
|
|
|
var transportConfigurationOptions = map[string]configurationOption{
|
|
"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 (true/false)"},
|
|
"rawmessages": configurationOption{"<bool>", "do not add additional info (message id, origin etc.) to incoming messages (true/false)"},
|
|
"asciiarrows": configurationOption{"<bool>", "replace some Unicode symbols with ASCII alternatives for better compatibility (true/false)"},
|
|
"muc": configurationOption{"<bool>", "use MUCs instead of the legacy PM representation of group chats (true/false)"},
|
|
"oobmode": configurationOption{"<bool>", "use XEP-0066 (OOB); pros: some modern clients won't show images without it, cons: very restricted, Tkabber would flood with popups (true/false)"},
|
|
"carbons": configurationOption{"<bool>", "send carbons to your another clients, will turn on only if supported by the server (true/false)"},
|
|
"hideids": configurationOption{"<bool>", "hide message IDs from message info (true/false)"},
|
|
"receipts": configurationOption{"<bool>", "if enabled, XMPP read receipts are synced to Telegram, otherwise, messages are marked as read automatically (true/false)"},
|
|
"nativeedits": configurationOption{"<bool>", "if possible, edit XMPP messages instead of showing Telegram edits as separate messages (true/false)"},
|
|
"ignoregroupdeletions": configurationOption{"<bool>", "suppress message deletion messages in group chats (true/false)"},
|
|
}
|
|
|
|
type configurationOption struct {
|
|
arguments string
|
|
description string
|
|
}
|
|
|
|
func (c *Client) helpString(typ gateway.CommandType, chatId int64) string {
|
|
var str strings.Builder
|
|
|
|
commandMap := gateway.GetCommands(typ)
|
|
chatType, _, _ := c.GetChatType(chatId, true)
|
|
|
|
str.WriteString("Available commands:\n")
|
|
|
|
if typ == gateway.CommandTypeTransport {
|
|
gateway.CommandsToHelpString(&str, chatType, gateway.OnlineFilterNotOnline, commandMap)
|
|
|
|
str.WriteString("\nOnline-only commands:\n")
|
|
gateway.CommandsToHelpString(&str, chatType, gateway.OnlineFilterOnline, commandMap)
|
|
|
|
str.WriteString("\nConfiguration options\n")
|
|
for _, name := range persistence.ConfigKeys {
|
|
option := transportConfigurationOptions[name]
|
|
str.WriteString(name)
|
|
str.WriteString(" ")
|
|
str.WriteString(option.arguments)
|
|
str.WriteString(" — ")
|
|
str.WriteString(option.description)
|
|
str.WriteString("\n")
|
|
}
|
|
} else if typ == gateway.CommandTypeChat {
|
|
gateway.CommandsToHelpString(&str, chatType, gateway.OnlineFilterAny, commandMap)
|
|
}
|
|
str.WriteString("\nYou may use ! instead of / if it conflicts with internal commands of a client")
|
|
|
|
return str.String()
|
|
}
|
|
|
|
func parseCommand(cmdline string) (string, []string) {
|
|
bodyFields := strings.Fields(cmdline)
|
|
return bodyFields[0][1:], bodyFields[1:]
|
|
}
|
|
|
|
func rawCmdArguments(cmdline string, start uint8) string {
|
|
var state uint
|
|
// /cmd ababa galamaga
|
|
// 01 2 3 45
|
|
startState := uint(3 + 2*start)
|
|
for i, r := range cmdline {
|
|
isOdd := state%2 == 1
|
|
isSpace := unicode.IsSpace(r)
|
|
if (!isOdd && !isSpace) || (isOdd && isSpace) {
|
|
state += 1
|
|
}
|
|
if state == startState {
|
|
return cmdline[i:]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func keyValueString(key, value string) string {
|
|
return fmt.Sprintf("%s: %s", key, value)
|
|
}
|
|
|
|
func (c *Client) usernameOrIDToID(username string) (int64, error) {
|
|
userID, err := strconv.ParseInt(username, 10, 64)
|
|
// 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
|
|
}
|
|
|
|
userID = chat.Id
|
|
if userID <= 0 {
|
|
return 0, errors.New("Not a user")
|
|
}
|
|
}
|
|
|
|
return userID, nil
|
|
}
|
|
|
|
// ProcessTransportCommand executes a command sent directly to the component
|
|
// and returns a response and execution success result
|
|
func (c *Client) ProcessTransportCommand(cmdline string, resource string) (string, bool) {
|
|
cmd, args := parseCommand(cmdline)
|
|
command, ok := gateway.TransportCommands[cmd]
|
|
if !ok {
|
|
return unknownCommand, false
|
|
}
|
|
if len(args) < command.RequiredArgs {
|
|
return notEnoughArguments, false
|
|
}
|
|
if command.OnlineOnly && !c.Online() {
|
|
return notOnline, false
|
|
}
|
|
|
|
switch cmd {
|
|
case "login", "code", "password":
|
|
if cmd == "login" && c.Session.Login != "" {
|
|
return "Phone number already provided, use /cancelauth to start over", false
|
|
}
|
|
|
|
if cmd == "login" {
|
|
err := c.TryLogin(resource, args[0])
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
|
|
c.locks.authorizerWriteLock.Lock()
|
|
defer c.locks.authorizerWriteLock.Unlock()
|
|
|
|
c.authorizer.PhoneNumber <- args[0]
|
|
} else {
|
|
c.locks.authorizerWriteLock.Lock()
|
|
defer c.locks.authorizerWriteLock.Unlock()
|
|
|
|
if c.authorizer == nil {
|
|
return TelegramNotInitialized, false
|
|
}
|
|
|
|
if c.authorizer.isClosed {
|
|
return TelegramAuthDone, false
|
|
}
|
|
|
|
switch cmd {
|
|
// check auth code
|
|
case "code":
|
|
c.authorizer.Code <- args[0]
|
|
// check auth password
|
|
case "password":
|
|
c.authorizer.Password <- args[0]
|
|
}
|
|
}
|
|
return "", true
|
|
// sign out
|
|
case "logout":
|
|
if !c.Online() && !c.locks.loginFinish.IsPending() {
|
|
return notOnline, false
|
|
}
|
|
|
|
_, err := c.client.LogOut()
|
|
if err != nil {
|
|
return errors.Wrap(err, "Logout error").Error(), false
|
|
}
|
|
|
|
c.unsubscribeFromAll()
|
|
|
|
c.Session.Login = ""
|
|
c.wizardStageOrPrompt(LoginStageCancel, "")
|
|
c.online = false
|
|
// cleanup
|
|
case "cleanup":
|
|
c.unsubscribeFromAll()
|
|
// cancel auth
|
|
case "cancelauth":
|
|
if c.Online() {
|
|
return "Not allowed when online, use /logout instead", false
|
|
}
|
|
c.cancelAuth()
|
|
return "Cancelled", true
|
|
// set @username
|
|
case "setusername":
|
|
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(), false
|
|
}
|
|
// set My Name
|
|
case "setname":
|
|
firstname := args[0]
|
|
var lastname string
|
|
|
|
if firstname == "" {
|
|
return "The name should contain at least one character", false
|
|
}
|
|
if len(args) > 1 {
|
|
lastname = rawCmdArguments(cmdline, 1)
|
|
}
|
|
|
|
c.locks.authorizerWriteLock.Lock()
|
|
if c.authorizer != nil && !c.authorizer.isClosed {
|
|
c.authorizer.FirstName <- firstname
|
|
c.authorizer.LastName <- lastname
|
|
c.locks.authorizerWriteLock.Unlock()
|
|
} else {
|
|
c.locks.authorizerWriteLock.Unlock()
|
|
if !c.Online() {
|
|
return notOnline, false
|
|
}
|
|
|
|
_, err := c.client.SetName(&client.SetNameRequest{
|
|
FirstName: firstname,
|
|
LastName: lastname,
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err, "Couldn't set name").Error(), false
|
|
}
|
|
}
|
|
// set About
|
|
case "setbio":
|
|
_, err := c.client.SetBio(&client.SetBioRequest{
|
|
Bio: rawCmdArguments(cmdline, 0),
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err, "Couldn't set bio").Error(), false
|
|
}
|
|
// set password
|
|
case "setpassword":
|
|
var oldPassword string
|
|
var newPassword string
|
|
if len(args) > 0 {
|
|
oldPassword = args[0]
|
|
}
|
|
if len(args) > 1 {
|
|
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(), false
|
|
}
|
|
case "config":
|
|
if len(args) > 1 {
|
|
if gateway.MessageOutgoingPermissionVersion == 0 && args[0] == "carbons" && args[1] == "true" {
|
|
return "The server did not allow to enable carbons", false
|
|
}
|
|
|
|
value, err := c.Session.Set(args[0], args[1])
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
if args[0] == "muc" {
|
|
switch args[1] {
|
|
case "true":
|
|
go c.MigrateToMUCs()
|
|
case "false":
|
|
go c.MigrateFromMUCs()
|
|
}
|
|
if c.loginStage == LoginStageMUC {
|
|
c.wizardStageOrPrompt(LoginStageSuccess, "")
|
|
}
|
|
}
|
|
gateway.DirtySessions = true
|
|
|
|
return fmt.Sprintf("%s set to %s", args[0], value), true
|
|
} else if len(args) > 0 {
|
|
value, err := c.Session.Get(args[0])
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
|
|
return fmt.Sprintf("%s is set to %s", args[0], value), true
|
|
}
|
|
|
|
var entries []string
|
|
for _, key := range persistence.ConfigKeys {
|
|
value, err := c.Session.Get(key)
|
|
if err != nil {
|
|
log.Errorf("Achtung! Programming error in sessions with key %v", key)
|
|
continue
|
|
}
|
|
entries = append(entries, fmt.Sprintf("%s is set to %s", key, value))
|
|
}
|
|
|
|
return strings.Join(entries, "\n"), true
|
|
case "status":
|
|
return fmt.Sprintf("Login stage: %v\nLogin: %v", c.loginStage, c.Session.Login), true
|
|
case "report":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", false
|
|
}
|
|
|
|
text := rawCmdArguments(cmdline, 1)
|
|
_, err = c.client.ReportChat(&client.ReportChatRequest{
|
|
ChatId: contact.Id,
|
|
Reason: &client.ReportReasonCustom{},
|
|
Text: text,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
} else {
|
|
return "Reported", true
|
|
}
|
|
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)
|
|
case "help":
|
|
return c.helpString(gateway.CommandTypeTransport, 0), true
|
|
case "preset":
|
|
cancelNextStage := false
|
|
|
|
defer func() {
|
|
if !cancelNextStage {
|
|
go c.promptMUC()
|
|
}
|
|
}()
|
|
switch args[0] {
|
|
case "modern", "classic":
|
|
for _, row := range persistence.Presets[args[0]] {
|
|
_, err := c.Session.Set(row[0], row[1])
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
}
|
|
gateway.DirtySessions = true
|
|
return fmt.Sprintf("Applied preset %s", args[0]), true
|
|
default:
|
|
cancelNextStage = true
|
|
return "Invalid argument. Allowed ones are " + gateway.TransportCommands["preset"].Arguments[0], false
|
|
}
|
|
case "pass":
|
|
switch c.loginStage {
|
|
case LoginStagePreset:
|
|
go c.promptMUC()
|
|
case LoginStageMUC:
|
|
c.wizardStageOrPrompt(LoginStageSuccess, "")
|
|
default:
|
|
return "Not applicable here", false
|
|
}
|
|
case "finish":
|
|
switch c.loginStage {
|
|
case LoginStagePreset, LoginStageMUC:
|
|
c.wizardStageOrPrompt(LoginStageSuccess, "")
|
|
default:
|
|
return "Not applicable here", false
|
|
}
|
|
}
|
|
|
|
return "", true
|
|
}
|
|
|
|
func (c *Client) promptMUC() {
|
|
c.wizardStageOrPrompt(LoginStageMUC, "Enable MUCs? Telegabber still supports the legacy approach of mapping Telegram group chats to personal messages in XMPP which might be suitable for some cases like logging or reliable participation in all group chats. Use `/config muc {true|false}` or /finish")
|
|
}
|
|
|
|
// ProcessChatCommand executes a command sent in a mapped chat
|
|
// and returns a response, the status of command support and the execution success result
|
|
func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool, bool) {
|
|
cmd, args := parseCommand(cmdline)
|
|
command, ok := gateway.ChatCommands[cmd]
|
|
if !ok {
|
|
return unknownCommand, false, false
|
|
}
|
|
if len(args) < command.RequiredArgs {
|
|
return notEnoughArguments, true, false
|
|
}
|
|
if command.OnlineOnly && !c.Online() {
|
|
return notOnline, true, false
|
|
}
|
|
|
|
|
|
chatType, _, chatTypeErr := c.GetChatType(chatID, true)
|
|
if chatTypeErr == nil && !gateway.IsCommandForChatType(command, chatType) {
|
|
return "Not applicable for this chat type", true, false
|
|
}
|
|
|
|
switch cmd {
|
|
// delete message
|
|
case "d":
|
|
if c.me == nil {
|
|
return "@me is not initialized", true, false
|
|
}
|
|
|
|
var limit int32
|
|
if len(args) > 0 {
|
|
limit64, err := strconv.ParseInt(args[0], 10, 32)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
limit = int32(limit64)
|
|
} else {
|
|
limit = 1
|
|
}
|
|
|
|
messages, err := c.getLastMessages(chatID, "", c.me.Id, limit)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
log.Debugf("pre-deletion query: %#v %#v", messages, messages.Messages)
|
|
|
|
var messageIds []int64
|
|
for _, message := range messages.Messages {
|
|
if message != nil {
|
|
messageIds = append(messageIds, message.Id)
|
|
}
|
|
}
|
|
|
|
_, err = c.client.DeleteMessages(&client.DeleteMessagesRequest{
|
|
ChatId: chatID,
|
|
MessageIds: messageIds,
|
|
Revoke: true,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
return "", true, true
|
|
// edit message
|
|
case "s":
|
|
if c.me == nil {
|
|
return "@me is not initialized", true, false
|
|
}
|
|
|
|
messages, err := c.getLastMessages(chatID, "", c.me.Id, 1)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if len(messages.Messages) == 0 {
|
|
return "No last message", true, false
|
|
}
|
|
|
|
message := messages.Messages[0]
|
|
if message == nil {
|
|
return "Last message is empty", true, false
|
|
}
|
|
|
|
content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0))
|
|
|
|
if content != nil {
|
|
_, err = c.client.EditMessageText(&client.EditMessageTextRequest{
|
|
ChatId: chatID,
|
|
MessageId: message.Id,
|
|
InputMessageContent: content,
|
|
})
|
|
if err != nil {
|
|
return "Message editing error", true, false
|
|
}
|
|
} else {
|
|
return "Message processing error", true, false
|
|
}
|
|
return "", true, true
|
|
// send without sound
|
|
case "silent":
|
|
content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0))
|
|
|
|
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, false
|
|
}
|
|
} else {
|
|
return "Message processing error", true, false
|
|
}
|
|
// 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 {
|
|
due += c.GetTZD()
|
|
|
|
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, false
|
|
}
|
|
}
|
|
|
|
content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 1))
|
|
|
|
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, false
|
|
}
|
|
return "Scheduled to " + result, true, true
|
|
} else {
|
|
return "Message processing error", true, false
|
|
}
|
|
// sends a raw non-interpreted message
|
|
case "raw":
|
|
content := c.PrepareOutgoingMessageContent(rawCmdArguments(cmdline, 0))
|
|
|
|
if content != nil {
|
|
_, err := c.client.SendMessage(&client.SendMessageRequest{
|
|
ChatId: chatID,
|
|
InputMessageContent: content,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
} else {
|
|
return "Message processing error", true, false
|
|
}
|
|
return "", true, true
|
|
// forward a message to chat
|
|
case "forward":
|
|
messageId, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return "Cannot parse message ID", true, false
|
|
}
|
|
|
|
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, false
|
|
}
|
|
|
|
messages, err := c.client.ForwardMessages(&client.ForwardMessagesRequest{
|
|
ChatId: targetChatId,
|
|
FromChatId: chatID,
|
|
MessageIds: []int64{messageId},
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if messages != nil && messages.Messages != nil {
|
|
for _, message := range messages.Messages {
|
|
c.ProcessIncomingMessage(targetChatId, message)
|
|
}
|
|
}
|
|
// print vCard
|
|
case "vcard":
|
|
info, err := c.GetVcardInfo(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
_, link := c.PermastoreFile(info.Photo, true)
|
|
entries := []string{
|
|
keyValueString("Chat title", info.Fn),
|
|
keyValueString("Photo", link),
|
|
keyValueString("Usernames", c.usernamesToString(info.Nicknames)),
|
|
keyValueString("Full name", info.Given+" "+info.Family),
|
|
keyValueString("Phone number", info.Tel),
|
|
}
|
|
return strings.Join(entries, "\n"), true, true
|
|
// add @contact
|
|
case "add":
|
|
response, success := c.cmdAdd(args)
|
|
return response, true, success
|
|
// join https://t.me/publichat or @publicchat
|
|
case "join":
|
|
response, success := c.cmdJoin(args)
|
|
return response, true, success
|
|
// create new supergroup
|
|
case "supergroup":
|
|
response, success := c.cmdSupergroup(args, cmdline)
|
|
return response, true, success
|
|
// create new channel
|
|
case "channel":
|
|
response, success := c.cmdChannel(args, cmdline)
|
|
return response, true, success
|
|
// create new secret chat with current user
|
|
case "secret":
|
|
_, err := c.client.CreateNewSecretChat(&client.CreateNewSecretChatRequest{
|
|
UserId: chatID,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// create group chat with current user
|
|
case "group":
|
|
_, err := c.client.CreateNewBasicGroupChat(&client.CreateNewBasicGroupChatRequest{
|
|
UserIds: []int64{chatID},
|
|
Title: args[0],
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// blacklists current user
|
|
case "block":
|
|
_, err := c.client.SetMessageSenderBlockList(&client.SetMessageSenderBlockListRequest{
|
|
SenderId: &client.MessageSenderUser{UserId: chatID},
|
|
BlockList: &client.BlockListMain{},
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// unblacklists current user
|
|
case "unblock":
|
|
_, err := c.client.SetMessageSenderBlockList(&client.SetMessageSenderBlockListRequest{
|
|
SenderId: &client.MessageSenderUser{UserId: chatID},
|
|
BlockList: nil,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// invite @username to current groupchat
|
|
case "invite":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
_, err = c.client.AddChatMember(&client.AddChatMemberRequest{
|
|
ChatId: chatID,
|
|
UserId: contact.Id,
|
|
ForwardLimit: 100,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// get link to current chat
|
|
case "link":
|
|
link, err := c.client.CreateChatInviteLink(&client.CreateChatInviteLinkRequest{
|
|
ChatId: chatID,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
return link.InviteLink, true, true
|
|
// kick @username from current group chat
|
|
case "kick":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusKicked, 0, "", "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// mute [@username [n hours]]
|
|
case "mute":
|
|
if len(args) > 0 {
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
var hours int64
|
|
if len(args) > 1 {
|
|
hours, err = strconv.ParseInt(args[1], 10, 32)
|
|
if err != nil {
|
|
return "Invalid number of hours", true, false
|
|
}
|
|
}
|
|
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusMuted, hours, "", "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
} else {
|
|
if !c.Session.IgnoreChat(chatID) {
|
|
return "Chat is already ignored", true, false
|
|
}
|
|
gateway.DirtySessions = true
|
|
}
|
|
// unmute [@username]
|
|
case "unmute":
|
|
if len(args) > 0 {
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusUnmuted, 0, "", "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
} else {
|
|
if !c.Session.UnignoreChat(chatID) {
|
|
return "Chat wasn't ignored", true, false
|
|
}
|
|
gateway.DirtySessions = true
|
|
}
|
|
// ban @username from current chat [for N hours]
|
|
case "ban":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
var hours int64
|
|
if len(args) > 1 {
|
|
hours, err = strconv.ParseInt(args[1], 10, 32)
|
|
if err != nil {
|
|
return "Invalid number of hours", true, false
|
|
}
|
|
}
|
|
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusBanned, hours, "", "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// unban @username
|
|
case "unban":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusUnbanned, 0, "", "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// promote @username to admin
|
|
case "promote":
|
|
contact, _, err := c.GetContactByUsername(args[0], false)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if contact == nil {
|
|
return "Contact not found", true, false
|
|
}
|
|
|
|
var customTitle string
|
|
if len(args) > 1 {
|
|
customTitle = args[1]
|
|
}
|
|
|
|
// clone the permissions
|
|
err = c.SetChatMemberStatus(chatID, contact.Id, ChatMemberStatusPromoted, 0, customTitle, "")
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// leave current chat
|
|
case "leave":
|
|
_, err := c.client.LeaveChat(&client.LeaveChatRequest{
|
|
ChatId: chatID,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
err = c.leaveChat(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// leave current chat (for owners)
|
|
case "leave!":
|
|
err := c.DeleteChat(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
err = c.leaveChat(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// 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, false
|
|
}
|
|
}
|
|
_, err = c.client.SetChatMessageAutoDeleteTime(&client.SetChatMessageAutoDeleteTimeRequest{
|
|
ChatId: chatID,
|
|
MessageAutoDeleteTime: int32(ttl),
|
|
})
|
|
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// close secret chat
|
|
case "close":
|
|
chat, _, err := c.GetContactByID(chatID, nil, true)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
if chat == nil {
|
|
return "Chat not found", true, false
|
|
}
|
|
|
|
chatType := chat.Type.ChatTypeType()
|
|
if chatType == client.TypeChatTypeSecret {
|
|
chatTypeSecret, _ := chat.Type.(*client.ChatTypeSecret)
|
|
_, err = c.client.CloseSecretChat(&client.CloseSecretChatRequest{
|
|
SecretChatId: chatTypeSecret.SecretChatId,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
err = c.leaveChat(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
}
|
|
// delete current chat
|
|
case "delete":
|
|
_, err := c.client.DeleteChatHistory(&client.DeleteChatHistoryRequest{
|
|
ChatId: chatID,
|
|
RemoveFromChatList: true,
|
|
Revoke: true,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
err = c.leaveChat(chatID)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
// message search
|
|
case "search":
|
|
var limit int32 = 100
|
|
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]
|
|
}
|
|
|
|
messages, err := c.getLastMessages(chatID, query, 0, limit)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
c.sendMessagesReverse(chatID, messages.Messages, true, "")
|
|
return "", true, true
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
messages, err := c.getNLastMessages(chatID, NewMessageLimitMessages(limit))
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
c.sendMessagesReverse(chatID, messages, true, "")
|
|
return "", true, true
|
|
// chat members
|
|
case "members":
|
|
var query string
|
|
if len(args) > 0 {
|
|
query = args[0]
|
|
}
|
|
|
|
members, err := c.GetChatMembers(chatID, false, query, MembersListMembers)
|
|
if err != nil {
|
|
return err.Error(), true, false
|
|
}
|
|
|
|
var entries []string
|
|
for _, member := range members {
|
|
senderId := c.GetSenderId(member.MemberId)
|
|
entries = append(entries, fmt.Sprintf(
|
|
"%v | role: %v",
|
|
c.FormatContact(senderId),
|
|
member.Status.ChatMemberStatusType(),
|
|
))
|
|
}
|
|
|
|
return strings.Join(entries, "\n"), true, true
|
|
case "help":
|
|
return c.helpString(gateway.CommandTypeChat, chatID), true, true
|
|
default:
|
|
return "", false, false
|
|
}
|
|
|
|
return "Success", true, true
|
|
}
|
|
|
|
func (c *Client) cmdAdd(args []string) (string, bool) {
|
|
chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{
|
|
Username: args[0],
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
if chat == nil {
|
|
return "No error, but chat is nil", false
|
|
}
|
|
|
|
c.subscribeToID(chat.Id, chat, true)
|
|
|
|
return "Subscription sent", true
|
|
}
|
|
|
|
func (c *Client) cmdJoin(args []string) (string, bool) {
|
|
if strings.HasPrefix(args[0], "@") {
|
|
chat, err := c.client.SearchPublicChat(&client.SearchPublicChatRequest{
|
|
Username: args[0],
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
if chat == nil {
|
|
return "No error, but chat is nil", false
|
|
}
|
|
_, err = c.client.JoinChat(&client.JoinChatRequest{
|
|
ChatId: chat.Id,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
} else {
|
|
_, err := c.client.JoinChatByInviteLink(&client.JoinChatByInviteLinkRequest{
|
|
InviteLink: args[0],
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
}
|
|
|
|
return "Joined", true
|
|
}
|
|
|
|
func (c *Client) cmdSupergroup(args []string, cmdline string) (string, bool) {
|
|
_, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{
|
|
Title: args[0],
|
|
Description: rawCmdArguments(cmdline, 1),
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
|
|
return "Created", true
|
|
}
|
|
|
|
func (c *Client) cmdChannel(args []string, cmdline string) (string, bool) {
|
|
_, err := c.client.CreateNewSupergroupChat(&client.CreateNewSupergroupChatRequest{
|
|
Title: args[0],
|
|
Description: rawCmdArguments(cmdline, 1),
|
|
IsChannel: true,
|
|
})
|
|
if err != nil {
|
|
return err.Error(), false
|
|
}
|
|
|
|
return "Created", true
|
|
}
|