diff --git a/errors.go b/errors.go index 3d8f6ef9..a4c66d1d 100755 --- a/errors.go +++ b/errors.go @@ -781,12 +781,12 @@ func (*errorSessionConfigsChanged) CRC() uint32 { return 0x00000000 } -//type unexpectedEOFError struct{} +type errorReconnectRequired struct{} -//func (*unexpectedEOFError) Error() string { -// return "unexpected error: unexpected EOF" -//} +func (*errorReconnectRequired) Error() string { + return "session configuration was changed, need to repeat request" +} -//func (*unexpectedEOFError) CRC() uint32 { -// return 0x00000000 -//} +func (*errorReconnectRequired) CRC() uint32 { + return 0x00000000 +} diff --git a/examples/stars/stars.go b/examples/stars/stars.go new file mode 100644 index 00000000..9a610263 --- /dev/null +++ b/examples/stars/stars.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + + "github.com/amarnathcjd/gogram/telegram" +) + +var paidUsers = make(map[int64]int32) + +func main() { + bot, _ := telegram.NewClient(telegram.ClientConfig{ + AppID: 123456, + AppHash: "YOUR_APP_HASH", + }) + + bot.Conn() + bot.LoginBot("YOUR_BOT_TOKEN") + + bot.AddRawHandler(&telegram.UpdateBotPrecheckoutQuery{}, preCheckoutQueryUpd) + bot.On("edit", func(m *telegram.NewMessage) error { + paidUsers[m.SenderID()] = m.Invoice().ReceiptMsgID + return bot.E(m.Respond("Payment Successful!")) + }, telegram.FilterFunc(func(upd *telegram.NewMessage) bool { + return upd.Invoice() != nil && upd.Invoice().ReceiptMsgID != 0 + })) + + bot.On("message:pay", payCmd) + bot.On("message:status", statusCmd) + bot.On("message:refund", refundCmd) // new type of message handlers, eg<> +} + +func payCmd(m *telegram.NewMessage) error { + invoice := telegram.InputMediaInvoice{ + Title: "Test Product", + Description: "Test Description", + Payload: []byte(""), + Invoice: &telegram.Invoice{ + Test: true, + Currency: "USD", + Prices: []*telegram.LabeledPrice{ + { + Amount: 1, + Label: "1 USD", + }, + }, + }, + ProviderData: &telegram.DataJson{}, + } + + m.ReplyMedia(&invoice) + return nil +} + +func preCheckoutQueryUpd(upd telegram.Update, c *telegram.Client) error { + _upd := upd.(*telegram.UpdateBotPrecheckoutQuery) + c.MessagesSetBotPrecheckoutResults(true, _upd.QueryID, "Success") + return nil +} + +func statusCmd(m *telegram.NewMessage) error { + if _, ok := paidUsers[m.SenderID()]; ok { + return m.E(m.Respond("You have paid!")) + } + return m.E(m.Respond("You have not paid!")) +} + +func refundCmd(m *telegram.NewMessage) error { + if recpt_id, ok := paidUsers[m.SenderID()]; ok { + delete(paidUsers, m.SenderID()) + u, _ := m.Client.GetSendableUser(m.SenderID()) + m.Client.PaymentsRefundStarsCharge(u, fmt.Sprintf("%d", recpt_id)) + return m.E(m.Respond("Refund Successful!")) + } + return m.E(m.Respond("You have not paid!")) +} diff --git a/mtproto.go b/mtproto.go index 2ba3002c..94fc0615 100755 --- a/mtproto.go +++ b/mtproto.go @@ -28,7 +28,7 @@ import ( "github.com/pkg/errors" ) -const defaultTimeout = 30 * time.Second +const defaultTimeout = 60 * time.Second // after 60 sec without any read/write, lib will try to reconnect type MTProto struct { Addr string @@ -67,7 +67,8 @@ type MTProto struct { serviceChannel chan tl.Object serviceModeActivated bool - authKey404 []int64 + authKey404 []int64 + shouldTransfer bool Logger *utils.Logger @@ -133,6 +134,8 @@ func NewMTProto(c Config) (*MTProto, error) { } //mtproto.offsetTime() + go mtproto.checkBreaking() + return mtproto, nil } @@ -189,7 +192,7 @@ func (m *MTProto) ImportAuth(stringSession string) (bool, error) { return false, err } m.authKey, m.authKeyHash, m.Addr, m.appID = sessionString.AuthKey(), sessionString.AuthKeyHash(), sessionString.IpAddr(), sessionString.AppID() - m.Logger.Debug("importing Auth from stringSession...") + m.Logger.Debug("importing - auth from stringSession...") if !m.memorySession { if err := m.SaveSession(); err != nil { return false, fmt.Errorf("saving session: %w", err) @@ -198,6 +201,10 @@ func (m *MTProto) ImportAuth(stringSession string) (bool, error) { return true, nil } +func (m *MTProto) SetTransfer(transfer bool) { + m.shouldTransfer = transfer +} + func (m *MTProto) GetDC() int { return utils.SearchAddr(m.Addr) } @@ -271,9 +278,9 @@ func (m *MTProto) CreateConnection(withLog bool) error { ctx, cancelfunc := context.WithCancel(context.Background()) m.stopRoutines = cancelfunc if withLog { - m.Logger.Info("Connecting to [" + m.Addr + "] - ...") + m.Logger.Info(fmt.Sprintf("connecting to [%s] - ...", m.Addr)) } else { - m.Logger.Debug("Connecting to [" + m.Addr + "] - ...") + m.Logger.Debug("connecting to [" + m.Addr + "] - ...") } err := m.connect(ctx) if err != nil { @@ -282,15 +289,15 @@ func (m *MTProto) CreateConnection(withLog bool) error { m.tcpActive = true if withLog { if m.proxy != nil && m.proxy.Host != "" { - m.Logger.Info("Connection to (~" + m.proxy.Host + ")[" + m.Addr + "] - established") + m.Logger.Info(fmt.Sprintf("connection to (~%s)[%s] - established", m.proxy.Host, m.Addr)) } else { - m.Logger.Info("Connection to [" + m.Addr + "] - established") + m.Logger.Info(fmt.Sprintf("connection to [%s] - established", m.Addr)) } } else { if m.proxy != nil && m.proxy.Host != "" { - m.Logger.Debug("Connection to (~" + m.proxy.Host + ")[" + m.Addr + "] - established") + m.Logger.Debug("connection to (~" + m.proxy.Host + ")[" + m.Addr + "] - established") } else { - m.Logger.Debug("Connection to [" + m.Addr + "] - established") + m.Logger.Debug("connection to [" + m.Addr + "] - established") } } @@ -323,13 +330,15 @@ func (m *MTProto) connect(ctx context.Context) error { return fmt.Errorf("creating transport: %w", err) } + m.SetTransfer(true) + go closeOnCancel(ctx, m.transport) return nil } func (m *MTProto) makeRequest(data tl.Object, expectedTypes ...reflect.Type) (any, error) { if !m.TcpActive() { - return nil, errors.New("can't make request. connection is not established") + _ = m.CreateConnection(false) } resp, err := m.sendPacket(data, expectedTypes...) if err != nil { @@ -346,16 +355,20 @@ func (m *MTProto) makeRequest(data tl.Object, expectedTypes ...reflect.Type) (an response := <-resp switch r := response.(type) { case *objects.RpcError: - //if err := RpcErrorToNative(r).(*ErrResponseCode); strings.Contains(err.Message, "FLOOD_WAIT_") { - //m.Logger.Info("flood wait detected on '" + strings.ReplaceAll(reflect.TypeOf(data).Elem().Name(), "Params", "") + fmt.Sprintf("' request. sleeping for %s", (time.Duration(realErr.AdditionalInfo.(int))*time.Second).String())) - //time.Sleep(time.Duration(realErr.AdditionalInfo.(int)) * time.Second) - //return m.makeRequest(data, expectedTypes...) TODO: implement flood wait correctly - //} + // if err := RpcErrorToNative(r).(*ErrResponseCode); strings.Contains(err.Message, "FLOOD_WAIT_") { + // m.Logger.Info("flood wait detected on '" + strings.ReplaceAll(reflect.TypeOf(data).Elem().Name(), "Params", "") + fmt.Sprintf("' request. sleeping for %s", (time.Duration(realErr.AdditionalInfo.(int))*time.Second).String())) + // time.Sleep(time.Duration(realErr.AdditionalInfo.(int)) * time.Second) + // return m.makeRequest(data, expectedTypes...) TODO: implement flood wait correctly + // } return nil, RpcErrorToNative(r) case *errorSessionConfigsChanged: m.Logger.Debug("session configs changed, resending request") return m.makeRequest(data, expectedTypes...) + + case *errorReconnectRequired: + m.Logger.Info("req info: " + fmt.Sprintf("%T", data)) + return nil, errors.New("required to reconnect!") } return tl.UnwrapNativeTypes(response), nil @@ -376,6 +389,12 @@ func (m *MTProto) TcpActive() bool { func (m *MTProto) Disconnect() error { m.stopRoutines() m.tcpActive = false + + for _, v := range m.responseChannels.Keys() { + ch, _ := m.responseChannels.Get(v) + ch <- &errorReconnectRequired{} + } + // m.responseChannels.Close() return nil } @@ -394,18 +413,18 @@ func (m *MTProto) Reconnect(WithLogs bool) error { return errors.Wrap(err, "disconnecting") } if WithLogs { - m.Logger.Info("Reconnecting to [" + m.Addr + "] - ...") + m.Logger.Info(fmt.Sprintf("reconnecting to [%s] - ...", m.Addr)) } err = m.CreateConnection(WithLogs) if err == nil && WithLogs { - m.Logger.Info("Reconnected to [" + m.Addr + "] - ...") + m.Logger.Info(fmt.Sprintf("reconnected to [%s] - ", m.Addr)) } m.InvokeRequestWithoutUpdate(&utils.PingParams{ PingID: 123456789, }) - m.MakeRequest(&utils.UpdatesGetStateParams{}) // to ask the server to send the updates + //m.MakeRequest(&utils.UpdatesGetStateParams{}) // to ask the server to send the updates return errors.Wrap(err, "recreating connection") } @@ -567,7 +586,6 @@ messageTypeSwitching: respChannelsBackup = m.responseChannels m.responseChannels = utils.NewSyncIntObjectChan() - m.Reconnect(false) for _, k := range respChannelsBackup.Keys() { @@ -593,7 +611,7 @@ messageTypeSwitching: if badMsg.Code == 16 || badMsg.Code == 17 { m.offsetTime() } - m.Logger.Debug("badMsgNotification: " + badMsg.Error()) + m.Logger.Debug("bad-msg-notification: " + badMsg.Error()) return badMsg case *objects.RpcResult: obj := message.Obj @@ -606,7 +624,7 @@ messageTypeSwitching: if strings.Contains(err.Error(), "no response channel found") { m.Logger.Error(errors.Wrap(err, "writing rpc response")) } else { - return errors.Wrap(err, "writing RPC response") + return errors.Wrap(err, "writing rpc response") } } @@ -625,7 +643,7 @@ messageTypeSwitching: } } if !processed { - m.Logger.Debug("~ unhandled update: " + fmt.Sprintf("%T", message)) + m.Logger.Debug("unhandled update: " + fmt.Sprintf("%T", message)) } } @@ -682,3 +700,20 @@ func closeOnCancel(ctx context.Context, c io.Closer) { c.Close() }() } + +func (m *MTProto) checkBreaking() { + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + for range ticker.C { + if m.shouldTransfer && !m.TcpActive() { + m.CreateConnection(false) + for i := 0; i < 2; i++ { + _, err := m.MakeRequest(&utils.UpdatesGetStateParams{}) + if err == nil { + break + } + } + } + } +} diff --git a/telegram/callbackquery.go b/telegram/callbackquery.go index 11a9d54f..e42032da 100755 --- a/telegram/callbackquery.go +++ b/telegram/callbackquery.go @@ -177,8 +177,8 @@ func (b *CallbackQuery) ForwardTo(ChatID int64, options ...*ForwardOptions) (*Ne return &m[0], nil } -func (b *CallbackQuery) Marshal() string { - return b.Client.JSON(b.OriginalUpdate) +func (b *CallbackQuery) Marshal(nointent ...bool) string { + return b.Client.JSON(b.OriginalUpdate, nointent) } type InlineCallbackQuery struct { @@ -255,6 +255,6 @@ func (b *InlineCallbackQuery) IsChannel() bool { return b.ChatType() == EntityChannel } -func (b *InlineCallbackQuery) Marshal() string { - return b.Client.JSON(b.OriginalUpdate) +func (b *InlineCallbackQuery) Marshal(nointent ...bool) string { + return b.Client.JSON(b.OriginalUpdate, nointent) } diff --git a/telegram/client.go b/telegram/client.go index a80c972b..c463034d 100644 --- a/telegram/client.go +++ b/telegram/client.go @@ -123,10 +123,6 @@ func NewClient(config ClientConfig) (*Client, error) { client.Log.Debug("client is running in no updates mode, no updates will be handled") } else { client.setupDispatcher() - if client.IsConnected() { - // TODO: Implement same for manual connect Call. - // client.UpdatesGetState() - } } if err := client.clientWarnings(config); err != nil { return nil, err @@ -204,7 +200,7 @@ func (c *Client) clientWarnings(config ClientConfig) error { } func (c *Client) setupDispatcher() { - c.dispatcher = &UpdateDispatcher{} + c.NewUpdateDispatcher() handleUpdaterWrapper := func(u any) bool { return HandleIncomingUpdates(u, c) } @@ -549,6 +545,7 @@ func (c *Client) Idle() { func (c *Client) Stop() error { close(c.stopCh) go c.cleanExportedSenders() + c.MTProto.SetTransfer(false) // to stop connection break check. return c.MTProto.Terminate() } @@ -572,3 +569,13 @@ func (c *Client) WrapError(err error) error { } return err } + +// return only the object, omitting the error +func (c *Client) W(obj any, err error) any { + return obj +} + +// return only the error, omitting the object +func (c *Client) E(obj any, err error) error { + return err +} diff --git a/telegram/const.go b/telegram/const.go index 8b4fd07a..9a27250e 100644 --- a/telegram/const.go +++ b/telegram/const.go @@ -4,7 +4,7 @@ import "regexp" const ( ApiVersion = 184 - Version = "v2.3.14" + Version = "v2.3.15" LogDebug = "debug" LogInfo = "info" diff --git a/telegram/helpers.go b/telegram/helpers.go index 5f23dd13..f4a475d9 100644 --- a/telegram/helpers.go +++ b/telegram/helpers.go @@ -1085,10 +1085,27 @@ func ComputeDigest(algo *PasswordKdfAlgoSHA256SHA256Pbkdf2Hmacsha512Iter100000SH } // easy wrapper for json.MarshalIndent, returns string -func (c *Client) JSON(object interface{}) string { +func (c *Client) JSON(object interface{}, nointent ...any) string { + if len(nointent) > 0 { + switch _noi := nointent[0].(type) { + case bool: + if _noi { + data, err := json.Marshal(object) + if err != nil { + return fmt.Sprintf("marshal: %s", err) + } + return string(data) + } + } + } + data, err := json.MarshalIndent(object, "", " ") if err != nil { - return fmt.Sprintf("Error: %s", err) + return fmt.Sprintf("marshal: %s", err) } return string(data) } + +// func (c *Client) JSON(object interface{}, err error) { +// return +// } diff --git a/telegram/inlinequery.go b/telegram/inlinequery.go index ad453725..b2f8d55d 100755 --- a/telegram/inlinequery.go +++ b/telegram/inlinequery.go @@ -279,8 +279,8 @@ func (b *InlineQuery) IsPrivate() bool { return b.PeerType == InlineQueryPeerTypePm || b.PeerType == InlineQueryPeerTypeSameBotPm } -func (b *InlineQuery) Marshal() string { - return b.Client.JSON(b.OriginalUpdate) +func (b *InlineQuery) Marshal(nointent ...bool) string { + return b.Client.JSON(b.OriginalUpdate, nointent) } func (m *InlineQuery) Args() string { diff --git a/telegram/newmessage.go b/telegram/newmessage.go index 4b11a026..bcc1b1a2 100755 --- a/telegram/newmessage.go +++ b/telegram/newmessage.go @@ -137,6 +137,11 @@ func (m *NewMessage) IsPrivate() bool { return m.ChatType() == EntityUser } +// returns the error only, of a method +func (m *NewMessage) E(obj any, err error) error { + return err +} + func (m *NewMessage) IsGroup() bool { if m.Channel != nil { return m.ChatType() == EntityChat || (m.ChatType() == EntityChannel && !m.Channel.Broadcast) @@ -156,8 +161,8 @@ func (m *NewMessage) IsReply() bool { return m.Message.ReplyTo != nil } -func (m *NewMessage) Marshal() string { - return m.Client.JSON(m.OriginalUpdate) +func (m *NewMessage) Marshal(nointent ...bool) string { + return m.Client.JSON(m.OriginalUpdate, nointent) } func (m *NewMessage) Unmarshal(data []byte) (*NewMessage, error) { @@ -590,13 +595,13 @@ type Album struct { Messages []*NewMessage } -func (a *Album) Marshal() string { +func (a *Album) Marshal(nointent ...bool) string { var messages []Message for _, m := range a.Messages { messages = append(messages, m.OriginalUpdate) } - return a.Client.JSON(messages) + return a.Client.JSON(messages, nointent) } func (a *Album) Download(opts ...*DownloadOptions) ([]string, error) { diff --git a/telegram/participant.go b/telegram/participant.go index 9fdefc08..e1c171ac 100755 --- a/telegram/participant.go +++ b/telegram/participant.go @@ -35,7 +35,7 @@ func (pu *ParticipantUpdate) ActorID() int64 { return 0 } -func (pu *ParticipantUpdate) Added() bool { +func (pu *ParticipantUpdate) IsAdded() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantBanned); ok { if _, ok := pu.New.(*ChannelParticipantObj); ok { @@ -51,11 +51,11 @@ func (pu *ParticipantUpdate) Added() bool { return false } -func (pu *ParticipantUpdate) Left() bool { +func (pu *ParticipantUpdate) IsLeft() bool { return pu.New == nil } -func (pu *ParticipantUpdate) Joined() bool { +func (pu *ParticipantUpdate) IsJoined() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantLeft); ok { if _, ok := pu.New.(*ChannelParticipantObj); ok { @@ -71,7 +71,7 @@ func (pu *ParticipantUpdate) Joined() bool { return false } -func (pu *ParticipantUpdate) Banned() bool { +func (pu *ParticipantUpdate) IsBanned() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantObj); ok { if _, ok := pu.New.(*ChannelParticipantBanned); ok { @@ -82,7 +82,7 @@ func (pu *ParticipantUpdate) Banned() bool { return false } -func (pu *ParticipantUpdate) Kicked() bool { +func (pu *ParticipantUpdate) IsKicked() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantObj); ok { if _, ok := pu.New.(*ChannelParticipantLeft); ok { @@ -93,7 +93,7 @@ func (pu *ParticipantUpdate) Kicked() bool { return false } -func (pu *ParticipantUpdate) Promoted() bool { +func (pu *ParticipantUpdate) IsPromoted() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantObj); ok { if _, ok := pu.New.(*ChannelParticipantAdmin); ok { @@ -109,7 +109,7 @@ func (pu *ParticipantUpdate) Promoted() bool { return false } -func (pu *ParticipantUpdate) Demoted() bool { +func (pu *ParticipantUpdate) IsDemoted() bool { if pu.Old != nil && pu.New != nil { if _, ok := pu.Old.(*ChannelParticipantAdmin); ok { if _, ok := pu.New.(*ChannelParticipantObj); ok { @@ -123,8 +123,8 @@ func (pu *ParticipantUpdate) Demoted() bool { return false } -func (pu *ParticipantUpdate) Marshal() string { - return pu.Client.JSON(pu.OriginalUpdate) +func (pu *ParticipantUpdate) Marshal(nointent ...bool) string { + return pu.Client.JSON(pu.OriginalUpdate, nointent) } // Rest Functions to be implemented diff --git a/telegram/updates.go b/telegram/updates.go index a2ab4de0..38628bbb 100644 --- a/telegram/updates.go +++ b/telegram/updates.go @@ -8,72 +8,50 @@ import ( "strings" "sync" "time" + "unsafe" ) -const DEF_ALBUM_WAIT_TIME = 600 * time.Millisecond +type MessageHandler func(m *NewMessage) error +type EditHandler func(m *NewMessage) error +type DeleteHandler func(m *DeleteMessage) error +type AlbumHandler func(m *Album) error +type InlineHandler func(m *InlineQuery) error +type CallbackHandler func(m *CallbackQuery) error +type InlineCallbackHandler func(m *InlineCallbackQuery) error +type ParticipantHandler func(m *ParticipantUpdate) error +type RawHandler func(m Update, c *Client) error type messageHandle struct { Pattern interface{} - Handler func(m *NewMessage) error + Handler MessageHandler Filters []Filter } func (c *Client) removeHandle(h interface{}) { - switch handle := h.(type) { - case *messageHandle: - for i, h := range c.dispatcher.messageHandles { + removeHandleFromSlice := func(handles []interface{}, handle interface{}) []interface{} { + for i, h := range handles { if reflect.DeepEqual(h, handle) { - c.dispatcher.messageHandles = append(c.dispatcher.messageHandles[:i], c.dispatcher.messageHandles[i+1:]...) - } - } - case *albumHandle: - for i, h := range c.dispatcher.albumHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.albumHandles = append(c.dispatcher.albumHandles[:i], c.dispatcher.albumHandles[i+1:]...) - } - } - case *chatActionHandle: - for i, h := range c.dispatcher.actionHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.actionHandles = append(c.dispatcher.actionHandles[:i], c.dispatcher.actionHandles[i+1:]...) - } - } - case *messageEditHandle: - for i, h := range c.dispatcher.messageEditHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.messageEditHandles = append(c.dispatcher.messageEditHandles[:i], c.dispatcher.messageEditHandles[i+1:]...) - } - } - case *inlineHandle: - for i, h := range c.dispatcher.inlineHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.inlineHandles = append(c.dispatcher.inlineHandles[:i], c.dispatcher.inlineHandles[i+1:]...) - } - } - case *callbackHandle: - for i, h := range c.dispatcher.callbackHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.callbackHandles = append(c.dispatcher.callbackHandles[:i], c.dispatcher.callbackHandles[i+1:]...) - } - } - case *inlineCallbackHandle: - for i, h := range c.dispatcher.inlineCallbackHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.inlineCallbackHandles = append(c.dispatcher.inlineCallbackHandles[:i], c.dispatcher.inlineCallbackHandles[i+1:]...) - } - } - case *participantHandle: - for i, h := range c.dispatcher.participantHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.participantHandles = append(c.dispatcher.participantHandles[:i], c.dispatcher.participantHandles[i+1:]...) - } - } - case *rawHandle: - for i, h := range c.dispatcher.rawHandles { - if reflect.DeepEqual(h, handle) { - c.dispatcher.rawHandles = append(c.dispatcher.rawHandles[:i], c.dispatcher.rawHandles[i+1:]...) + return append(handles[:i], handles[i+1:]...) } } + return handles + } + + handleMap := map[reflect.Type]*[]interface{}{ + reflect.TypeOf((*messageHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.messageHandles)), + reflect.TypeOf((*albumHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.albumHandles)), + reflect.TypeOf((*chatActionHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.actionHandles)), + reflect.TypeOf((*messageEditHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.messageEditHandles)), + reflect.TypeOf((*inlineHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.inlineHandles)), + reflect.TypeOf((*callbackHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.callbackHandles)), + reflect.TypeOf((*inlineCallbackHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.inlineCallbackHandles)), + reflect.TypeOf((*participantHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.participantHandles)), + reflect.TypeOf((*rawHandle)(nil)): (*[]interface{})(unsafe.Pointer(&c.dispatcher.rawHandles)), + } + + handleType := reflect.TypeOf(h) + if handles, ok := handleMap[handleType]; ok { + *handles = removeHandleFromSlice(*handles, h) } } @@ -89,7 +67,7 @@ type albumBox struct { } func (a *albumBox) Wait() { - time.Sleep(DEF_ALBUM_WAIT_TIME) + time.Sleep(600 * time.Millisecond) a.waitExit <- struct{}{} } @@ -100,11 +78,11 @@ func (a *albumBox) Add(m *NewMessage) { } type chatActionHandle struct { - Handler func(m *NewMessage) error + Handler MessageHandler } type messageEditHandle struct { Pattern interface{} - Handler func(m *NewMessage) error + Handler MessageHandler } type messageDeleteHandle struct { @@ -114,17 +92,17 @@ type messageDeleteHandle struct { type inlineHandle struct { Pattern interface{} - Handler func(m *InlineQuery) error + Handler InlineHandler } type callbackHandle struct { Pattern interface{} - Handler func(m *CallbackQuery) error + Handler CallbackHandler } type inlineCallbackHandle struct { Pattern interface{} - Handler func(m *InlineCallbackQuery) error + Handler InlineCallbackHandler } type participantHandle struct { @@ -133,7 +111,7 @@ type participantHandle struct { type rawHandle struct { updateType Update - Handler func(m Update, c *Client) error + Handler RawHandler } type UpdateDispatcher struct { @@ -150,6 +128,11 @@ type UpdateDispatcher struct { activeAlbums map[int64]*albumBox } +// creates and populates a new UpdateDispatcher +func (c *Client) NewUpdateDispatcher() { + c.dispatcher = &UpdateDispatcher{} +} + func (c *Client) handleMessageUpdate(update Message) { switch msg := update.(type) { case *MessageObj: @@ -494,7 +477,7 @@ var ( } ) -func (c *Client) AddMessageHandler(pattern interface{}, handler func(m *NewMessage) error, filters ...Filter) messageHandle { +func (c *Client) AddMessageHandler(pattern interface{}, handler MessageHandler, filters ...Filter) messageHandle { var messageFilters []Filter if len(filters) > 0 { messageFilters = filters @@ -518,7 +501,7 @@ func (c *Client) AddAlbumHandler(handler func(m *Album) error) albumHandle { return albumHandle{Handler: handler} } -func (c *Client) AddActionHandler(handler func(m *NewMessage) error) chatActionHandle { +func (c *Client) AddActionHandler(handler MessageHandler) chatActionHandle { c.dispatcher.actionHandles = append(c.dispatcher.actionHandles, chatActionHandle{Handler: handler}) return chatActionHandle{Handler: handler} } @@ -528,7 +511,7 @@ func (c *Client) AddActionHandler(handler func(m *NewMessage) error) chatActionH // Included Updates: // - Message Edited // - Channel Post Edited -func (c *Client) AddEditHandler(pattern interface{}, handler func(m *NewMessage) error) messageEditHandle { +func (c *Client) AddEditHandler(pattern interface{}, handler MessageHandler) messageEditHandle { handle := messageEditHandle{Pattern: pattern, Handler: handler} c.dispatcher.messageEditHandles = append(c.dispatcher.messageEditHandles, handle) return handle @@ -538,7 +521,7 @@ func (c *Client) AddEditHandler(pattern interface{}, handler func(m *NewMessage) // // Included Updates: // - Inline Query -func (c *Client) AddInlineHandler(pattern interface{}, handler func(m *InlineQuery) error) inlineHandle { +func (c *Client) AddInlineHandler(pattern interface{}, handler InlineHandler) inlineHandle { handle := inlineHandle{Pattern: pattern, Handler: handler} c.dispatcher.inlineHandles = append(c.dispatcher.inlineHandles, handle) return handle @@ -548,7 +531,7 @@ func (c *Client) AddInlineHandler(pattern interface{}, handler func(m *InlineQue // // Included Updates: // - Callback Query -func (c *Client) AddCallbackHandler(pattern interface{}, handler func(m *CallbackQuery) error) callbackHandle { +func (c *Client) AddCallbackHandler(pattern interface{}, handler CallbackHandler) callbackHandle { handle := callbackHandle{Pattern: pattern, Handler: handler} c.dispatcher.callbackHandles = append(c.dispatcher.callbackHandles, handle) return handle @@ -558,7 +541,7 @@ func (c *Client) AddCallbackHandler(pattern interface{}, handler func(m *Callbac // // Included Updates: // - Inline Callback Query -func (c *Client) AddInlineCallbackHandler(pattern interface{}, handler func(m *InlineCallbackQuery) error) inlineCallbackHandle { +func (c *Client) AddInlineCallbackHandler(pattern interface{}, handler InlineCallbackHandler) inlineCallbackHandle { handle := inlineCallbackHandle{Pattern: pattern, Handler: handler} c.dispatcher.inlineCallbackHandles = append(c.dispatcher.inlineCallbackHandles, handle) return handle @@ -573,13 +556,13 @@ func (c *Client) AddInlineCallbackHandler(pattern interface{}, handler func(m *I // - Kicked Channel Participant // - Channel Participant Admin // - Channel Participant Creator -func (c *Client) AddParticipantHandler(handler func(m *ParticipantUpdate) error) participantHandle { +func (c *Client) AddParticipantHandler(handler ParticipantHandler) participantHandle { handle := participantHandle{Handler: handler} c.dispatcher.participantHandles = append(c.dispatcher.participantHandles, handle) return handle } -func (c *Client) AddRawHandler(updateType Update, handler func(m Update, c *Client) error) rawHandle { +func (c *Client) AddRawHandler(updateType Update, handler RawHandler) rawHandle { handle := rawHandle{updateType: updateType, Handler: handler} c.dispatcher.rawHandles = append(c.dispatcher.rawHandles, handle) return handle @@ -642,7 +625,7 @@ UpdateTypeSwitching: c.Log.Debug("update gap is too long, requesting getState") c.UpdatesGetState() default: - c.Log.Warn("skipping unhanded update (", reflect.TypeOf(u), "): ", c.JSON(u)) + c.Log.Debug("skipping unhanded update (", reflect.TypeOf(u), "): ", c.JSON(u)) } return true } @@ -684,45 +667,98 @@ func (c *Client) GetDifference(Pts, Limit int32) (Message, error) { return nil, nil } -// TODO: impl like client.On("message", func(m *NewMessage) { ... }) - -// interface for all common events -// type ev interface{} - -// var ( -// OnMessage ev = "message" -// OnEdit ev = "edit" -// OnDelete ev = "delete" -// OnInline ev = "inline" -// OnCallback ev = "callback" -// OnInlineCallback ev = "inlineCallback" -// OnParticipant ev = "participant" -// OnRaw ev = "raw" -// ) - -// type handleInterface interface{} - -// func (c *Client) On(pattern interface{}, handler interface{}, filters ...Filter) handleInterface { -// switch pattern := pattern.(type) { -// case ev: -// switch pattern { -// case OnMessage: -// return c.AddMessageHandler(OnNewMessage, handler, filters...) -// case OnEditMessage: -// return c.AddEditHandler(OnEditMessage, handler) -// case OnInline: -// return c.AddInlineHandler(OnInlineQuery, handler) -// case OnCallback: -// return c.AddCallbackHandler(OnCallbackQuery, handler) -// case OnInlineCallback: -// return c.AddInlineCallbackHandler(OnInlineCallbackQuery, handler) -// case OnParticipant: -// return c.AddParticipantHandler(handler) -// case OnRaw: -// return c.AddRawHandler(Update{}, func(m Update, c *Client) error { -// return handler(packUpdate(c, m)) -// }) -// } -// } -// return c.AddMessageHandler(pattern, handler, filters...) -// } +type ev interface{} + +var ( + OnMessage ev = "message" + OnEdit ev = "edit" + OnDelete ev = "delete" + OnAlbum ev = "album" + OnInline ev = "inline" + OnCallback ev = "callback" + OnInlineCallback ev = "inlineCallback" + OnParticipant ev = "participant" + OnRaw ev = "raw" +) + +type handleInterface interface{} + +func (c *Client) On(pattern any, handler interface{}, filters ...Filter) handleInterface { + var patternKey string + var args string + + if patternStr, ok := pattern.(string); ok { + if strings.Contains(patternStr, ":") { + parts := strings.SplitN(patternStr, ":", 2) + patternKey = strings.TrimSpace(parts[0]) + args = strings.TrimSpace(parts[1]) + } else { + patternKey = strings.TrimSpace(patternStr) + } + } + + switch ev(patternKey) { + case OnMessage: + if h, ok := handler.(func(m *NewMessage) error); ok { + if args != "" { + return c.AddMessageHandler(args, h, filters...) + } + return c.AddMessageHandler(OnNewMessage, h, filters...) + } + case OnEdit: + if h, ok := handler.(func(m *NewMessage) error); ok { + if args != "" { + return c.AddEditHandler(args, h) + } + return c.AddEditHandler(OnEditMessage, h) + } + case OnDelete: + if h, ok := handler.(func(m *DeleteMessage) error); ok { + if args != "" { + return c.AddDeleteHandler(args, h) + } + return c.AddDeleteHandler(OnDeleteMessage, h) + } + case OnAlbum: + if h, ok := handler.(func(m *Album) error); ok { + return c.AddAlbumHandler(h) + } + case OnInline: + if h, ok := handler.(func(m *InlineQuery) error); ok { + if args != "" { + return c.AddInlineHandler(args, h) + } + return c.AddInlineHandler(OnInlineQuery, h) + } + case OnCallback: + if h, ok := handler.(func(m *CallbackQuery) error); ok { + if args != "" { + return c.AddCallbackHandler(args, h) + } + return c.AddCallbackHandler(OnCallbackQuery, h) + } + case OnInlineCallback: + if h, ok := handler.(func(m *InlineCallbackQuery) error); ok { + if args != "" { + return c.AddInlineCallbackHandler(args, h) + } + return c.AddInlineCallbackHandler(OnInlineCallbackQuery, h) + } + case OnParticipant: + if h, ok := handler.(func(m *ParticipantUpdate) error); ok { + return c.AddParticipantHandler(h) + } + case OnRaw: + if h, ok := handler.(func(m Update, c *Client) error); ok { + return c.AddRawHandler(nil, h) + } + default: + if update, ok := pattern.(Update); ok { + if h, ok := handler.(func(m Update, c *Client) error); ok { + return c.AddRawHandler(update, h) + } + } + } + + return nil +}