1
0
forked from lug/matterbridge

Compare commits

..

14 Commits

Author SHA1 Message Date
Wim
e9105003b0 Release v0.9.2 2017-01-28 22:15:32 +01:00
Wim
587bb06558 Update changelog 2017-01-28 00:39:33 +01:00
Wim
53e9664cde Add support for private channels (slack). Closes #118 2017-01-28 00:36:53 +01:00
Wim
482fbac68f Update vendor (slack) 2017-01-28 00:36:22 +01:00
Wim
dcccd43427 Use unknown as username if unsigned channel (telegram) 2017-01-27 23:59:24 +01:00
Wim
397b8ff892 Update changelog 2017-01-27 23:40:20 +01:00
Wim
38a4cf315a Add telegram links about channel and tokens 2017-01-27 23:36:14 +01:00
Wim
5f8b24e32c Fix username (telegram) 2017-01-27 23:30:46 +01:00
Wim
678a7ceb4e Fix channel and group messages (telegram) 2017-01-27 23:26:06 +01:00
Wim
077d494c7b Update vendor (telegram) 2017-01-27 00:23:14 +01:00
Wim
09b243d8c2 Update vendor (irc) 2017-01-24 21:24:57 +01:00
Wim
991183e514 Fix IgnoreNicks (global). Closes #115 2017-01-21 21:00:40 +01:00
Josip Janžić
9bf10e4b58 Fix tls by setting ServerName (xmpp) (#114)
Fixes error message shown by tls: "either ServerName or InsecureSkipVerify must be specified in the tls.Config"
2017-01-18 21:01:42 +01:00
Wim
884599d27d Bump version 2017-01-18 20:06:52 +01:00
24 changed files with 543 additions and 147 deletions

View File

@@ -36,12 +36,12 @@ docker run -ti -v /tmp/matterbridge.toml:/matterbridge.toml 42wim/matterbridge
## binaries ## binaries
Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/) Binaries can be found [here] (https://github.com/42wim/matterbridge/releases/)
* For use with mattermost 3.5.x - 3.6.0 [v0.9.1](https://github.com/42wim/matterircd/releases/tag/v0.9.1) * For use with mattermost 3.5.x - 3.6.0 [v0.9.2](https://github.com/42wim/matterircd/releases/tag/v0.9.2)
* For use with mattermost 3.3.0 - 3.4.0 [v0.7.1](https://github.com/42wim/matterircd/releases/tag/v0.7.1) * For use with mattermost 3.3.0 - 3.4.0 [v0.7.1](https://github.com/42wim/matterircd/releases/tag/v0.7.1)
## Compatibility ## Compatibility
### Mattermost ### Mattermost
* Matterbridge v0.9.1 works with mattermost 3.5.x - 3.6.0 [3.6.0 release](https://github.com/mattermost/platform/releases/tag/v3.6.0) * Matterbridge v0.9.2 works with mattermost 3.5.x - 3.6.0 [3.6.0 release](https://github.com/mattermost/platform/releases/tag/v3.6.0)
* Matterbridge v0.7.1 works with mattermost 3.3.0 - 3.4.0 [3.4.0 release](https://github.com/mattermost/platform/releases/tag/v3.4.0) * Matterbridge v0.7.1 works with mattermost 3.3.0 - 3.4.0 [3.4.0 release](https://github.com/mattermost/platform/releases/tag/v3.4.0)
#### Webhooks version #### Webhooks version

View File

@@ -204,6 +204,14 @@ func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
b.channels = ev.Info.Channels b.channels = ev.Info.Channels
b.si = ev.Info b.si = ev.Info
b.Users, _ = b.sc.GetUsers() b.Users, _ = b.sc.GetUsers()
// add private channels
groups, _ := b.sc.GetGroups(true)
for _, g := range groups {
channel := new(slack.Channel)
channel.ID = g.ID
channel.Name = g.Name
b.channels = append(b.channels, *channel)
}
case *slack.InvalidAuthEvent: case *slack.InvalidAuthEvent:
flog.Fatalf("Invalid Token %#v", ev) flog.Fatalf("Invalid Token %#v", ev)
default: default:

View File

@@ -117,18 +117,45 @@ func (b *Btelegram) Send(msg config.Message) error {
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK| blackfriday.EXTENSION_BACKSLASH_LINE_BREAK|
blackfriday.EXTENSION_DEFINITION_LISTS) blackfriday.EXTENSION_DEFINITION_LISTS)
m := tgbotapi.NewMessage(chatid, msg.Username+string(parsed)) m := tgbotapi.NewMessage(chatid, html.EscapeString(msg.Username)+string(parsed))
m.ParseMode = "HTML" m.ParseMode = "HTML"
_, err = b.c.Send(m) _, err = b.c.Send(m)
return err return err
} }
func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) { func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
username := ""
text := ""
channel := ""
for update := range updates { for update := range updates {
if update.Message == nil { // handle channels
continue if update.ChannelPost != nil {
if update.ChannelPost.From != nil {
username = update.ChannelPost.From.FirstName
if username == "" {
username = update.ChannelPost.From.UserName
}
}
text = update.ChannelPost.Text
channel = strconv.FormatInt(update.ChannelPost.Chat.ID, 10)
}
// handle groups
if update.Message != nil {
if update.Message.From != nil {
username = update.Message.From.FirstName
if username == "" {
username = update.Message.From.UserName
}
}
text = update.Message.Text
channel = strconv.FormatInt(update.Message.Chat.ID, 10)
}
if username == "" {
username = "unknown"
}
if text != "" {
flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
b.Remote <- config.Message{Username: username, Text: text, Channel: channel, Account: b.Account}
} }
flog.Debugf("Sending message from %s on %s to gateway", update.Message.From.UserName, b.Account)
b.Remote <- config.Message{Username: update.Message.From.UserName, Text: update.Message.Text, Channel: strconv.FormatInt(update.Message.Chat.ID, 10), Account: b.Account}
} }
} }

View File

@@ -61,6 +61,7 @@ func (b *Bxmpp) Send(msg config.Message) error {
func (b *Bxmpp) createXMPP() (*xmpp.Client, error) { func (b *Bxmpp) createXMPP() (*xmpp.Client, error) {
tc := new(tls.Config) tc := new(tls.Config)
tc.InsecureSkipVerify = b.Config.SkipTLSVerify tc.InsecureSkipVerify = b.Config.SkipTLSVerify
tc.ServerName = strings.Split(b.Config.Server, ":")[0]
options := xmpp.Options{ options := xmpp.Options{
Host: b.Config.Server, Host: b.Config.Server,
User: b.Config.Jid, User: b.Config.Jid,

View File

@@ -1,3 +1,15 @@
# v0.9.2
## Bugfix
* general: make ignorenicks work again #115
* telegram: fix receiving from channels and groups #112
* telegram: use html for username
* telegram: use ```unknown``` as username when username is not visible.
* irc: update vendor (fixes some crashes) #117
* xmpp: fix tls by setting ServerName #114
## New features
* slack: support private channels #118
# v0.9.1 # v0.9.1
## New features ## New features
* Rocket.Chat: New protocol support added (https://rocket.chat) * Rocket.Chat: New protocol support added (https://rocket.chat)

View File

@@ -16,7 +16,6 @@ type Gateway struct {
Bridges map[string]*bridge.Bridge Bridges map[string]*bridge.Bridge
ChannelsOut map[string][]string ChannelsOut map[string][]string
ChannelsIn map[string][]string ChannelsIn map[string][]string
ignoreNicks map[string][]string
ChannelOptions map[string]config.ChannelOptions ChannelOptions map[string]config.ChannelOptions
Name string Name string
Message chan config.Message Message chan config.Message
@@ -69,8 +68,6 @@ func (gw *Gateway) Start() error {
return err return err
} }
} }
//TODO fix mapIgnores
//gw.mapIgnores()
go gw.handleReceive() go gw.handleReceive()
return nil return nil
} }
@@ -79,8 +76,10 @@ func (gw *Gateway) handleReceive() {
for { for {
select { select {
case msg := <-gw.Message: case msg := <-gw.Message:
for _, br := range gw.Bridges { if !gw.ignoreMessage(&msg) {
gw.handleMessage(msg, br) for _, br := range gw.Bridges {
gw.handleMessage(msg, br)
}
} }
} }
} }
@@ -110,15 +109,6 @@ func (gw *Gateway) mapChannels() error {
return nil return nil
} }
func (gw *Gateway) mapIgnores() {
m := make(map[string][]string)
for _, br := range gw.MyConfig.In {
accInfo := strings.Split(br.Account, ".")
m[br.Account] = strings.Fields(gw.Config.IRC[accInfo[1]].IgnoreNicks)
}
gw.ignoreNicks = m
}
func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string { func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string {
channels := gw.ChannelsIn[msg.Account] channels := gw.ChannelsIn[msg.Account]
// broadcast to every out channel (irc QUIT) // broadcast to every out channel (irc QUIT)
@@ -134,9 +124,6 @@ func (gw *Gateway) getDestChannel(msg *config.Message, dest string) []string {
} }
func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) { func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
if gw.ignoreMessage(&msg) {
return
}
// only relay join/part when configged // only relay join/part when configged
if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart { if msg.Event == config.EVENT_JOIN_LEAVE && !gw.Bridges[dest.Account].Config.ShowJoinPart {
return return
@@ -163,9 +150,9 @@ func (gw *Gateway) handleMessage(msg config.Message, dest *bridge.Bridge) {
} }
func (gw *Gateway) ignoreMessage(msg *config.Message) bool { func (gw *Gateway) ignoreMessage(msg *config.Message) bool {
// should we discard messages ? for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) {
for _, entry := range gw.ignoreNicks[msg.Account] {
if msg.Username == entry { if msg.Username == entry {
log.Debugf("ignoring %s from %s", msg.Username, msg.Account)
return true return true
} }
} }

View File

@@ -47,8 +47,10 @@ func (gw *SameChannelGateway) handleReceive(c chan config.Message) {
for { for {
select { select {
case msg := <-c: case msg := <-c:
for _, br := range gw.Bridges { if !gw.ignoreMessage(&msg) {
gw.handleMessage(msg, br) for _, br := range gw.Bridges {
gw.handleMessage(msg, br)
}
} }
} }
} }
@@ -71,6 +73,16 @@ func (gw *SameChannelGateway) handleMessage(msg config.Message, dest *bridge.Bri
} }
} }
func (gw *SameChannelGateway) ignoreMessage(msg *config.Message) bool {
for _, entry := range strings.Fields(gw.Bridges[msg.Account].Config.IgnoreNicks) {
if msg.Username == entry {
log.Debugf("ignoring %s from %s", msg.Username, msg.Account)
return true
}
}
return false
}
func (gw *SameChannelGateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) { func (gw *SameChannelGateway) modifyUsername(msg *config.Message, dest *bridge.Bridge) {
br := gw.Bridges[msg.Account] br := gw.Bridges[msg.Account]
nick := gw.Config.General.RemoteNickFormat nick := gw.Config.General.RemoteNickFormat

View File

@@ -9,7 +9,7 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
) )
var version = "0.9.1" var version = "0.9.2"
func init() { func init() {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true}) log.SetFormatter(&log.TextFormatter{FullTimestamp: true})

View File

@@ -405,6 +405,7 @@ ShowJoinPart=false
#REQUIRED #REQUIRED
[telegram.secure] [telegram.secure]
#Token to connect with telegram API #Token to connect with telegram API
#See https://core.telegram.org/bots#6-botfather and https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau
#REQUIRED #REQUIRED
Token="Yourtokenhere" Token="Yourtokenhere"
@@ -534,6 +535,7 @@ enable=true
# - ID:123456789 (where 123456789 is the channel ID) # - ID:123456789 (where 123456789 is the channel ID)
# (https://github.com/42wim/matterbridge/issues/57) # (https://github.com/42wim/matterbridge/issues/57)
#telegram - chatid (a large negative number, eg -123456789) #telegram - chatid (a large negative number, eg -123456789)
# see (https://www.linkedin.com/pulse/telegram-bots-beginners-marco-frau)
#hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel) #hipchat - id_channel (see https://www.hipchat.com/account/xmpp for the correct channel)
#rocketchat - #channel (# is required) #rocketchat - #channel (# is required)
#REQUIRED #REQUIRED

View File

@@ -7,7 +7,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/technoweenie/multipartstreamer"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
@@ -16,6 +15,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/technoweenie/multipartstreamer"
) )
// BotAPI allows you to interact with the Telegram Bot API. // BotAPI allows you to interact with the Telegram Bot API.
@@ -80,7 +81,7 @@ func (bot *BotAPI) MakeRequest(endpoint string, params url.Values) (APIResponse,
json.Unmarshal(bytes, &apiResp) json.Unmarshal(bytes, &apiResp)
if !apiResp.Ok { if !apiResp.Ok {
return APIResponse{}, errors.New(apiResp.Description) return apiResp, errors.New(apiResp.Description)
} }
return apiResp, nil return apiResp, nil
@@ -105,16 +106,17 @@ func (bot *BotAPI) makeMessageRequest(endpoint string, params url.Values) (Messa
// //
// Requires the parameter to hold the file not be in the params. // Requires the parameter to hold the file not be in the params.
// File should be a string to a file path, a FileBytes struct, // File should be a string to a file path, a FileBytes struct,
// or a FileReader struct. // a FileReader struct, or a url.URL.
// //
// Note that if your FileReader has a size set to -1, it will read // Note that if your FileReader has a size set to -1, it will read
// the file into memory to calculate a size. // the file into memory to calculate a size.
func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) { func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldname string, file interface{}) (APIResponse, error) {
ms := multipartstreamer.New() ms := multipartstreamer.New()
ms.WriteFields(params)
switch f := file.(type) { switch f := file.(type) {
case string: case string:
ms.WriteFields(params)
fileHandle, err := os.Open(f) fileHandle, err := os.Open(f)
if err != nil { if err != nil {
return APIResponse{}, err return APIResponse{}, err
@@ -128,9 +130,13 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle) ms.WriteReader(fieldname, fileHandle.Name(), fi.Size(), fileHandle)
case FileBytes: case FileBytes:
ms.WriteFields(params)
buf := bytes.NewBuffer(f.Bytes) buf := bytes.NewBuffer(f.Bytes)
ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf) ms.WriteReader(fieldname, f.Name, int64(len(f.Bytes)), buf)
case FileReader: case FileReader:
ms.WriteFields(params)
if f.Size != -1 { if f.Size != -1 {
ms.WriteReader(fieldname, f.Name, f.Size, f.Reader) ms.WriteReader(fieldname, f.Name, f.Size, f.Reader)
@@ -145,6 +151,10 @@ func (bot *BotAPI) UploadFile(endpoint string, params map[string]string, fieldna
buf := bytes.NewBuffer(data) buf := bytes.NewBuffer(data)
ms.WriteReader(fieldname, f.Name, int64(len(data)), buf) ms.WriteReader(fieldname, f.Name, int64(len(data)), buf)
case url.URL:
params[fieldname] = f.String()
ms.WriteFields(params)
default: default:
return APIResponse{}, errors.New(ErrBadFileType) return APIResponse{}, errors.New(ErrBadFileType)
} }
@@ -399,15 +409,22 @@ func (bot *BotAPI) RemoveWebhook() (APIResponse, error) {
// If you do not have a legitimate TLS certificate, you need to include // If you do not have a legitimate TLS certificate, you need to include
// your self signed certificate with the config. // your self signed certificate with the config.
func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) { func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
if config.Certificate == nil { if config.Certificate == nil {
v := url.Values{} v := url.Values{}
v.Add("url", config.URL.String()) v.Add("url", config.URL.String())
if config.MaxConnections != 0 {
v.Add("max_connections", strconv.Itoa(config.MaxConnections))
}
return bot.MakeRequest("setWebhook", v) return bot.MakeRequest("setWebhook", v)
} }
params := make(map[string]string) params := make(map[string]string)
params["url"] = config.URL.String() params["url"] = config.URL.String()
if config.MaxConnections != 0 {
params["max_connections"] = strconv.Itoa(config.MaxConnections)
}
resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate) resp, err := bot.UploadFile("setWebhook", params, "certificate", config.Certificate)
if err != nil { if err != nil {
@@ -424,9 +441,23 @@ func (bot *BotAPI) SetWebhook(config WebhookConfig) (APIResponse, error) {
return apiResp, nil return apiResp, nil
} }
// GetWebhookInfo allows you to fetch information about a webhook and if
// one currently is set, along with pending update count and error messages.
func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
resp, err := bot.MakeRequest("getWebhookInfo", url.Values{})
if err != nil {
return WebhookInfo{}, err
}
var info WebhookInfo
err = json.Unmarshal(resp.Result, &info)
return info, err
}
// GetUpdatesChan starts and returns a channel for getting updates. // GetUpdatesChan starts and returns a channel for getting updates.
func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) { func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
updatesChan := make(chan Update, 100) ch := make(chan Update, 100)
go func() { go func() {
for { for {
@@ -442,18 +473,18 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (<-chan Update, error) {
for _, update := range updates { for _, update := range updates {
if update.UpdateID >= config.Offset { if update.UpdateID >= config.Offset {
config.Offset = update.UpdateID + 1 config.Offset = update.UpdateID + 1
updatesChan <- update ch <- update
} }
} }
} }
}() }()
return updatesChan, nil return ch, nil
} }
// ListenForWebhook registers a http handler for a webhook. // ListenForWebhook registers a http handler for a webhook.
func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update { func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
updatesChan := make(chan Update, 100) ch := make(chan Update, 100)
http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
bytes, _ := ioutil.ReadAll(r.Body) bytes, _ := ioutil.ReadAll(r.Body)
@@ -461,10 +492,10 @@ func (bot *BotAPI) ListenForWebhook(pattern string) <-chan Update {
var update Update var update Update
json.Unmarshal(bytes, &update) json.Unmarshal(bytes, &update)
updatesChan <- update ch <- update
}) })
return updatesChan return ch
} }
// AnswerInlineQuery sends a response to an inline query. // AnswerInlineQuery sends a response to an inline query.
@@ -495,8 +526,14 @@ func (bot *BotAPI) AnswerCallbackQuery(config CallbackConfig) (APIResponse, erro
v := url.Values{} v := url.Values{}
v.Add("callback_query_id", config.CallbackQueryID) v.Add("callback_query_id", config.CallbackQueryID)
v.Add("text", config.Text) if config.Text != "" {
v.Add("text", config.Text)
}
v.Add("show_alert", strconv.FormatBool(config.ShowAlert)) v.Add("show_alert", strconv.FormatBool(config.ShowAlert))
if config.URL != "" {
v.Add("url", config.URL)
}
v.Add("cache_time", strconv.Itoa(config.CacheTime))
bot.debugLog("answerCallbackQuery", v, nil) bot.debugLog("answerCallbackQuery", v, nil)
@@ -648,3 +685,18 @@ func (bot *BotAPI) UnbanChatMember(config ChatMemberConfig) (APIResponse, error)
return bot.MakeRequest("unbanChatMember", v) return bot.MakeRequest("unbanChatMember", v)
} }
// GetGameHighScores allows you to get the high scores for a game.
func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
v, _ := config.values()
resp, err := bot.MakeRequest(config.method(), v)
if err != nil {
return []GameHighScore{}, err
}
var highScores []GameHighScore
err = json.Unmarshal(resp.Result, &highScores)
return highScores, err
}

View File

@@ -198,7 +198,10 @@ type MessageConfig struct {
// values returns a url.Values representation of MessageConfig. // values returns a url.Values representation of MessageConfig.
func (config MessageConfig) values() (url.Values, error) { func (config MessageConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("text", config.Text) v.Add("text", config.Text)
v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview)) v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
if config.ParseMode != "" { if config.ParseMode != "" {
@@ -223,7 +226,10 @@ type ForwardConfig struct {
// values returns a url.Values representation of ForwardConfig. // values returns a url.Values representation of ForwardConfig.
func (config ForwardConfig) values() (url.Values, error) { func (config ForwardConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10)) v.Add("from_chat_id", strconv.FormatInt(config.FromChatID, 10))
v.Add("message_id", strconv.Itoa(config.MessageID)) v.Add("message_id", strconv.Itoa(config.MessageID))
return v, nil return v, nil
@@ -253,7 +259,10 @@ func (config PhotoConfig) params() (map[string]string, error) {
// Values returns a url.Values representation of PhotoConfig. // Values returns a url.Values representation of PhotoConfig.
func (config PhotoConfig) values() (url.Values, error) { func (config PhotoConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
if config.Caption != "" { if config.Caption != "" {
@@ -275,6 +284,7 @@ func (config PhotoConfig) method() string {
// AudioConfig contains information about a SendAudio request. // AudioConfig contains information about a SendAudio request.
type AudioConfig struct { type AudioConfig struct {
BaseFile BaseFile
Caption string
Duration int Duration int
Performer string Performer string
Title string Title string
@@ -282,7 +292,10 @@ type AudioConfig struct {
// values returns a url.Values representation of AudioConfig. // values returns a url.Values representation of AudioConfig.
func (config AudioConfig) values() (url.Values, error) { func (config AudioConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
if config.Duration != 0 { if config.Duration != 0 {
@@ -295,6 +308,9 @@ func (config AudioConfig) values() (url.Values, error) {
if config.Title != "" { if config.Title != "" {
v.Add("title", config.Title) v.Add("title", config.Title)
} }
if config.Caption != "" {
v.Add("caption", config.Caption)
}
return v, nil return v, nil
} }
@@ -313,6 +329,9 @@ func (config AudioConfig) params() (map[string]string, error) {
if config.Title != "" { if config.Title != "" {
params["title"] = config.Title params["title"] = config.Title
} }
if config.Caption != "" {
params["caption"] = config.Caption
}
return params, nil return params, nil
} }
@@ -334,7 +353,10 @@ type DocumentConfig struct {
// values returns a url.Values representation of DocumentConfig. // values returns a url.Values representation of DocumentConfig.
func (config DocumentConfig) values() (url.Values, error) { func (config DocumentConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
@@ -365,7 +387,10 @@ type StickerConfig struct {
// values returns a url.Values representation of StickerConfig. // values returns a url.Values representation of StickerConfig.
func (config StickerConfig) values() (url.Values, error) { func (config StickerConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
@@ -398,7 +423,10 @@ type VideoConfig struct {
// values returns a url.Values representation of VideoConfig. // values returns a url.Values representation of VideoConfig.
func (config VideoConfig) values() (url.Values, error) { func (config VideoConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
if config.Duration != 0 { if config.Duration != 0 {
@@ -431,12 +459,16 @@ func (config VideoConfig) method() string {
// VoiceConfig contains information about a SendVoice request. // VoiceConfig contains information about a SendVoice request.
type VoiceConfig struct { type VoiceConfig struct {
BaseFile BaseFile
Caption string
Duration int Duration int
} }
// values returns a url.Values representation of VoiceConfig. // values returns a url.Values representation of VoiceConfig.
func (config VoiceConfig) values() (url.Values, error) { func (config VoiceConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add(config.name(), config.FileID) v.Add(config.name(), config.FileID)
if config.Duration != 0 { if config.Duration != 0 {
@@ -476,7 +508,10 @@ type LocationConfig struct {
// values returns a url.Values representation of LocationConfig. // values returns a url.Values representation of LocationConfig.
func (config LocationConfig) values() (url.Values, error) { func (config LocationConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
@@ -500,7 +535,10 @@ type VenueConfig struct {
} }
func (config VenueConfig) values() (url.Values, error) { func (config VenueConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64)) v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64)) v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))
@@ -526,7 +564,10 @@ type ContactConfig struct {
} }
func (config ContactConfig) values() (url.Values, error) { func (config ContactConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("phone_number", config.PhoneNumber) v.Add("phone_number", config.PhoneNumber)
v.Add("first_name", config.FirstName) v.Add("first_name", config.FirstName)
@@ -539,6 +580,94 @@ func (config ContactConfig) method() string {
return "sendContact" return "sendContact"
} }
// GameConfig allows you to send a game.
type GameConfig struct {
BaseChat
GameShortName string
}
func (config GameConfig) values() (url.Values, error) {
v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("game_short_name", config.GameShortName)
return v, nil
}
func (config GameConfig) method() string {
return "sendGame"
}
// SetGameScoreConfig allows you to update the game score in a chat.
type SetGameScoreConfig struct {
UserID int
Score int
Force bool
DisableEditMessage bool
ChatID int
ChannelUsername string
MessageID int
InlineMessageID string
}
func (config SetGameScoreConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("user_id", strconv.Itoa(config.UserID))
v.Add("score", strconv.Itoa(config.Score))
if config.InlineMessageID == "" {
if config.ChannelUsername == "" {
v.Add("chat_id", strconv.Itoa(config.ChatID))
} else {
v.Add("chat_id", config.ChannelUsername)
}
v.Add("message_id", strconv.Itoa(config.MessageID))
} else {
v.Add("inline_message_id", config.InlineMessageID)
}
v.Add("disable_edit_message", strconv.FormatBool(config.DisableEditMessage))
return v, nil
}
func (config SetGameScoreConfig) method() string {
return "setGameScore"
}
// GetGameHighScoresConfig allows you to fetch the high scores for a game.
type GetGameHighScoresConfig struct {
UserID int
ChatID int
ChannelUsername string
MessageID int
InlineMessageID string
}
func (config GetGameHighScoresConfig) values() (url.Values, error) {
v := url.Values{}
v.Add("user_id", strconv.Itoa(config.UserID))
if config.InlineMessageID == "" {
if config.ChannelUsername == "" {
v.Add("chat_id", strconv.Itoa(config.ChatID))
} else {
v.Add("chat_id", config.ChannelUsername)
}
v.Add("message_id", strconv.Itoa(config.MessageID))
} else {
v.Add("inline_message_id", config.InlineMessageID)
}
return v, nil
}
func (config GetGameHighScoresConfig) method() string {
return "getGameHighScores"
}
// ChatActionConfig contains information about a SendChatAction request. // ChatActionConfig contains information about a SendChatAction request.
type ChatActionConfig struct { type ChatActionConfig struct {
BaseChat BaseChat
@@ -547,7 +676,10 @@ type ChatActionConfig struct {
// values returns a url.Values representation of ChatActionConfig. // values returns a url.Values representation of ChatActionConfig.
func (config ChatActionConfig) values() (url.Values, error) { func (config ChatActionConfig) values() (url.Values, error) {
v, _ := config.BaseChat.values() v, err := config.BaseChat.values()
if err != nil {
return v, err
}
v.Add("action", config.Action) v.Add("action", config.Action)
return v, nil return v, nil
} }
@@ -566,7 +698,10 @@ type EditMessageTextConfig struct {
} }
func (config EditMessageTextConfig) values() (url.Values, error) { func (config EditMessageTextConfig) values() (url.Values, error) {
v, _ := config.BaseEdit.values() v, err := config.BaseEdit.values()
if err != nil {
return v, err
}
v.Add("text", config.Text) v.Add("text", config.Text)
v.Add("parse_mode", config.ParseMode) v.Add("parse_mode", config.ParseMode)
@@ -635,6 +770,7 @@ type UpdateConfig struct {
type WebhookConfig struct { type WebhookConfig struct {
URL *url.URL URL *url.URL
Certificate interface{} Certificate interface{}
MaxConnections int
} }
// FileBytes contains information about a set of bytes to upload // FileBytes contains information about a set of bytes to upload
@@ -669,6 +805,8 @@ type CallbackConfig struct {
CallbackQueryID string `json:"callback_query_id"` CallbackQueryID string `json:"callback_query_id"`
Text string `json:"text"` Text string `json:"text"`
ShowAlert bool `json:"show_alert"` ShowAlert bool `json:"show_alert"`
URL string `json:"url"`
CacheTime int `json:"cache_time"`
} }
// ChatMemberConfig contains information about a user in a chat for use // ChatMemberConfig contains information about a user in a chat for use

View File

@@ -1,6 +1,7 @@
package tgbotapi package tgbotapi
import ( import (
"log"
"net/url" "net/url"
) )
@@ -20,6 +21,7 @@ func NewMessage(chatID int64, text string) MessageConfig {
// NewMessageToChannel creates a new Message that is sent to a channel // NewMessageToChannel creates a new Message that is sent to a channel
// by username. // by username.
//
// username is the username of the channel, text is the message text. // username is the username of the channel, text is the message text.
func NewMessageToChannel(username string, text string) MessageConfig { func NewMessageToChannel(username string, text string) MessageConfig {
return MessageConfig{ return MessageConfig{
@@ -316,6 +318,21 @@ func NewWebhookWithCert(link string, file interface{}) WebhookConfig {
} }
} }
// NewWebhookWithCert creates a new webhook with a certificate and max_connections.
//
// link is the url you wish to get webhooks,
// file contains a string to a file, FileReader, or FileBytes.
// maxConnections defines maximum number of connections from telegram to your server
func NewWebhookWithCertAndMaxConnections(link string, file interface{}, maxConnections int) WebhookConfig {
u, _ := url.Parse(link)
return WebhookConfig{
URL: u,
Certificate: file,
MaxConnections: maxConnections,
}
}
// NewInlineQueryResultArticle creates a new inline query article. // NewInlineQueryResultArticle creates a new inline query article.
func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle { func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle {
return InlineQueryResultArticle{ return InlineQueryResultArticle{
@@ -479,12 +496,23 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
// NewHideKeyboard hides the keyboard, with the option for being selective // NewHideKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone. // or hiding for everyone.
func NewHideKeyboard(selective bool) ReplyKeyboardHide { func NewHideKeyboard(selective bool) ReplyKeyboardHide {
log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
return ReplyKeyboardHide{ return ReplyKeyboardHide{
HideKeyboard: true, HideKeyboard: true,
Selective: selective, Selective: selective,
} }
} }
// NewRemoveKeyboard hides the keyboard, with the option for being selective
// or hiding for everyone.
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
return ReplyKeyboardRemove{
RemoveKeyboard: true,
Selective: selective,
}
}
// NewKeyboardButton creates a regular keyboard button. // NewKeyboardButton creates a regular keyboard button.
func NewKeyboardButton(text string) KeyboardButton { func NewKeyboardButton(text string) KeyboardButton {
return KeyboardButton{ return KeyboardButton{

View File

@@ -12,10 +12,17 @@ import (
// APIResponse is a response from the Telegram API with the result // APIResponse is a response from the Telegram API with the result
// stored raw. // stored raw.
type APIResponse struct { type APIResponse struct {
Ok bool `json:"ok"` Ok bool `json:"ok"`
Result json.RawMessage `json:"result"` Result json.RawMessage `json:"result"`
ErrorCode int `json:"error_code"` ErrorCode int `json:"error_code"`
Description string `json:"description"` Description string `json:"description"`
Parameters *ResponseParameters `json:"parameters"`
}
// ResponseParameters are various errors that can be returned in APIResponse.
type ResponseParameters struct {
MigrateToChatID int64 `json:"migrate_to_chat_id"` // optional
RetryAfter int `json:"retry_after"` // optional
} }
// Update is an update response, from GetUpdates. // Update is an update response, from GetUpdates.
@@ -23,11 +30,23 @@ type Update struct {
UpdateID int `json:"update_id"` UpdateID int `json:"update_id"`
Message *Message `json:"message"` Message *Message `json:"message"`
EditedMessage *Message `json:"edited_message"` EditedMessage *Message `json:"edited_message"`
ChannelPost *Message `json:"channel_post"`
EditedChannelPost *Message `json:"edited_channel_post"`
InlineQuery *InlineQuery `json:"inline_query"` InlineQuery *InlineQuery `json:"inline_query"`
ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"` ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result"`
CallbackQuery *CallbackQuery `json:"callback_query"` CallbackQuery *CallbackQuery `json:"callback_query"`
} }
// UpdatesChannel is the channel for getting updates.
type UpdatesChannel <-chan Update
// Clear discards all unprocessed incoming updates.
func (ch UpdatesChannel) Clear() {
for len(ch) != 0 {
<-ch
}
}
// User is a user on Telegram. // User is a user on Telegram.
type User struct { type User struct {
ID int `json:"id"` ID int `json:"id"`
@@ -61,12 +80,13 @@ type GroupChat struct {
// Chat contains information about the place a message was sent. // Chat contains information about the place a message was sent.
type Chat struct { type Chat struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Type string `json:"type"` Type string `json:"type"`
Title string `json:"title"` // optional Title string `json:"title"` // optional
UserName string `json:"username"` // optional UserName string `json:"username"` // optional
FirstName string `json:"first_name"` // optional FirstName string `json:"first_name"` // optional
LastName string `json:"last_name"` // optional LastName string `json:"last_name"` // optional
AllMembersAreAdmins bool `json:"all_members_are_administrators"` // optional
} }
// IsPrivate returns if the Chat is a private conversation. // IsPrivate returns if the Chat is a private conversation.
@@ -103,6 +123,7 @@ type Message struct {
Chat *Chat `json:"chat"` Chat *Chat `json:"chat"`
ForwardFrom *User `json:"forward_from"` // optional ForwardFrom *User `json:"forward_from"` // optional
ForwardFromChat *Chat `json:"forward_from_chat"` // optional ForwardFromChat *Chat `json:"forward_from_chat"` // optional
ForwardFromMessageID int `json:"forward_from_message_id"` // optional
ForwardDate int `json:"forward_date"` // optional ForwardDate int `json:"forward_date"` // optional
ReplyToMessage *Message `json:"reply_to_message"` // optional ReplyToMessage *Message `json:"reply_to_message"` // optional
EditDate int `json:"edit_date"` // optional EditDate int `json:"edit_date"` // optional
@@ -110,6 +131,7 @@ type Message struct {
Entities *[]MessageEntity `json:"entities"` // optional Entities *[]MessageEntity `json:"entities"` // optional
Audio *Audio `json:"audio"` // optional Audio *Audio `json:"audio"` // optional
Document *Document `json:"document"` // optional Document *Document `json:"document"` // optional
Game *Game `json:"game"` // optional
Photo *[]PhotoSize `json:"photo"` // optional Photo *[]PhotoSize `json:"photo"` // optional
Sticker *Sticker `json:"sticker"` // optional Sticker *Sticker `json:"sticker"` // optional
Video *Video `json:"video"` // optional Video *Video `json:"video"` // optional
@@ -314,6 +336,12 @@ type ReplyKeyboardHide struct {
Selective bool `json:"selective"` // optional Selective bool `json:"selective"` // optional
} }
// ReplyKeyboardRemove allows the Bot to hide a custom keyboard.
type ReplyKeyboardRemove struct {
RemoveKeyboard bool `json:"remove_keyboard"`
Selective bool `json:"selective"`
}
// InlineKeyboardMarkup is a custom keyboard presented for an inline bot. // InlineKeyboardMarkup is a custom keyboard presented for an inline bot.
type InlineKeyboardMarkup struct { type InlineKeyboardMarkup struct {
InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"` InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
@@ -324,11 +352,15 @@ type InlineKeyboardMarkup struct {
// //
// Note that some values are references as even an empty string // Note that some values are references as even an empty string
// will change behavior. // will change behavior.
//
// CallbackGame, if set, MUST be first button in first row.
type InlineKeyboardButton struct { type InlineKeyboardButton struct {
Text string `json:"text"` Text string `json:"text"`
URL *string `json:"url,omitempty"` // optional URL *string `json:"url,omitempty"` // optional
CallbackData *string `json:"callback_data,omitempty"` // optional CallbackData *string `json:"callback_data,omitempty"` // optional
SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional SwitchInlineQuery *string `json:"switch_inline_query,omitempty"` // optional
SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"` // optional
CallbackGame *CallbackGame `json:"callback_game,omitempty"` // optional
} }
// CallbackQuery is data sent when a keyboard button with callback data // CallbackQuery is data sent when a keyboard button with callback data
@@ -338,7 +370,9 @@ type CallbackQuery struct {
From *User `json:"from"` From *User `json:"from"`
Message *Message `json:"message"` // optional Message *Message `json:"message"` // optional
InlineMessageID string `json:"inline_message_id"` // optional InlineMessageID string `json:"inline_message_id"` // optional
Data string `json:"data"` // optional ChatInstance string `json:"chat_instance"`
Data string `json:"data"` // optional
GameShortName string `json:"game_short_name"` // optional
} }
// ForceReply allows the Bot to have users directly reply to it without // ForceReply allows the Bot to have users directly reply to it without
@@ -369,6 +403,49 @@ func (chat ChatMember) HasLeft() bool { return chat.Status == "left" }
// WasKicked returns if the ChatMember was kicked from the chat. // WasKicked returns if the ChatMember was kicked from the chat.
func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" } func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" }
// Game is a game within Telegram.
type Game struct {
Title string `json:"title"`
Description string `json:"description"`
Photo []PhotoSize `json:"photo"`
Text string `json:"text"`
TextEntities []MessageEntity `json:"text_entities"`
Animation Animation `json:"animation"`
}
// Animation is a GIF animation demonstrating the game.
type Animation struct {
FileID string `json:"file_id"`
Thumb PhotoSize `json:"thumb"`
FileName string `json:"file_name"`
MimeType string `json:"mime_type"`
FileSize int `json:"file_size"`
}
// GameHighScore is a user's score and position on the leaderboard.
type GameHighScore struct {
Position int `json:"position"`
User User `json:"user"`
Score int `json:"score"`
}
// CallbackGame is for starting a game in an inline keyboard button.
type CallbackGame struct{}
// WebhookInfo is information about a currently set webhook.
type WebhookInfo struct {
URL string `json:"url"`
HasCustomCertificate bool `json:"has_custom_certificate"`
PendingUpdateCount int `json:"pending_update_count"`
LastErrorDate int `json:"last_error_date"` // optional
LastErrorMessage string `json:"last_error_message"` // optional
}
// IsSet returns true if a webhook is currently set.
func (info WebhookInfo) IsSet() bool {
return info.URL != ""
}
// InlineQuery is a Query from Telegram for an inline request. // InlineQuery is a Query from Telegram for an inline request.
type InlineQuery struct { type InlineQuery struct {
ID string `json:"id"` ID string `json:"id"`
@@ -460,6 +537,7 @@ type InlineQueryResultAudio struct {
ID string `json:"id"` // required ID string `json:"id"` // required
URL string `json:"audio_url"` // required URL string `json:"audio_url"` // required
Title string `json:"title"` // required Title string `json:"title"` // required
Caption string `json:"caption"`
Performer string `json:"performer"` Performer string `json:"performer"`
Duration int `json:"audio_duration"` Duration int `json:"audio_duration"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
@@ -472,6 +550,7 @@ type InlineQueryResultVoice struct {
ID string `json:"id"` // required ID string `json:"id"` // required
URL string `json:"voice_url"` // required URL string `json:"voice_url"` // required
Title string `json:"title"` // required Title string `json:"title"` // required
Caption string `json:"caption"`
Duration int `json:"voice_duration"` Duration int `json:"voice_duration"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"` ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
InputMessageContent interface{} `json:"input_message_content,omitempty"` InputMessageContent interface{} `json:"input_message_content,omitempty"`
@@ -507,6 +586,14 @@ type InlineQueryResultLocation struct {
ThumbHeight int `json:"thumb_height"` ThumbHeight int `json:"thumb_height"`
} }
// InlineQueryResultGame is an inline query response game.
type InlineQueryResultGame struct {
Type string `json:"type"`
ID string `json:"id"`
GameShortName string `json:"game_short_name"`
ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup"`
}
// ChosenInlineResult is an inline query result chosen by a User // ChosenInlineResult is an inline query result chosen by a User
type ChosenInlineResult struct { type ChosenInlineResult struct {
ResultID string `json:"result_id"` ResultID string `json:"result_id"`

View File

@@ -30,7 +30,7 @@ func botRequest(path string, values url.Values, debug bool) (*botResponseFull, e
return response, nil return response, nil
} }
// GetBotInfo will retrive the complete bot information // GetBotInfo will retrieve the complete bot information
func (api *Client) GetBotInfo(bot string) (*Bot, error) { func (api *Client) GetBotInfo(bot string) (*Bot, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},

View File

@@ -29,18 +29,18 @@ type chatResponseFull struct {
// PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request // PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request
type PostMessageParameters struct { type PostMessageParameters struct {
Text string Text string `json:"text"`
Username string Username string `json:"user_name"`
AsUser bool AsUser bool `json:"as_user"`
Parse string Parse string `json:"parse"`
LinkNames int LinkNames int `json:"link_names"`
Attachments []Attachment Attachments []Attachment `json:"attachments"`
UnfurlLinks bool UnfurlLinks bool `json:"unfurl_links"`
UnfurlMedia bool UnfurlMedia bool `json:"unfurl_media"`
IconURL string IconURL string `json:"icon_url"`
IconEmoji string IconEmoji string `json:"icon_emoji"`
Markdown bool `json:"mrkdwn,omitempty"` Markdown bool `json:"mrkdwn,omitempty"`
EscapeText bool EscapeText bool `json:"escape_text"`
} }
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set // NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set

View File

@@ -17,42 +17,38 @@ func main() {
rtm := api.NewRTM() rtm := api.NewRTM()
go rtm.ManageConnection() go rtm.ManageConnection()
Loop: for msg := range rtm.IncomingEvents {
for { fmt.Print("Event Received: ")
select { switch ev := msg.Data.(type) {
case msg := <-rtm.IncomingEvents: case *slack.HelloEvent:
fmt.Print("Event Received: ") // Ignore hello
switch ev := msg.Data.(type) {
case *slack.HelloEvent:
// Ignore hello
case *slack.ConnectedEvent: case *slack.ConnectedEvent:
fmt.Println("Infos:", ev.Info) fmt.Println("Infos:", ev.Info)
fmt.Println("Connection counter:", ev.ConnectionCount) fmt.Println("Connection counter:", ev.ConnectionCount)
// Replace #general with your Channel ID // Replace #general with your Channel ID
rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general")) rtm.SendMessage(rtm.NewOutgoingMessage("Hello world", "#general"))
case *slack.MessageEvent: case *slack.MessageEvent:
fmt.Printf("Message: %v\n", ev) fmt.Printf("Message: %v\n", ev)
case *slack.PresenceChangeEvent: case *slack.PresenceChangeEvent:
fmt.Printf("Presence Change: %v\n", ev) fmt.Printf("Presence Change: %v\n", ev)
case *slack.LatencyReport: case *slack.LatencyReport:
fmt.Printf("Current latency: %v\n", ev.Value) fmt.Printf("Current latency: %v\n", ev.Value)
case *slack.RTMError: case *slack.RTMError:
fmt.Printf("Error: %s\n", ev.Error()) fmt.Printf("Error: %s\n", ev.Error())
case *slack.InvalidAuthEvent: case *slack.InvalidAuthEvent:
fmt.Printf("Invalid credentials") fmt.Printf("Invalid credentials")
break Loop return
default: default:
// Ignore other events.. // Ignore other events..
// fmt.Printf("Unexpected: %v\n", msg.Data) // fmt.Printf("Unexpected: %v\n", msg.Data)
}
} }
} }
} }

View File

@@ -198,3 +198,13 @@ func (info Info) GetGroupByID(groupID string) *Group {
} }
return nil return nil
} }
// GetIMByID returns an IM given an IM id
func (info Info) GetIMByID(imID string) *IM {
for _, im := range info.IMs {
if im.ID == imID {
return &im
}
}
return nil
}

View File

@@ -8,6 +8,7 @@ import (
type OAuthResponseIncomingWebhook struct { type OAuthResponseIncomingWebhook struct {
URL string `json:"url"` URL string `json:"url"`
Channel string `json:"channel"` Channel string `json:"channel"`
ChannelID string `json:"channel_id,omitempty"`
ConfigurationURL string `json:"configuration_url"` ConfigurationURL string `json:"configuration_url"`
} }
@@ -23,6 +24,7 @@ type OAuthResponse struct {
TeamID string `json:"team_id"` TeamID string `json:"team_id"`
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"` IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
Bot OAuthResponseBot `json:"bot"` Bot OAuthResponseBot `json:"bot"`
UserID string `json:"user_id,omitempty"`
SlackResponse SlackResponse
} }

View File

@@ -44,6 +44,16 @@ type Login struct {
Region string `json:"region"` Region string `json:"region"`
} }
type BillableInfoResponse struct {
BillableInfo map[string]BillingActive `json:"billable_info"`
SlackResponse
}
type BillingActive struct {
BillingActive bool `json:"billing_active"`
}
// AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request // AccessLogParameters contains all the parameters necessary (including the optional ones) for a GetAccessLogs() request
type AccessLogParameters struct { type AccessLogParameters struct {
Count int Count int
@@ -73,6 +83,20 @@ func teamRequest(path string, values url.Values, debug bool) (*TeamResponse, err
return response, nil return response, nil
} }
func billableInfoRequest(path string, values url.Values, debug bool) (map[string]BillingActive, error) {
response := &BillableInfoResponse{}
err := post(path, values, response, debug)
if err != nil {
return nil, err
}
if !response.Ok {
return nil, errors.New(response.Error)
}
return response.BillableInfo, nil
}
func accessLogsRequest(path string, values url.Values, debug bool) (*LoginResponse, error) { func accessLogsRequest(path string, values url.Values, debug bool) (*LoginResponse, error) {
response := &LoginResponse{} response := &LoginResponse{}
err := post(path, values, response, debug) err := post(path, values, response, debug)
@@ -117,3 +141,20 @@ func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging,
return response.Logins, &response.Paging, nil return response.Logins, &response.Paging, nil
} }
func (api *Client) GetBillableInfo(user string) (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
"user": {user},
}
return billableInfoRequest("team.billableInfo", values, api.debug)
}
// GetBillableInfoForTeam returns the billing_active status of all users on the team.
func (api *Client) GetBillableInfoForTeam() (map[string]BillingActive, error) {
values := url.Values{
"token": {api.config.token},
}
return billableInfoRequest("team.billableInfo", values, api.debug)
}

View File

@@ -122,7 +122,7 @@ func (api *Client) GetUserPresence(user string) (*UserPresence, error) {
return &response.UserPresence, nil return &response.UserPresence, nil
} }
// GetUserInfo will retrive the complete user information // GetUserInfo will retrieve the complete user information
func (api *Client) GetUserInfo(user string) (*User, error) { func (api *Client) GetUserInfo(user string) (*User, error) {
values := url.Values{ values := url.Values{
"token": {api.config.token}, "token": {api.config.token},

View File

@@ -74,7 +74,9 @@ func (irc *Connection) readLoop() {
irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg)) irc.Log.Printf("<-- %s\n", strings.TrimSpace(msg))
} }
irc.Lock()
irc.lastMessage = time.Now() irc.lastMessage = time.Now()
irc.Unlock()
event, err := parseToEvent(msg) event, err := parseToEvent(msg)
event.Connection = irc event.Connection = irc
if err == nil { if err == nil {
@@ -171,10 +173,12 @@ func (irc *Connection) pingLoop() {
//Ping at the ping frequency //Ping at the ping frequency
irc.SendRawf("PING %d", time.Now().UnixNano()) irc.SendRawf("PING %d", time.Now().UnixNano())
//Try to recapture nickname if it's not as configured. //Try to recapture nickname if it's not as configured.
irc.Lock()
if irc.nick != irc.nickcurrent { if irc.nick != irc.nickcurrent {
irc.nickcurrent = irc.nick irc.nickcurrent = irc.nick
irc.SendRawf("NICK %s", irc.nick) irc.SendRawf("NICK %s", irc.nick)
} }
irc.Unlock()
case <-irc.end: case <-irc.end:
ticker.Stop() ticker.Stop()
ticker2.Stop() ticker2.Stop()
@@ -183,13 +187,21 @@ func (irc *Connection) pingLoop() {
} }
} }
func (irc *Connection) isQuitting() bool {
irc.Lock()
defer irc.Unlock()
return irc.quit
}
// Main loop to control the connection. // Main loop to control the connection.
func (irc *Connection) Loop() { func (irc *Connection) Loop() {
errChan := irc.ErrorChan() errChan := irc.ErrorChan()
for !irc.quit { for !irc.isQuitting() {
err := <-errChan err := <-errChan
irc.Log.Printf("Error, disconnected: %s\n", err) close(irc.end)
for !irc.quit { irc.Wait()
for !irc.isQuitting() {
irc.Log.Printf("Error, disconnected: %s\n", err)
if err = irc.Reconnect(); err != nil { if err = irc.Reconnect(); err != nil {
irc.Log.Printf("Error while reconnecting: %s\n", err) irc.Log.Printf("Error while reconnecting: %s\n", err)
time.Sleep(60 * time.Second) time.Sleep(60 * time.Second)
@@ -211,8 +223,10 @@ func (irc *Connection) Quit() {
} }
irc.SendRaw(quit) irc.SendRaw(quit)
irc.Lock()
irc.stopped = true irc.stopped = true
irc.quit = true irc.quit = true
irc.Unlock()
} }
// Use the connection to join a given channel. // Use the connection to join a given channel.
@@ -341,37 +355,14 @@ func (irc *Connection) Connected() bool {
// A disconnect sends all buffered messages (if possible), // A disconnect sends all buffered messages (if possible),
// stops all goroutines and then closes the socket. // stops all goroutines and then closes the socket.
func (irc *Connection) Disconnect() { func (irc *Connection) Disconnect() {
for event := range irc.events {
irc.ClearCallback(event)
}
if irc.end != nil {
close(irc.end)
}
irc.end = nil
if irc.pwrite != nil {
close(irc.pwrite)
}
irc.Wait()
if irc.socket != nil { if irc.socket != nil {
irc.socket.Close() irc.socket.Close()
} }
irc.socket = nil
irc.ErrorChan() <- ErrDisconnected irc.ErrorChan() <- ErrDisconnected
} }
// Reconnect to a server using the current connection. // Reconnect to a server using the current connection.
func (irc *Connection) Reconnect() error { func (irc *Connection) Reconnect() error {
if irc.end != nil {
close(irc.end)
}
irc.end = nil
irc.Wait() //make sure that wait group is cleared ensuring that all spawned goroutines have completed
irc.end = make(chan struct{}) irc.end = make(chan struct{})
return irc.Connect(irc.Server) return irc.Connect(irc.Server)
} }
@@ -427,7 +418,7 @@ func (irc *Connection) Connect(server string) error {
} }
irc.stopped = false irc.stopped = false
//irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr()) irc.Log.Printf("Connected to %s (%s)\n", irc.Server, irc.socket.RemoteAddr())
irc.pwrite = make(chan string, 10) irc.pwrite = make(chan string, 10)
irc.Error = make(chan error, 2) irc.Error = make(chan error, 2)

View File

@@ -136,9 +136,8 @@ func (irc *Connection) RunCallbacks(event *Event) {
func (irc *Connection) setupCallbacks() { func (irc *Connection) setupCallbacks() {
irc.events = make(map[string]map[int]func(*Event)) irc.events = make(map[string]map[int]func(*Event))
//Handle error events. This has to be called in a new thred to allow //Handle error events.
//readLoop to exit irc.AddCallback("ERROR", func(e *Event) { irc.Disconnect() })
irc.AddCallback("ERROR", func(e *Event) { go irc.Disconnect() })
//Handle ping events //Handle ping events
irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) }) irc.AddCallback("PING", func(e *Event) { irc.SendRaw("PONG :" + e.Message()) })
@@ -201,7 +200,7 @@ func (irc *Connection) setupCallbacks() {
ns, _ := strconv.ParseInt(e.Message(), 10, 64) ns, _ := strconv.ParseInt(e.Message(), 10, 64)
delta := time.Duration(time.Now().UnixNano() - ns) delta := time.Duration(time.Now().UnixNano() - ns)
if irc.Debug { if irc.Debug {
irc.Log.Printf("Lag: %vs\n", delta) irc.Log.Printf("Lag: %.3f s\n", delta.Seconds())
} }
}) })
@@ -216,6 +215,8 @@ func (irc *Connection) setupCallbacks() {
// 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>" // 1: RPL_WELCOME "Welcome to the Internet Relay Network <nick>!<user>@<host>"
// Set irc.nickcurrent to the actually used nick in this connection. // Set irc.nickcurrent to the actually used nick in this connection.
irc.AddCallback("001", func(e *Event) { irc.AddCallback("001", func(e *Event) {
irc.Lock()
irc.nickcurrent = e.Arguments[0] irc.nickcurrent = e.Arguments[0]
irc.Unlock()
}) })
} }

View File

@@ -13,6 +13,7 @@ import (
) )
type Connection struct { type Connection struct {
sync.Mutex
sync.WaitGroup sync.WaitGroup
Debug bool Debug bool
Error chan error Error chan error
@@ -46,7 +47,7 @@ type Connection struct {
Log *log.Logger Log *log.Logger
stopped bool stopped bool
quit bool quit bool //User called Quit, do not reconnect.
} }
// A struct to represent an event. // A struct to represent an event.

6
vendor/manifest vendored
View File

@@ -63,7 +63,7 @@
"importpath": "github.com/go-telegram-bot-api/telegram-bot-api", "importpath": "github.com/go-telegram-bot-api/telegram-bot-api",
"repository": "https://github.com/go-telegram-bot-api/telegram-bot-api", "repository": "https://github.com/go-telegram-bot-api/telegram-bot-api",
"vcs": "git", "vcs": "git",
"revision": "a7f48eb2dd301356942677e65bebe0c9aef07013", "revision": "3293f3ad8411de32d99e443cd82aec7c3bb01a5a",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
@@ -146,7 +146,7 @@
"importpath": "github.com/nlopes/slack", "importpath": "github.com/nlopes/slack",
"repository": "https://github.com/nlopes/slack", "repository": "https://github.com/nlopes/slack",
"vcs": "git", "vcs": "git",
"revision": "e595e9d8590a04ff76407e4e7d1791d25b095c66", "revision": "248e1d53d27901137764ae625890c127bf67e7aa",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
@@ -186,7 +186,7 @@
"importpath": "github.com/thoj/go-ircevent", "importpath": "github.com/thoj/go-ircevent",
"repository": "https://github.com/thoj/go-ircevent", "repository": "https://github.com/thoj/go-ircevent",
"vcs": "git", "vcs": "git",
"revision": "98c1902dd2097f38142384167e60206ba26f1585", "revision": "1b0acb5f2f1b615cfbd4b9f91abb14cb39a18769",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },