2016-09-19 19:05:13 +00:00
package bdiscord
import (
2017-11-02 23:05:10 +00:00
"bytes"
2018-09-12 20:30:14 +00:00
"errors"
2018-02-23 23:05:43 +00:00
"fmt"
2018-06-09 10:47:40 +00:00
"regexp"
"strings"
"sync"
2018-02-26 23:33:21 +00:00
"github.com/42wim/matterbridge/bridge"
2016-09-19 19:05:13 +00:00
"github.com/42wim/matterbridge/bridge/config"
2018-02-03 00:11:11 +00:00
"github.com/42wim/matterbridge/bridge/helper"
2018-08-06 19:10:54 +00:00
"github.com/matterbridge/discordgo"
2016-09-19 19:05:13 +00:00
)
2018-07-21 22:27:49 +00:00
const MessageLength = 1950
2018-02-24 22:22:15 +00:00
type Bdiscord struct {
2017-08-12 12:51:41 +00:00
c * discordgo . Session
Channels [ ] * discordgo . Channel
Nick string
UseChannelID bool
userMemberMap map [ string ] * discordgo . Member
2018-09-12 20:30:14 +00:00
nickMemberMap map [ string ] * discordgo . Member
2017-08-12 12:51:41 +00:00
guildID string
webhookID string
webhookToken string
channelInfoMap map [ string ] * config . ChannelInfo
2017-02-13 17:52:52 +00:00
sync . RWMutex
2018-03-04 22:52:14 +00:00
* bridge . Config
2016-09-19 19:05:13 +00:00
}
2018-03-04 22:52:14 +00:00
func New ( cfg * bridge . Config ) bridge . Bridger {
b := & Bdiscord { Config : cfg }
2017-02-13 17:52:52 +00:00
b . userMemberMap = make ( map [ string ] * discordgo . Member )
2018-09-12 20:30:14 +00:00
b . nickMemberMap = make ( map [ string ] * discordgo . Member )
2017-08-12 12:51:41 +00:00
b . channelInfoMap = make ( map [ string ] * config . ChannelInfo )
2018-03-04 22:52:14 +00:00
if b . GetString ( "WebhookURL" ) != "" {
2018-02-26 23:33:21 +00:00
b . Log . Debug ( "Configuring Discord Incoming Webhook" )
2018-03-04 22:52:14 +00:00
b . webhookID , b . webhookToken = b . splitURL ( b . GetString ( "WebhookURL" ) )
2017-06-26 18:07:27 +00:00
}
2016-09-19 19:05:13 +00:00
return b
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) Connect ( ) error {
2016-09-19 19:05:13 +00:00
var err error
2018-03-04 22:52:14 +00:00
var token string
2018-02-26 23:33:21 +00:00
b . Log . Info ( "Connecting" )
2018-03-04 22:52:14 +00:00
if b . GetString ( "WebhookURL" ) == "" {
2018-02-26 23:33:21 +00:00
b . Log . Info ( "Connecting using token" )
2017-06-24 17:36:10 +00:00
} else {
2018-02-26 23:33:21 +00:00
b . Log . Info ( "Connecting using webhookurl (for posting) and token" )
2017-06-24 17:36:10 +00:00
}
2018-03-04 22:52:14 +00:00
if ! strings . HasPrefix ( b . GetString ( "Token" ) , "Bot " ) {
token = "Bot " + b . GetString ( "Token" )
2016-11-14 15:30:43 +00:00
}
2018-03-04 22:52:14 +00:00
b . c , err = discordgo . New ( token )
2016-09-19 19:05:13 +00:00
if err != nil {
return err
}
2018-02-26 23:33:21 +00:00
b . Log . Info ( "Connection succeeded" )
2016-09-19 19:05:13 +00:00
b . c . AddHandler ( b . messageCreate )
2017-02-13 17:52:52 +00:00
b . c . AddHandler ( b . memberUpdate )
2017-04-15 17:00:15 +00:00
b . c . AddHandler ( b . messageUpdate )
2017-09-11 20:45:15 +00:00
b . c . AddHandler ( b . messageDelete )
2016-09-19 19:05:13 +00:00
err = b . c . Open ( )
if err != nil {
return err
}
2017-07-16 12:39:00 +00:00
guilds , err := b . c . UserGuilds ( 100 , "" , "" )
2016-09-19 19:05:13 +00:00
if err != nil {
return err
}
userinfo , err := b . c . User ( "@me" )
if err != nil {
return err
}
b . Nick = userinfo . Username
for _ , guild := range guilds {
2018-03-04 22:52:14 +00:00
if guild . Name == b . GetString ( "Server" ) {
2016-09-19 19:05:13 +00:00
b . Channels , err = b . c . GuildChannels ( guild . ID )
2017-02-13 17:52:52 +00:00
b . guildID = guild . ID
2016-09-19 19:05:13 +00:00
if err != nil {
return err
}
}
}
2018-03-17 21:56:58 +00:00
for _ , channel := range b . Channels {
b . Log . Debugf ( "found channel %#v" , channel )
}
2016-09-19 19:05:13 +00:00
return nil
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) Disconnect ( ) error {
2018-03-04 22:52:14 +00:00
return b . c . Close ( )
2017-02-14 20:12:02 +00:00
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) JoinChannel ( channel config . ChannelInfo ) error {
2017-08-12 12:51:41 +00:00
b . channelInfoMap [ channel . ID ] = & channel
idcheck := strings . Split ( channel . Name , "ID:" )
2016-10-25 23:01:36 +00:00
if len ( idcheck ) > 1 {
b . UseChannelID = true
}
2016-09-19 19:05:13 +00:00
return nil
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) Send ( msg config . Message ) ( string , error ) {
2018-02-28 21:23:29 +00:00
b . Log . Debugf ( "=> Receiving %#v" , msg )
2018-02-23 23:05:43 +00:00
2016-09-19 19:05:13 +00:00
channelID := b . getChannelID ( msg . Channel )
if channelID == "" {
2018-02-23 23:05:43 +00:00
return "" , fmt . Errorf ( "Could not find channelID for %v" , msg . Channel )
2016-09-19 19:05:13 +00:00
}
2018-02-23 23:05:43 +00:00
// Make a action /me of the message
2017-07-31 19:37:19 +00:00
if msg . Event == config . EVENT_USER_ACTION {
msg . Text = "_" + msg . Text + "_"
}
2017-08-12 12:51:41 +00:00
2018-02-23 23:05:43 +00:00
// use initial webhook
2017-08-12 12:51:41 +00:00
wID := b . webhookID
wToken := b . webhookToken
2018-02-23 23:05:43 +00:00
// check if have a channel specific webhook
2017-08-12 14:30:00 +00:00
if ci , ok := b . channelInfoMap [ msg . Channel + b . Account ] ; ok {
2017-08-12 12:51:41 +00:00
if ci . Options . WebhookURL != "" {
wID , wToken = b . splitURL ( ci . Options . WebhookURL )
}
}
2018-02-23 23:05:43 +00:00
// Use webhook to send the message
if wID != "" {
2018-02-24 21:38:27 +00:00
// skip events
2018-06-28 19:19:02 +00:00
if msg . Event != "" && msg . Event != config . EVENT_JOIN_LEAVE && msg . Event != config . EVENT_TOPIC_CHANGE {
2018-02-24 21:38:27 +00:00
return "" , nil
}
2018-02-26 23:33:21 +00:00
b . Log . Debugf ( "Broadcasting using Webhook" )
2018-04-17 21:52:48 +00:00
for _ , f := range msg . Extra [ "file" ] {
fi := f . ( config . FileInfo )
if fi . URL != "" {
2018-06-29 20:35:29 +00:00
msg . Text += " " + fi . URL
2018-04-17 21:52:48 +00:00
}
}
2018-07-21 21:19:11 +00:00
// skip empty messages
if msg . Text == "" {
return "" , nil
}
2018-07-21 22:27:49 +00:00
msg . Text = helper . ClipMessage ( msg . Text , MessageLength )
2018-02-23 23:05:43 +00:00
err := b . c . WebhookExecute (
wID ,
wToken ,
true ,
& discordgo . WebhookParams {
Content : msg . Text ,
Username : msg . Username ,
AvatarURL : msg . Avatar ,
} )
return "" , err
}
2017-11-02 23:05:10 +00:00
2018-02-26 23:33:21 +00:00
b . Log . Debugf ( "Broadcasting using token (API)" )
2018-02-23 23:05:43 +00:00
// Delete message
if msg . Event == config . EVENT_MSG_DELETE {
if msg . ID == "" {
return "" , nil
2017-11-02 23:05:10 +00:00
}
2018-02-23 23:05:43 +00:00
err := b . c . ChannelMessageDelete ( channelID , msg . ID )
return "" , err
}
2017-11-04 13:47:14 +00:00
2018-02-23 23:05:43 +00:00
// Upload a file if it exists
if msg . Extra != nil {
for _ , rmsg := range helper . HandleExtra ( & msg , b . General ) {
2018-07-21 22:27:49 +00:00
rmsg . Text = helper . ClipMessage ( rmsg . Text , MessageLength )
2018-02-23 23:05:43 +00:00
b . c . ChannelMessageSend ( channelID , rmsg . Username + rmsg . Text )
}
// check if we have files to upload (from slack, telegram or mattermost)
if len ( msg . Extra [ "file" ] ) > 0 {
return b . handleUploadFile ( & msg , channelID )
2017-08-27 22:33:17 +00:00
}
2018-02-23 23:05:43 +00:00
}
2018-07-21 22:27:49 +00:00
msg . Text = helper . ClipMessage ( msg . Text , MessageLength )
2018-09-12 20:30:14 +00:00
msg . Text = b . replaceUserMentions ( msg . Text )
2018-02-23 23:05:43 +00:00
// Edit message
if msg . ID != "" {
_ , err := b . c . ChannelMessageEdit ( channelID , msg . ID , msg . Username + msg . Text )
return msg . ID , err
}
// Post normal message
res , err := b . c . ChannelMessageSend ( channelID , msg . Username + msg . Text )
if err != nil {
return "" , err
}
return res . ID , err
2016-09-19 19:05:13 +00:00
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) messageDelete ( s * discordgo . Session , m * discordgo . MessageDelete ) {
2017-09-11 20:45:15 +00:00
rmsg := config . Message { Account : b . Account , ID : m . ID , Event : config . EVENT_MSG_DELETE , Text : config . EVENT_MSG_DELETE }
rmsg . Channel = b . getChannelName ( m . ChannelID )
if b . UseChannelID {
rmsg . Channel = "ID:" + m . ChannelID
}
2018-02-28 21:23:29 +00:00
b . Log . Debugf ( "<= Sending message from %s to gateway" , b . Account )
b . Log . Debugf ( "<= Message is %#v" , rmsg )
2017-09-11 20:45:15 +00:00
b . Remote <- rmsg
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) messageUpdate ( s * discordgo . Session , m * discordgo . MessageUpdate ) {
2018-03-04 22:52:14 +00:00
if b . GetBool ( "EditDisable" ) {
2017-04-15 17:00:15 +00:00
return
}
// only when message is actually edited
if m . Message . EditedTimestamp != "" {
2018-02-26 23:33:21 +00:00
b . Log . Debugf ( "Sending edit message" )
2018-03-04 22:52:14 +00:00
m . Content = m . Content + b . GetString ( "EditSuffix" )
2017-04-15 17:00:15 +00:00
b . messageCreate ( s , ( * discordgo . MessageCreate ) ( m ) )
}
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) messageCreate ( s * discordgo . Session , m * discordgo . MessageCreate ) {
2018-02-14 22:05:50 +00:00
var err error
2018-02-23 23:05:43 +00:00
2016-09-19 19:05:13 +00:00
// not relay our own messages
if m . Author . Username == b . Nick {
return
}
2017-06-26 18:07:27 +00:00
// if using webhooks, do not relay if it's ours
2017-08-12 12:51:41 +00:00
if b . useWebhook ( ) && m . Author . Bot && b . isWebhookID ( m . Author . ID ) {
2017-06-26 18:07:27 +00:00
return
}
2017-07-09 11:41:46 +00:00
2018-02-23 23:05:43 +00:00
// add the url of the attachments to content
2016-10-25 22:09:22 +00:00
if len ( m . Attachments ) > 0 {
for _ , attach := range m . Attachments {
m . Content = m . Content + "\n" + attach . URL
}
}
2017-07-09 11:41:46 +00:00
2018-02-23 23:05:43 +00:00
rmsg := config . Message { Account : b . Account , Avatar : "https://cdn.discordapp.com/avatars/" + m . Author . ID + "/" + m . Author . Avatar + ".jpg" , UserID : m . Author . ID , ID : m . ID }
2017-07-09 11:41:46 +00:00
if m . Content != "" {
2018-02-28 21:23:29 +00:00
b . Log . Debugf ( "== Receiving event %#v" , m . Message )
2017-07-09 11:41:46 +00:00
m . Message . Content = b . stripCustomoji ( m . Message . Content )
m . Message . Content = b . replaceChannelMentions ( m . Message . Content )
2018-02-23 23:05:43 +00:00
rmsg . Text , err = m . ContentWithMoreMentionsReplaced ( b . c )
2018-02-14 22:05:50 +00:00
if err != nil {
2018-02-26 23:33:21 +00:00
b . Log . Errorf ( "ContentWithMoreMentionsReplaced failed: %s" , err )
2018-02-23 23:05:43 +00:00
rmsg . Text = m . ContentWithMentionsReplaced ( )
2018-02-14 22:05:50 +00:00
}
2016-10-25 22:12:31 +00:00
}
2017-07-09 11:41:46 +00:00
2018-02-23 23:05:43 +00:00
// set channel name
2017-07-30 15:48:23 +00:00
rmsg . Channel = b . getChannelName ( m . ChannelID )
2016-10-25 23:01:36 +00:00
if b . UseChannelID {
2017-07-30 15:48:23 +00:00
rmsg . Channel = "ID:" + m . ChannelID
2016-10-25 23:01:36 +00:00
}
2017-08-01 16:18:55 +00:00
2018-02-23 23:05:43 +00:00
// set username
2018-03-04 22:52:14 +00:00
if ! b . GetBool ( "UseUserName" ) {
2017-08-01 16:18:55 +00:00
rmsg . Username = b . getNick ( m . Author )
} else {
rmsg . Username = m . Author . Username
}
2017-06-24 21:17:57 +00:00
2018-02-23 23:05:43 +00:00
// if we have embedded content add it to text
2018-03-04 22:52:14 +00:00
if b . GetBool ( "ShowEmbeds" ) && m . Message . Embeds != nil {
2017-06-24 21:17:57 +00:00
for _ , embed := range m . Message . Embeds {
2018-02-23 23:05:43 +00:00
rmsg . Text = rmsg . Text + "embed: " + embed . Title + " - " + embed . Description + " - " + embed . URL + "\n"
2017-06-24 21:17:57 +00:00
}
}
2017-07-09 11:41:46 +00:00
// no empty messages
2018-02-23 23:05:43 +00:00
if rmsg . Text == "" {
2017-07-09 11:41:46 +00:00
return
}
2018-02-23 23:05:43 +00:00
// do we have a /me action
var ok bool
rmsg . Text , ok = b . replaceAction ( rmsg . Text )
2017-07-30 15:48:23 +00:00
if ok {
rmsg . Event = config . EVENT_USER_ACTION
}
2018-02-28 21:23:29 +00:00
b . Log . Debugf ( "<= Sending message from %s on %s to gateway" , m . Author . Username , b . Account )
b . Log . Debugf ( "<= Message is %#v" , rmsg )
2017-07-30 15:48:23 +00:00
b . Remote <- rmsg
2016-09-19 19:05:13 +00:00
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) memberUpdate ( s * discordgo . Session , m * discordgo . GuildMemberUpdate ) {
2017-02-13 17:52:52 +00:00
b . Lock ( )
if _ , ok := b . userMemberMap [ m . Member . User . ID ] ; ok {
2018-02-26 23:33:21 +00:00
b . Log . Debugf ( "%s: memberupdate: user %s (nick %s) changes nick to %s" , b . Account , m . Member . User . Username , b . userMemberMap [ m . Member . User . ID ] . Nick , m . Member . Nick )
2017-02-13 17:52:52 +00:00
}
b . userMemberMap [ m . Member . User . ID ] = m . Member
2018-09-12 20:30:14 +00:00
b . nickMemberMap [ m . Member . Nick ] = m . Member
2017-02-13 17:52:52 +00:00
b . Unlock ( )
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) getNick ( user * discordgo . User ) string {
2017-02-13 17:52:52 +00:00
var err error
b . Lock ( )
defer b . Unlock ( )
if _ , ok := b . userMemberMap [ user . ID ] ; ok {
2017-05-22 19:57:19 +00:00
if b . userMemberMap [ user . ID ] != nil {
if b . userMemberMap [ user . ID ] . Nick != "" {
// only return if nick is set
return b . userMemberMap [ user . ID ] . Nick
}
// otherwise return username
return user . Username
2017-02-13 17:52:52 +00:00
}
}
// if we didn't find nick, search for it
2017-06-15 20:29:01 +00:00
member , err := b . c . GuildMember ( b . guildID , user . ID )
2017-02-13 17:52:52 +00:00
if err != nil {
return user . Username
}
2017-06-15 20:29:01 +00:00
b . userMemberMap [ user . ID ] = member
2017-02-13 17:52:52 +00:00
// only return if nick is set
if b . userMemberMap [ user . ID ] . Nick != "" {
return b . userMemberMap [ user . ID ] . Nick
}
return user . Username
}
2018-09-12 20:30:14 +00:00
func ( b * Bdiscord ) getGuildMemberByNick ( nick string ) ( * discordgo . Member , error ) {
b . Lock ( )
defer b . Unlock ( )
if _ , ok := b . nickMemberMap [ nick ] ; ok {
if b . nickMemberMap [ nick ] != nil {
return b . nickMemberMap [ nick ] , nil
}
}
return nil , errors . New ( "Couldn't find guild member with nick " + nick ) // This will most likely get ignored by the caller
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) getChannelID ( name string ) string {
2016-10-25 23:01:36 +00:00
idcheck := strings . Split ( name , "ID:" )
if len ( idcheck ) > 1 {
return idcheck [ 1 ]
}
2016-09-19 19:05:13 +00:00
for _ , channel := range b . Channels {
if channel . Name == name {
return channel . ID
}
}
return ""
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) getChannelName ( id string ) string {
2016-09-19 19:05:13 +00:00
for _ , channel := range b . Channels {
if channel . ID == id {
return channel . Name
}
}
return ""
}
2017-03-18 15:50:09 +00:00
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) replaceChannelMentions ( text string ) string {
2017-05-23 20:26:37 +00:00
var err error
re := regexp . MustCompile ( "<#[0-9]+>" )
text = re . ReplaceAllStringFunc ( text , func ( m string ) string {
channel := b . getChannelName ( m [ 2 : len ( m ) - 1 ] )
// if at first don't succeed, try again
if channel == "" {
b . Channels , err = b . c . GuildChannels ( b . guildID )
if err != nil {
return "#unknownchannel"
}
channel = b . getChannelName ( m [ 2 : len ( m ) - 1 ] )
2017-06-03 16:21:47 +00:00
return "#" + channel
2017-05-23 20:26:37 +00:00
}
2017-06-03 16:21:47 +00:00
return "#" + channel
2017-05-23 20:26:37 +00:00
} )
2018-09-12 20:30:14 +00:00
return text
}
func ( b * Bdiscord ) replaceUserMentions ( text string ) string {
re := regexp . MustCompile ( "@[^@]{1,32}" )
text = re . ReplaceAllStringFunc ( text , func ( m string ) string {
mention := strings . TrimSpace ( m [ 1 : ] )
var member * discordgo . Member
var err error
for {
b . Log . Debugf ( "Testing mention: '%s'" , mention )
member , err = b . getGuildMemberByNick ( mention )
if err != nil {
lastSpace := strings . LastIndex ( mention , " " )
if lastSpace == - 1 {
break
}
mention = strings . TrimSpace ( mention [ 0 : lastSpace ] )
} else {
break
}
}
if err != nil {
return m
}
return member . User . Mention ( )
} )
b . Log . Debugf ( "Message with mention replaced: %s" , text )
2017-05-23 20:26:37 +00:00
return text
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) replaceAction ( text string ) ( string , bool ) {
2017-07-30 15:48:23 +00:00
if strings . HasPrefix ( text , "_" ) && strings . HasSuffix ( text , "_" ) {
return strings . Replace ( text , "_" , "" , - 1 ) , true
}
return text , false
}
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) stripCustomoji ( text string ) string {
2017-04-15 14:23:34 +00:00
// <:doge:302803592035958784>
re := regexp . MustCompile ( "<(:.*?:)[0-9]+>" )
return re . ReplaceAllString ( text , ` $1 ` )
}
2017-08-12 12:51:41 +00:00
// splitURL splits a webhookURL and returns the id and token
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) splitURL ( url string ) ( string , string ) {
2017-08-12 12:51:41 +00:00
webhookURLSplit := strings . Split ( url , "/" )
2018-02-07 13:57:38 +00:00
if len ( webhookURLSplit ) != 7 {
2018-02-26 23:33:21 +00:00
b . Log . Fatalf ( "%s is no correct discord WebhookURL" , url )
2018-02-07 13:57:38 +00:00
}
2017-08-12 12:51:41 +00:00
return webhookURLSplit [ len ( webhookURLSplit ) - 2 ] , webhookURLSplit [ len ( webhookURLSplit ) - 1 ]
}
// useWebhook returns true if we have a webhook defined somewhere
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) useWebhook ( ) bool {
2018-03-04 22:52:14 +00:00
if b . GetString ( "WebhookURL" ) != "" {
2017-08-12 12:51:41 +00:00
return true
}
for _ , channel := range b . channelInfoMap {
if channel . Options . WebhookURL != "" {
return true
}
}
return false
}
// isWebhookID returns true if the specified id is used in a defined webhook
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) isWebhookID ( id string ) bool {
2018-03-04 22:52:14 +00:00
if b . GetString ( "WebhookURL" ) != "" {
wID , _ := b . splitURL ( b . GetString ( "WebhookURL" ) )
2017-08-12 14:30:00 +00:00
if wID == id {
return true
}
}
2017-08-12 12:51:41 +00:00
for _ , channel := range b . channelInfoMap {
if channel . Options . WebhookURL != "" {
wID , _ := b . splitURL ( channel . Options . WebhookURL )
if wID == id {
return true
}
}
}
return false
}
2018-02-23 23:05:43 +00:00
// handleUploadFile handles native upload of files
2018-02-24 22:22:15 +00:00
func ( b * Bdiscord ) handleUploadFile ( msg * config . Message , channelID string ) ( string , error ) {
2018-02-23 23:05:43 +00:00
var err error
for _ , f := range msg . Extra [ "file" ] {
fi := f . ( config . FileInfo )
files := [ ] * discordgo . File { }
files = append ( files , & discordgo . File { fi . Name , "" , bytes . NewReader ( * fi . Data ) } )
_ , err = b . c . ChannelMessageSendComplex ( channelID , & discordgo . MessageSend { Content : msg . Username + fi . Comment , Files : files } )
if err != nil {
return "" , fmt . Errorf ( "file upload failed: %#v" , err )
}
}
return "" , nil
}