mirror of https://github.com/velour/catbase.git
commit
695c749727
|
@ -93,6 +93,7 @@ func New(config *config.Config, connector Connector) Bot {
|
||||||
|
|
||||||
connector.RegisterMessageReceived(bot.MsgReceived)
|
connector.RegisterMessageReceived(bot.MsgReceived)
|
||||||
connector.RegisterEventReceived(bot.EventReceived)
|
connector.RegisterEventReceived(bot.EventReceived)
|
||||||
|
connector.RegisterReplyMessageReceived(bot.ReplyMsgReceived)
|
||||||
|
|
||||||
return bot
|
return bot
|
||||||
}
|
}
|
||||||
|
@ -145,7 +146,7 @@ func (b *bot) migrateDB() {
|
||||||
|
|
||||||
// Adds a constructed handler to the bots handlers list
|
// Adds a constructed handler to the bots handlers list
|
||||||
func (b *bot) AddHandler(name string, h Handler) {
|
func (b *bot) AddHandler(name string, h Handler) {
|
||||||
b.plugins[strings.ToLower(name)] = h
|
b.plugins[name] = h
|
||||||
b.pluginOrdering = append(b.pluginOrdering, name)
|
b.pluginOrdering = append(b.pluginOrdering, name)
|
||||||
if entry := h.RegisterWeb(); entry != nil {
|
if entry := h.RegisterWeb(); entry != nil {
|
||||||
b.httpEndPoints[name] = *entry
|
b.httpEndPoints[name] = *entry
|
||||||
|
|
|
@ -22,7 +22,6 @@ func (b *bot) MsgReceived(msg msg.Message) {
|
||||||
|
|
||||||
// msg := b.buildMessage(client, inMsg)
|
// msg := b.buildMessage(client, inMsg)
|
||||||
// do need to look up user and fix it
|
// do need to look up user and fix it
|
||||||
|
|
||||||
if strings.HasPrefix(msg.Body, "help ") && msg.Command {
|
if strings.HasPrefix(msg.Body, "help ") && msg.Command {
|
||||||
parts := strings.Fields(strings.ToLower(msg.Body))
|
parts := strings.Fields(strings.ToLower(msg.Body))
|
||||||
b.checkHelp(msg.Channel, parts)
|
b.checkHelp(msg.Channel, parts)
|
||||||
|
@ -53,16 +52,40 @@ func (b *bot) EventReceived(msg msg.Message) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) SendMessage(channel, message string) {
|
// Handle incoming replys
|
||||||
b.conn.SendMessage(channel, message)
|
func (b *bot) ReplyMsgReceived(msg msg.Message, identifier string) {
|
||||||
|
log.Println("Received message: ", msg)
|
||||||
|
|
||||||
|
for _, name := range b.pluginOrdering {
|
||||||
|
p := b.plugins[name]
|
||||||
|
if p.ReplyMessage(msg, identifier) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) SendAction(channel, message string) {
|
func (b *bot) SendMessage(channel, message string) string {
|
||||||
b.conn.SendAction(channel, message)
|
return b.conn.SendMessage(channel, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) React(channel, reaction string, message msg.Message) {
|
func (b *bot) SendAction(channel, message string) string {
|
||||||
b.conn.React(channel, reaction, message)
|
return b.conn.SendAction(channel, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
||||||
|
return b.conn.ReplyToMessageIdentifier(channel, message, identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
||||||
|
return b.conn.ReplyToMessage(channel, message, replyTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) React(channel, reaction string, message msg.Message) bool {
|
||||||
|
return b.conn.React(channel, reaction, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bot) Edit(channel, newMessage, identifier string) bool {
|
||||||
|
return b.conn.Edit(channel, newMessage, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bot) GetEmojiList() map[string]string {
|
func (b *bot) GetEmojiList() map[string]string {
|
||||||
|
|
|
@ -15,10 +15,14 @@ type Bot interface {
|
||||||
DB() *sqlx.DB
|
DB() *sqlx.DB
|
||||||
Who(string) []user.User
|
Who(string) []user.User
|
||||||
AddHandler(string, Handler)
|
AddHandler(string, Handler)
|
||||||
SendMessage(string, string)
|
SendMessage(string, string) string
|
||||||
SendAction(string, string)
|
SendAction(string, string) string
|
||||||
React(string, string, msg.Message)
|
ReplyToMessageIdentifier(string, string, string) (string, bool)
|
||||||
|
ReplyToMessage(string, string, msg.Message) (string, bool)
|
||||||
|
React(string, string, msg.Message) bool
|
||||||
|
Edit(string, string, string) bool
|
||||||
MsgReceived(msg.Message)
|
MsgReceived(msg.Message)
|
||||||
|
ReplyMsgReceived(msg.Message, string)
|
||||||
EventReceived(msg.Message)
|
EventReceived(msg.Message)
|
||||||
Filter(msg.Message, string) string
|
Filter(msg.Message, string) string
|
||||||
LastMessage(string) (msg.Message, error)
|
LastMessage(string) (msg.Message, error)
|
||||||
|
@ -30,10 +34,14 @@ type Bot interface {
|
||||||
type Connector interface {
|
type Connector interface {
|
||||||
RegisterEventReceived(func(message msg.Message))
|
RegisterEventReceived(func(message msg.Message))
|
||||||
RegisterMessageReceived(func(message msg.Message))
|
RegisterMessageReceived(func(message msg.Message))
|
||||||
|
RegisterReplyMessageReceived(func(msg.Message, string))
|
||||||
|
|
||||||
SendMessage(channel, message string)
|
SendMessage(channel, message string) string
|
||||||
SendAction(channel, message string)
|
SendAction(channel, message string) string
|
||||||
React(string, string, msg.Message)
|
ReplyToMessageIdentifier(string, string, string) (string, bool)
|
||||||
|
ReplyToMessage(string, string, msg.Message) (string, bool)
|
||||||
|
React(string, string, msg.Message) bool
|
||||||
|
Edit(string, string, string) bool
|
||||||
GetEmojiList() map[string]string
|
GetEmojiList() map[string]string
|
||||||
Serve() error
|
Serve() error
|
||||||
|
|
||||||
|
@ -44,6 +52,7 @@ type Connector interface {
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
Message(message msg.Message) bool
|
Message(message msg.Message) bool
|
||||||
Event(kind string, message msg.Message) bool
|
Event(kind string, message msg.Message) bool
|
||||||
|
ReplyMessage(msg.Message, string) bool
|
||||||
BotMessage(message msg.Message) bool
|
BotMessage(message msg.Message) bool
|
||||||
Help(channel string, parts []string)
|
Help(channel string, parts []string)
|
||||||
RegisterWeb() *string
|
RegisterWeb() *string
|
||||||
|
|
52
bot/mock.go
52
bot/mock.go
|
@ -3,7 +3,10 @@
|
||||||
package bot
|
package bot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
|
@ -25,13 +28,22 @@ type MockBot struct {
|
||||||
func (mb *MockBot) Config() *config.Config { return &mb.Cfg }
|
func (mb *MockBot) Config() *config.Config { return &mb.Cfg }
|
||||||
func (mb *MockBot) DBVersion() int64 { return 1 }
|
func (mb *MockBot) DBVersion() int64 { return 1 }
|
||||||
func (mb *MockBot) DB() *sqlx.DB { return mb.db }
|
func (mb *MockBot) DB() *sqlx.DB { return mb.db }
|
||||||
|
func (mb *MockBot) Conn() Connector { return nil }
|
||||||
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
func (mb *MockBot) Who(string) []user.User { return []user.User{} }
|
||||||
func (mb *MockBot) AddHandler(name string, f Handler) {}
|
func (mb *MockBot) AddHandler(name string, f Handler) {}
|
||||||
func (mb *MockBot) SendMessage(ch string, msg string) {
|
func (mb *MockBot) SendMessage(ch string, msg string) string {
|
||||||
mb.Messages = append(mb.Messages, msg)
|
mb.Messages = append(mb.Messages, msg)
|
||||||
|
return fmt.Sprintf("m-%d", len(mb.Actions)-1)
|
||||||
}
|
}
|
||||||
func (mb *MockBot) SendAction(ch string, msg string) {
|
func (mb *MockBot) SendAction(ch string, msg string) string {
|
||||||
mb.Actions = append(mb.Actions, msg)
|
mb.Actions = append(mb.Actions, msg)
|
||||||
|
return fmt.Sprintf("a-%d", len(mb.Actions)-1)
|
||||||
|
}
|
||||||
|
func (mb *MockBot) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
func (mb *MockBot) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
||||||
|
return "", false
|
||||||
}
|
}
|
||||||
func (mb *MockBot) MsgReceived(msg msg.Message) {}
|
func (mb *MockBot) MsgReceived(msg msg.Message) {}
|
||||||
func (mb *MockBot) EventReceived(msg msg.Message) {}
|
func (mb *MockBot) EventReceived(msg msg.Message) {}
|
||||||
|
@ -39,7 +51,41 @@ func (mb *MockBot) Filter(msg msg.Message, s string) string { return "" }
|
||||||
func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil }
|
func (mb *MockBot) LastMessage(ch string) (msg.Message, error) { return msg.Message{}, nil }
|
||||||
func (mb *MockBot) CheckAdmin(nick string) bool { return false }
|
func (mb *MockBot) CheckAdmin(nick string) bool { return false }
|
||||||
|
|
||||||
func (mb *MockBot) React(channel, reaction string, message msg.Message) {}
|
func (mb *MockBot) React(channel, reaction string, message msg.Message) bool { return false }
|
||||||
|
|
||||||
|
func (mb *MockBot) Edit(channel, newMessage, identifier string) bool {
|
||||||
|
isMessage := identifier[0] == 'm'
|
||||||
|
if !isMessage && identifier[0] != 'a' {
|
||||||
|
log.Printf("failed to parse identifier: %s", identifier)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
index, err := strconv.Atoi(strings.Split(identifier, "-")[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to parse identifier: %s", identifier)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if isMessage {
|
||||||
|
if index < len(mb.Messages) {
|
||||||
|
mb.Messages[index] = newMessage
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if index < len(mb.Actions) {
|
||||||
|
mb.Actions[index] = newMessage
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mb *MockBot) ReplyMsgReceived(msg.Message, string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) }
|
func (mb *MockBot) GetEmojiList() map[string]string { return make(map[string]string) }
|
||||||
func (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
|
func (mb *MockBot) RegisterFilter(s string, f func(string) string) {}
|
||||||
|
|
||||||
|
|
27
irc/irc.go
27
irc/irc.go
|
@ -44,6 +44,7 @@ type Irc struct {
|
||||||
|
|
||||||
eventReceived func(msg.Message)
|
eventReceived func(msg.Message)
|
||||||
messageReceived func(msg.Message)
|
messageReceived func(msg.Message)
|
||||||
|
replyMessageReceived func(msg.Message, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(c *config.Config) *Irc {
|
func New(c *config.Config) *Irc {
|
||||||
|
@ -61,12 +62,16 @@ func (i *Irc) RegisterMessageReceived(f func(msg.Message)) {
|
||||||
i.messageReceived = f
|
i.messageReceived = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Irc) RegisterReplyMessageReceived(f func(msg.Message, string)) {
|
||||||
|
i.replyMessageReceived = f
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Irc) JoinChannel(channel string) {
|
func (i *Irc) JoinChannel(channel string) {
|
||||||
log.Printf("Joining channel: %s", channel)
|
log.Printf("Joining channel: %s", channel)
|
||||||
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
|
i.Client.Out <- irc.Msg{Cmd: irc.JOIN, Args: []string{channel}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) SendMessage(channel, message string) {
|
func (i *Irc) SendMessage(channel, message string) string {
|
||||||
for len(message) > 0 {
|
for len(message) > 0 {
|
||||||
m := irc.Msg{
|
m := irc.Msg{
|
||||||
Cmd: "PRIVMSG",
|
Cmd: "PRIVMSG",
|
||||||
|
@ -90,17 +95,33 @@ func (i *Irc) SendMessage(channel, message string) {
|
||||||
|
|
||||||
i.Client.Out <- m
|
i.Client.Out <- m
|
||||||
}
|
}
|
||||||
|
return "NO_IRC_IDENTIFIERS"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends action to channel
|
// Sends action to channel
|
||||||
func (i *Irc) SendAction(channel, message string) {
|
func (i *Irc) SendAction(channel, message string) string {
|
||||||
message = actionPrefix + " " + message + "\x01"
|
message = actionPrefix + " " + message + "\x01"
|
||||||
|
|
||||||
i.SendMessage(channel, message)
|
i.SendMessage(channel, message)
|
||||||
|
return "NO_IRC_IDENTIFIERS"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) React(channel, reaction string, message msg.Message) {
|
func (i *Irc) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
||||||
|
return "NO_IRC_IDENTIFIERS", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Irc) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
||||||
|
return "NO_IRC_IDENTIFIERS", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Irc) React(channel, reaction string, message msg.Message) bool {
|
||||||
//we're not goign to do anything because it's IRC
|
//we're not goign to do anything because it's IRC
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Irc) Edit(channel, newMessage, identifier string) bool {
|
||||||
|
//we're not goign to do anything because it's IRC
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Irc) GetEmojiList() map[string]string {
|
func (i *Irc) GetEmojiList() map[string]string {
|
||||||
|
|
2
main.go
2
main.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/velour/catbase/plugins/leftpad"
|
"github.com/velour/catbase/plugins/leftpad"
|
||||||
"github.com/velour/catbase/plugins/reaction"
|
"github.com/velour/catbase/plugins/reaction"
|
||||||
"github.com/velour/catbase/plugins/reminder"
|
"github.com/velour/catbase/plugins/reminder"
|
||||||
|
"github.com/velour/catbase/plugins/rpgORdie"
|
||||||
"github.com/velour/catbase/plugins/rss"
|
"github.com/velour/catbase/plugins/rss"
|
||||||
"github.com/velour/catbase/plugins/stats"
|
"github.com/velour/catbase/plugins/stats"
|
||||||
"github.com/velour/catbase/plugins/talker"
|
"github.com/velour/catbase/plugins/talker"
|
||||||
|
@ -69,6 +70,7 @@ func main() {
|
||||||
b.AddHandler("emojifyme", emojifyme.New(b))
|
b.AddHandler("emojifyme", emojifyme.New(b))
|
||||||
b.AddHandler("twitch", twitch.New(b))
|
b.AddHandler("twitch", twitch.New(b))
|
||||||
b.AddHandler("inventory", inventory.New(b))
|
b.AddHandler("inventory", inventory.New(b))
|
||||||
|
b.AddHandler("rpgORdie", rpgORdie.New(b))
|
||||||
// catches anything left, will always return true
|
// catches anything left, will always return true
|
||||||
b.AddHandler("factoid", fact.New(b))
|
b.AddHandler("factoid", fact.New(b))
|
||||||
|
|
||||||
|
|
|
@ -117,3 +117,5 @@ func (p *AdminPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *AdminPlugin) RegisterWeb() *string {
|
func (p *AdminPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *AdminPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -935,3 +935,5 @@ func (p *BabblerPlugin) babbleSeedBookends(babblerName string, start, end []stri
|
||||||
|
|
||||||
return strings.Join(words, " "), nil
|
return strings.Join(words, " "), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *BabblerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -461,3 +461,5 @@ func (p *BeersPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *BeersPlugin) RegisterWeb() *string {
|
func (p *BeersPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *BeersPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -364,3 +364,5 @@ func (p *CounterPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *CounterPlugin) RegisterWeb() *string {
|
func (p *CounterPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CounterPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -100,3 +100,5 @@ func (p *DicePlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *DicePlugin) RegisterWeb() *string {
|
func (p *DicePlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DicePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -231,3 +231,5 @@ func (p *DowntimePlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *DowntimePlugin) RegisterWeb() *string {
|
func (p *DowntimePlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *DowntimePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -112,3 +112,5 @@ func (p *EmojifyMePlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *EmojifyMePlugin) RegisterWeb() *string {
|
func (p *EmojifyMePlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *EmojifyMePlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -764,3 +764,5 @@ func (p *Factoid) serveQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Factoid) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -170,3 +170,5 @@ func (p *RememberPlugin) recordMsg(message msg.Message) {
|
||||||
log.Printf("Logging message: %s: %s", message.User.Name, message.Body)
|
log.Printf("Logging message: %s: %s", message.User.Name, message.Body)
|
||||||
p.Log[message.Channel] = append(p.Log[message.Channel], message)
|
p.Log[message.Channel] = append(p.Log[message.Channel], message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *RememberPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -228,3 +228,5 @@ func (p *FirstPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *FirstPlugin) RegisterWeb() *string {
|
func (p *FirstPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *FirstPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -236,3 +236,5 @@ func (p *InventoryPlugin) RegisterWeb() *string {
|
||||||
// nothing to register
|
// nothing to register
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *InventoryPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -76,3 +76,5 @@ func (p *LeftpadPlugin) RegisterWeb() *string {
|
||||||
// nothing to register
|
// nothing to register
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *LeftpadPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -80,3 +80,5 @@ func (p *ReactionPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *ReactionPlugin) RegisterWeb() *string {
|
func (p *ReactionPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReactionPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -322,3 +322,5 @@ func reminderer(p *ReminderPlugin) {
|
||||||
p.queueUpNextReminder()
|
p.queueUpNextReminder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *ReminderPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
package rpgORdie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/velour/catbase/bot"
|
||||||
|
"github.com/velour/catbase/bot/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DUDE = ":lion_face:"
|
||||||
|
BOULDER = ":full_moon:"
|
||||||
|
HOLE = ":new_moon:"
|
||||||
|
EMPTY = ":white_large_square:"
|
||||||
|
|
||||||
|
OK = iota
|
||||||
|
INVALID = iota
|
||||||
|
WIN = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type RPGPlugin struct {
|
||||||
|
Bot bot.Bot
|
||||||
|
listenFor map[string]*board
|
||||||
|
}
|
||||||
|
|
||||||
|
type board struct {
|
||||||
|
state [][]string
|
||||||
|
x, y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRandomBoard() *board {
|
||||||
|
boardSize := 5
|
||||||
|
b := board{
|
||||||
|
state: make([][]string, boardSize),
|
||||||
|
x: boardSize - 1,
|
||||||
|
y: boardSize - 1,
|
||||||
|
}
|
||||||
|
for i := 0; i < boardSize; i++ {
|
||||||
|
b.state[i] = make([]string, boardSize)
|
||||||
|
for j := 0; j < boardSize; j++ {
|
||||||
|
b.state[i][j] = ":white_large_square:"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.state[boardSize-1][boardSize-1] = DUDE
|
||||||
|
b.state[boardSize/2][boardSize/2] = BOULDER
|
||||||
|
b.state[0][0] = HOLE
|
||||||
|
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *board) toMessageString() string {
|
||||||
|
lines := make([]string, len(b.state))
|
||||||
|
for i := 0; i < len(b.state); i++ {
|
||||||
|
lines[i] = strings.Join(b.state[i], "")
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *board) checkAndMove(dx, dy int) int {
|
||||||
|
newX := b.x + dx
|
||||||
|
newY := b.y + dy
|
||||||
|
|
||||||
|
if newX < 0 || newY < 0 || newX >= len(b.state) || newY >= len(b.state) {
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.state[newY][newX] == HOLE {
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
win := false
|
||||||
|
if b.state[newY][newX] == BOULDER {
|
||||||
|
newBoulderX := newX + dx
|
||||||
|
newBoulderY := newY + dy
|
||||||
|
|
||||||
|
if newBoulderX < 0 || newBoulderY < 0 || newBoulderX >= len(b.state) || newBoulderY >= len(b.state) {
|
||||||
|
return INVALID
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.state[newBoulderY][newBoulderX] != HOLE {
|
||||||
|
b.state[newBoulderY][newBoulderX] = BOULDER
|
||||||
|
} else {
|
||||||
|
win = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.state[newY][newX] = DUDE
|
||||||
|
b.state[b.y][b.x] = EMPTY
|
||||||
|
b.x = newX
|
||||||
|
b.y = newY
|
||||||
|
|
||||||
|
if win {
|
||||||
|
return WIN
|
||||||
|
}
|
||||||
|
return OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(b bot.Bot) *RPGPlugin {
|
||||||
|
return &RPGPlugin{
|
||||||
|
Bot: b,
|
||||||
|
listenFor: map[string]*board{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) Message(message msg.Message) bool {
|
||||||
|
if strings.ToLower(message.Body) == "start rpg" {
|
||||||
|
b := NewRandomBoard()
|
||||||
|
ts := p.Bot.SendMessage(message.Channel, b.toMessageString())
|
||||||
|
p.listenFor[ts] = b
|
||||||
|
p.Bot.ReplyToMessageIdentifier(message.Channel, "Over here.", ts)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) LoadData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) Help(channel string, parts []string) {
|
||||||
|
p.Bot.SendMessage(channel, "Go find a walkthrough or something.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) Event(kind string, message msg.Message) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) BotMessage(message msg.Message) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) RegisterWeb() *string {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *RPGPlugin) ReplyMessage(message msg.Message, identifier string) bool {
|
||||||
|
if strings.ToLower(message.User.Name) != strings.ToLower(p.Bot.Config().Nick) {
|
||||||
|
if b, ok := p.listenFor[identifier]; ok {
|
||||||
|
|
||||||
|
var res int
|
||||||
|
|
||||||
|
if message.Body == "left" {
|
||||||
|
res = b.checkAndMove(-1, 0)
|
||||||
|
} else if message.Body == "right" {
|
||||||
|
res = b.checkAndMove(1, 0)
|
||||||
|
} else if message.Body == "up" {
|
||||||
|
res = b.checkAndMove(0, -1)
|
||||||
|
} else if message.Body == "down" {
|
||||||
|
res = b.checkAndMove(0, 1)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch res {
|
||||||
|
case OK:
|
||||||
|
p.Bot.Edit(message.Channel, b.toMessageString(), identifier)
|
||||||
|
case WIN:
|
||||||
|
p.Bot.Edit(message.Channel, b.toMessageString(), identifier)
|
||||||
|
p.Bot.ReplyToMessageIdentifier(message.Channel, "congratulations, you beat the easiest level imaginable.", identifier)
|
||||||
|
case INVALID:
|
||||||
|
p.Bot.ReplyToMessageIdentifier(message.Channel, fmt.Sprintf("you can't move %s", message.Body), identifier)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
package rpgORdie
|
||||||
|
|
||||||
|
import ()
|
|
@ -117,3 +117,5 @@ func (p *RSSPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *RSSPlugin) RegisterWeb() *string {
|
func (p *RSSPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *RSSPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -275,3 +275,5 @@ func (p *StatsPlugin) mkSightingStat(message msg.Message) stats {
|
||||||
func (p *StatsPlugin) mkChannelStat(message msg.Message) stats {
|
func (p *StatsPlugin) mkChannelStat(message msg.Message) stats {
|
||||||
return stats{stat{mkDay(), "channel", message.Channel, 1}}
|
return stats{stat{mkDay(), "channel", message.Channel, 1}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *StatsPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -119,3 +119,5 @@ func (p *TalkerPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *TalkerPlugin) RegisterWeb() *string {
|
func (p *TalkerPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *TalkerPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -238,3 +238,5 @@ func (p *TwitchPlugin) checkTwitch(channel string, twitcher *Twitcher, alwaysPri
|
||||||
twitcher.game = game
|
twitcher.game = game
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *TwitchPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -66,3 +66,5 @@ func (p *YourPlugin) BotMessage(message msg.Message) bool {
|
||||||
func (p *YourPlugin) RegisterWeb() *string {
|
func (p *YourPlugin) RegisterWeb() *string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *YourPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
|
@ -122,3 +122,5 @@ func (p *ZorkPlugin) Help(ch string, _ []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ZorkPlugin) RegisterWeb() *string { return nil }
|
func (p *ZorkPlugin) RegisterWeb() *string { return nil }
|
||||||
|
|
||||||
|
func (p *ZorkPlugin) ReplyMessage(message msg.Message, identifier string) bool { return false }
|
||||||
|
|
194
slack/slack.go
194
slack/slack.go
|
@ -5,6 +5,7 @@ package slack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
// "sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/velour/catbase/bot"
|
"github.com/velour/catbase/bot"
|
||||||
|
@ -40,6 +41,7 @@ type Slack struct {
|
||||||
|
|
||||||
eventReceived func(msg.Message)
|
eventReceived func(msg.Message)
|
||||||
messageReceived func(msg.Message)
|
messageReceived func(msg.Message)
|
||||||
|
replyMessageReceived func(msg.Message, string)
|
||||||
}
|
}
|
||||||
|
|
||||||
var idCounter uint64
|
var idCounter uint64
|
||||||
|
@ -133,6 +135,7 @@ type slackMessage struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Ts string `json:"ts"`
|
Ts string `json:"ts"`
|
||||||
|
ThreadTs string `json:"thread_ts"`
|
||||||
Error struct {
|
Error struct {
|
||||||
Code uint64 `json:"code"`
|
Code uint64 `json:"code"`
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
|
@ -163,6 +166,27 @@ func New(c *config.Config) *Slack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkReturnStatus(response *http.Response) bool {
|
||||||
|
type Response struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
|
response.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading Slack API body: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp Response
|
||||||
|
err = json.Unmarshal(body, &resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing message response: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return resp.OK
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Slack) RegisterEventReceived(f func(msg.Message)) {
|
func (s *Slack) RegisterEventReceived(f func(msg.Message)) {
|
||||||
s.eventReceived = f
|
s.eventReceived = f
|
||||||
}
|
}
|
||||||
|
@ -171,32 +195,112 @@ func (s *Slack) RegisterMessageReceived(f func(msg.Message)) {
|
||||||
s.messageReceived = f
|
s.messageReceived = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) SendMessageType(channel, messageType, subType, message string) error {
|
func (s *Slack) RegisterReplyMessageReceived(f func(msg.Message, string)) {
|
||||||
m := slackMessage{
|
s.replyMessageReceived = f
|
||||||
ID: atomic.AddUint64(&idCounter, 1),
|
|
||||||
Type: messageType,
|
|
||||||
SubType: subType,
|
|
||||||
Channel: channel,
|
|
||||||
Text: message,
|
|
||||||
}
|
}
|
||||||
err := websocket.JSON.Send(s.ws, m)
|
|
||||||
|
func (s *Slack) SendMessageType(channel, message string, meMessage bool) (string, error) {
|
||||||
|
postUrl := "https://slack.com/api/chat.postMessage"
|
||||||
|
if meMessage {
|
||||||
|
postUrl = "https://slack.com/api/chat.meMessage"
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.PostForm(postUrl,
|
||||||
|
url.Values{"token": {s.config.Slack.Token},
|
||||||
|
"channel": {channel},
|
||||||
|
"text": {message},
|
||||||
|
"as_user": {"true"},
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending Slack message: %s", err)
|
log.Printf("Error sending Slack message: %s", err)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error reading Slack API body: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) SendMessage(channel, message string) {
|
log.Println(string(body))
|
||||||
|
|
||||||
|
type MessageResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var mr MessageResponse
|
||||||
|
err = json.Unmarshal(body, &mr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error parsing message response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mr.OK {
|
||||||
|
return "", errors.New("failure response received")
|
||||||
|
}
|
||||||
|
|
||||||
|
return mr.Timestamp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) SendMessage(channel, message string) string {
|
||||||
log.Printf("Sending message to %s: %s", channel, message)
|
log.Printf("Sending message to %s: %s", channel, message)
|
||||||
s.SendMessageType(channel, "message", "", message)
|
identifier, _ := s.SendMessageType(channel, message, false)
|
||||||
|
return identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) SendAction(channel, message string) {
|
func (s *Slack) SendAction(channel, message string) string {
|
||||||
log.Printf("Sending action to %s: %s", channel, message)
|
log.Printf("Sending action to %s: %s", channel, message)
|
||||||
s.SendMessageType(channel, "message", "me_message", "_"+message+"_")
|
identifier, _ := s.SendMessageType(channel, "_"+message+"_", true)
|
||||||
|
return identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) React(channel, reaction string, message msg.Message) {
|
func (s *Slack) ReplyToMessageIdentifier(channel, message, identifier string) (string, bool) {
|
||||||
|
resp, err := http.PostForm("https://slack.com/api/chat.postMessage",
|
||||||
|
url.Values{"token": {s.config.Slack.Token},
|
||||||
|
"channel": {channel},
|
||||||
|
"text": {message},
|
||||||
|
"as_user": {"true"},
|
||||||
|
"thread_ts": {identifier},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error sending Slack reply: %s", err)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error reading Slack API body: %s", err)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(string(body))
|
||||||
|
|
||||||
|
type MessageResponse struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Timestamp string `json:"ts"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var mr MessageResponse
|
||||||
|
err = json.Unmarshal(body, &mr)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error parsing message response: %s", err)
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mr.OK {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return mr.Timestamp, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) ReplyToMessage(channel, message string, replyTo msg.Message) (string, bool) {
|
||||||
|
return s.ReplyToMessageIdentifier(channel, message, replyTo.AdditionalData["RAW_SLACK_TIMESTAMP"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) React(channel, reaction string, message msg.Message) bool {
|
||||||
log.Printf("Reacting in %s: %s", channel, reaction)
|
log.Printf("Reacting in %s: %s", channel, reaction)
|
||||||
resp, err := http.PostForm("https://slack.com/api/reactions.add",
|
resp, err := http.PostForm("https://slack.com/api/reactions.add",
|
||||||
url.Values{"token": {s.config.Slack.Token},
|
url.Values{"token": {s.config.Slack.Token},
|
||||||
|
@ -204,9 +308,24 @@ func (s *Slack) React(channel, reaction string, message msg.Message) {
|
||||||
"channel": {channel},
|
"channel": {channel},
|
||||||
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
|
"timestamp": {message.AdditionalData["RAW_SLACK_TIMESTAMP"]}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error sending Slack reaction: %s", err)
|
log.Println("reaction failed: %s", err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
log.Print(resp)
|
return checkReturnStatus(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Slack) Edit(channel, newMessage, identifier string) bool {
|
||||||
|
log.Printf("Editing in (%s) %s: %s", identifier, channel, newMessage)
|
||||||
|
resp, err := http.PostForm("https://slack.com/api/chat.update",
|
||||||
|
url.Values{"token": {s.config.Slack.Token},
|
||||||
|
"channel": {channel},
|
||||||
|
"text": {newMessage},
|
||||||
|
"ts": {identifier}})
|
||||||
|
if err != nil {
|
||||||
|
log.Println("edit failed: %s", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return checkReturnStatus(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Slack) GetEmojiList() map[string]string {
|
func (s *Slack) GetEmojiList() map[string]string {
|
||||||
|
@ -273,14 +392,17 @@ func (s *Slack) Serve() error {
|
||||||
}
|
}
|
||||||
switch msg.Type {
|
switch msg.Type {
|
||||||
case "message":
|
case "message":
|
||||||
if !msg.Hidden {
|
if !msg.Hidden && msg.ThreadTs == "" {
|
||||||
m := s.buildMessage(msg)
|
m := s.buildMessage(msg)
|
||||||
if m.Time.Before(s.lastRecieved) {
|
if m.Time.Before(s.lastRecieved) {
|
||||||
log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time)
|
log.Printf("Ignoring message: %+v\nlastRecieved: %v msg: %v", msg.ID, s.lastRecieved, m.Time)
|
||||||
} else {
|
} else {
|
||||||
s.lastRecieved = m.Time
|
s.lastRecieved = m.Time
|
||||||
s.messageReceived(s.buildMessage(msg))
|
s.messageReceived(m)
|
||||||
}
|
}
|
||||||
|
} else if msg.ThreadTs != "" {
|
||||||
|
//we're throwing away some information here by not parsing the correct reply object type, but that's okay
|
||||||
|
s.replyMessageReceived(s.buildLightReplyMessage(msg), msg.ThreadTs)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID)
|
log.Printf("THAT MESSAGE WAS HIDDEN: %+v", msg.ID)
|
||||||
}
|
}
|
||||||
|
@ -337,6 +459,40 @@ func (s *Slack) buildMessage(m slackMessage) msg.Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Slack) buildLightReplyMessage(m slackMessage) msg.Message {
|
||||||
|
text := html.UnescapeString(m.Text)
|
||||||
|
|
||||||
|
text = fixText(s.getUser, text)
|
||||||
|
|
||||||
|
isCmd, text := bot.IsCmd(s.config, text)
|
||||||
|
|
||||||
|
isAction := m.SubType == "me_message"
|
||||||
|
|
||||||
|
u, _ := s.getUser(m.User)
|
||||||
|
if m.Username != "" {
|
||||||
|
u = m.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
tstamp := slackTStoTime(m.Ts)
|
||||||
|
|
||||||
|
return msg.Message{
|
||||||
|
User: &user.User{
|
||||||
|
ID: m.User,
|
||||||
|
Name: u,
|
||||||
|
},
|
||||||
|
Body: text,
|
||||||
|
Raw: m.Text,
|
||||||
|
Channel: m.Channel,
|
||||||
|
Command: isCmd,
|
||||||
|
Action: isAction,
|
||||||
|
Host: string(m.ID),
|
||||||
|
Time: tstamp,
|
||||||
|
AdditionalData: map[string]string{
|
||||||
|
"RAW_SLACK_TIMESTAMP": m.Ts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// markAllChannelsRead gets a list of all channels and marks each as read
|
// markAllChannelsRead gets a list of all channels and marks each as read
|
||||||
func (s *Slack) markAllChannelsRead() {
|
func (s *Slack) markAllChannelsRead() {
|
||||||
chs := s.getAllChannels()
|
chs := s.getAllChannels()
|
||||||
|
|
Loading…
Reference in New Issue