mirror of
https://github.com/cwinfo/matterbridge.git
synced 2025-06-28 17:19:24 +00:00
Compare commits
15 Commits
Author | SHA1 | Date | |
---|---|---|---|
c4c6aff9a5 | |||
d71850cef6 | |||
2597c9bfac | |||
93307b57aa | |||
618953c865 | |||
e04dd78624 | |||
fa0c4025f7 | |||
2d2d185200 | |||
cb7278eb50 | |||
89aa114192 | |||
ed062e0ce5 | |||
a69ef8402b | |||
8779f67d2d | |||
e4b72136b8 | |||
4ff5091bc2 |
@ -69,7 +69,7 @@ See https://github.com/42wim/matterbridge/wiki
|
|||||||
|
|
||||||
# Installing
|
# Installing
|
||||||
## Binaries
|
## Binaries
|
||||||
* Latest stable release [v1.11.0](https://github.com/42wim/matterbridge/releases/latest)
|
* Latest stable release [v1.11.1](https://github.com/42wim/matterbridge/releases/latest)
|
||||||
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
* Development releases (follows master) can be downloaded [here](https://dl.bintray.com/42wim/nightly/)
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
@ -13,6 +13,8 @@ import (
|
|||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MessageLength = 1950
|
||||||
|
|
||||||
type Bdiscord struct {
|
type Bdiscord struct {
|
||||||
c *discordgo.Session
|
c *discordgo.Session
|
||||||
Channels []*discordgo.Channel
|
Channels []*discordgo.Channel
|
||||||
@ -127,16 +129,22 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
// Use webhook to send the message
|
// Use webhook to send the message
|
||||||
if wID != "" {
|
if wID != "" {
|
||||||
// skip events
|
// skip events
|
||||||
if msg.Event != "" {
|
if msg.Event != "" && msg.Event != config.EVENT_JOIN_LEAVE && msg.Event != config.EVENT_TOPIC_CHANGE {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
b.Log.Debugf("Broadcasting using Webhook")
|
b.Log.Debugf("Broadcasting using Webhook")
|
||||||
for _, f := range msg.Extra["file"] {
|
for _, f := range msg.Extra["file"] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
if fi.URL != "" {
|
if fi.URL != "" {
|
||||||
msg.Text += fi.URL + " "
|
msg.Text += " " + fi.URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// skip empty messages
|
||||||
|
if msg.Text == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
||||||
err := b.c.WebhookExecute(
|
err := b.c.WebhookExecute(
|
||||||
wID,
|
wID,
|
||||||
wToken,
|
wToken,
|
||||||
@ -163,6 +171,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
|
rmsg.Text = helper.ClipMessage(rmsg.Text, MessageLength)
|
||||||
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
b.c.ChannelMessageSend(channelID, rmsg.Username+rmsg.Text)
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
@ -171,6 +180,7 @@ func (b *Bdiscord) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
msg.Text = helper.ClipMessage(msg.Text, MessageLength)
|
||||||
// Edit message
|
// Edit message
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
_, err := b.c.ChannelMessageEdit(channelID, msg.ID, msg.Username+msg.Text)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@ -59,7 +60,7 @@ func HandleExtra(msg *config.Message, general *config.Protocol) []config.Message
|
|||||||
for _, f := range extra[config.EVENT_FILE_FAILURE_SIZE] {
|
for _, f := range extra[config.EVENT_FILE_FAILURE_SIZE] {
|
||||||
fi := f.(config.FileInfo)
|
fi := f.(config.FileInfo)
|
||||||
text := fmt.Sprintf("file %s too big to download (%#v > allowed size: %#v)", fi.Name, fi.Size, general.MediaDownloadSize)
|
text := fmt.Sprintf("file %s too big to download (%#v > allowed size: %#v)", fi.Name, fi.Size, general.MediaDownloadSize)
|
||||||
rmsg = append(rmsg, config.Message{Text: text, Username: "<system> ", Channel: msg.Channel})
|
rmsg = append(rmsg, config.Message{Text: text, Username: "<system> ", Channel: msg.Channel, Account: msg.Account})
|
||||||
}
|
}
|
||||||
return rmsg
|
return rmsg
|
||||||
}
|
}
|
||||||
@ -115,3 +116,15 @@ func RemoveEmptyNewLines(msg string) string {
|
|||||||
lines = strings.TrimRight(lines, "\n")
|
lines = strings.TrimRight(lines, "\n")
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ClipMessage(text string, length int) string {
|
||||||
|
// clip too long messages
|
||||||
|
if len(text) > length {
|
||||||
|
text = text[:length-len(" *message clipped*")]
|
||||||
|
if r, size := utf8.DecodeLastRuneInString(text); r == utf8.RuneError {
|
||||||
|
text = text[:len(text)-size]
|
||||||
|
}
|
||||||
|
text += " *message clipped*"
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
@ -397,7 +397,7 @@ func (b *Birc) handlePrivMsg(client *girc.Client, event girc.Event) {
|
|||||||
rmsg.Text += event.StripAction()
|
rmsg.Text += event.StripAction()
|
||||||
|
|
||||||
// strip IRC colors
|
// strip IRC colors
|
||||||
re := regexp.MustCompile(`[[:cntrl:]](?:\d{1,2}(?:,\d{1,2})?)?`)
|
re := regexp.MustCompile(`\x03(?:\d{1,2}(?:,\d{1,2})?)?|[[:cntrl:]]`)
|
||||||
rmsg.Text = re.ReplaceAllString(rmsg.Text, "")
|
rmsg.Text = re.ReplaceAllString(rmsg.Text, "")
|
||||||
|
|
||||||
// start detecting the charset
|
// start detecting the charset
|
||||||
|
@ -185,6 +185,9 @@ func (b *Bmattermost) handleMatter() {
|
|||||||
for message := range messages {
|
for message := range messages {
|
||||||
message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General)
|
message.Avatar = helper.GetAvatar(b.avatarMap, message.UserID, b.General)
|
||||||
message.Account = b.Account
|
message.Account = b.Account
|
||||||
|
if nick := b.mc.GetNickName(message.UserID); nick != "" {
|
||||||
|
message.Username = nick
|
||||||
|
}
|
||||||
message.Text, ok = b.replaceAction(message.Text)
|
message.Text, ok = b.replaceAction(message.Text)
|
||||||
if ok {
|
if ok {
|
||||||
message.Event = config.EVENT_USER_ACTION
|
message.Event = config.EVENT_USER_ACTION
|
||||||
@ -205,7 +208,7 @@ func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only download avatars if we have a place to upload them (configured mediaserver)
|
// only download avatars if we have a place to upload them (configured mediaserver)
|
||||||
if b.General.MediaServerUpload != "" {
|
if b.General.MediaServerUpload != "" || b.General.MediaDownloadPath != "" {
|
||||||
b.handleDownloadAvatar(message.UserID, message.Channel)
|
b.handleDownloadAvatar(message.UserID, message.Channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,14 +19,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Bslack struct {
|
type Bslack struct {
|
||||||
mh *matterhook.Client
|
mh *matterhook.Client
|
||||||
sc *slack.Client
|
sc *slack.Client
|
||||||
rtm *slack.RTM
|
rtm *slack.RTM
|
||||||
Users []slack.User
|
Users []slack.User
|
||||||
Usergroups []slack.UserGroup
|
Usergroups []slack.UserGroup
|
||||||
si *slack.Info
|
si *slack.Info
|
||||||
channels []slack.Channel
|
channels []slack.Channel
|
||||||
uuid string
|
UseChannelID bool
|
||||||
|
uuid string
|
||||||
*bridge.Config
|
*bridge.Config
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
@ -98,6 +99,20 @@ func (b *Bslack) Disconnect() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
func (b *Bslack) JoinChannel(channel config.ChannelInfo) error {
|
||||||
|
// use ID:channelid and resolve it to the actual name
|
||||||
|
idcheck := strings.Split(channel.Name, "ID:")
|
||||||
|
if len(idcheck) > 1 {
|
||||||
|
b.UseChannelID = true
|
||||||
|
ch, err := b.sc.GetChannelInfo(idcheck[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
channel.Name = ch.Name
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// we can only join channels using the API
|
// we can only join channels using the API
|
||||||
if b.sc != nil {
|
if b.sc != nil {
|
||||||
if strings.HasPrefix(b.GetString("Token"), "xoxb") {
|
if strings.HasPrefix(b.GetString("Token"), "xoxb") {
|
||||||
@ -131,11 +146,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
return b.sendWebhook(msg)
|
return b.sendWebhook(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the slack channel
|
channelID := b.getChannelID(msg.Channel)
|
||||||
schannel, err := b.getChannelByName(msg.Channel)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete message
|
// Delete message
|
||||||
if msg.Event == config.EVENT_MSG_DELETE {
|
if msg.Event == config.EVENT_MSG_DELETE {
|
||||||
@ -145,7 +156,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
}
|
}
|
||||||
// we get a "slack <ID>", split it
|
// we get a "slack <ID>", split it
|
||||||
ts := strings.Fields(msg.ID)
|
ts := strings.Fields(msg.ID)
|
||||||
_, _, err := b.sc.DeleteMessage(schannel.ID, ts[1])
|
_, _, err := b.sc.DeleteMessage(channelID, ts[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return msg.ID, err
|
return msg.ID, err
|
||||||
}
|
}
|
||||||
@ -160,7 +171,7 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
// Edit message if we have an ID
|
// Edit message if we have an ID
|
||||||
if msg.ID != "" {
|
if msg.ID != "" {
|
||||||
ts := strings.Fields(msg.ID)
|
ts := strings.Fields(msg.ID)
|
||||||
_, _, _, err := b.sc.UpdateMessage(schannel.ID, ts[1], msg.Text)
|
_, _, _, err := b.sc.UpdateMessage(channelID, ts[1], msg.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return msg.ID, err
|
return msg.ID, err
|
||||||
}
|
}
|
||||||
@ -192,16 +203,16 @@ func (b *Bslack) Send(msg config.Message) (string, error) {
|
|||||||
// Upload a file if it exists
|
// Upload a file if it exists
|
||||||
if msg.Extra != nil {
|
if msg.Extra != nil {
|
||||||
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
for _, rmsg := range helper.HandleExtra(&msg, b.General) {
|
||||||
b.sc.PostMessage(schannel.ID, rmsg.Username+rmsg.Text, np)
|
b.sc.PostMessage(channelID, rmsg.Username+rmsg.Text, np)
|
||||||
}
|
}
|
||||||
// check if we have files to upload (from slack, telegram or mattermost)
|
// check if we have files to upload (from slack, telegram or mattermost)
|
||||||
if len(msg.Extra["file"]) > 0 {
|
if len(msg.Extra["file"]) > 0 {
|
||||||
b.handleUploadFile(&msg, schannel.ID)
|
b.handleUploadFile(&msg, channelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post normal message
|
// Post normal message
|
||||||
_, id, err := b.sc.PostMessage(schannel.ID, msg.Text, np)
|
_, id, err := b.sc.PostMessage(channelID, msg.Text, np)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -212,11 +223,11 @@ func (b *Bslack) Reload(cfg *bridge.Config) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bslack) getAvatar(user string) string {
|
func (b *Bslack) getAvatar(userid string) string {
|
||||||
var avatar string
|
var avatar string
|
||||||
if b.Users != nil {
|
if b.Users != nil {
|
||||||
for _, u := range b.Users {
|
for _, u := range b.Users {
|
||||||
if user == u.Name {
|
if userid == u.ID {
|
||||||
return u.Profile.Image48
|
return u.Profile.Image48
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,6 +235,7 @@ func (b *Bslack) getAvatar(user string) string {
|
|||||||
return avatar
|
return avatar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
|
func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
|
||||||
if b.channels == nil {
|
if b.channels == nil {
|
||||||
return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name)
|
return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name)
|
||||||
@ -235,6 +247,7 @@ func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("%s: channel %s not found", b.Account, name)
|
return nil, fmt.Errorf("%s: channel %s not found", b.Account, name)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
|
func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
|
||||||
if b.channels == nil {
|
if b.channels == nil {
|
||||||
@ -270,7 +283,7 @@ func (b *Bslack) handleSlack() {
|
|||||||
message.Text = html.UnescapeString(message.Text)
|
message.Text = html.UnescapeString(message.Text)
|
||||||
|
|
||||||
// Add the avatar
|
// Add the avatar
|
||||||
message.Avatar = b.getAvatar(strings.ToLower(message.Username))
|
message.Avatar = b.getAvatar(message.UserID)
|
||||||
|
|
||||||
b.Log.Debugf("<= Message is %#v", message)
|
b.Log.Debugf("<= Message is %#v", message)
|
||||||
b.Remote <- *message
|
b.Remote <- *message
|
||||||
@ -486,6 +499,10 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
|
|||||||
|
|
||||||
rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})}
|
rmsg := config.Message{Text: ev.Text, Channel: channel.Name, Account: b.Account, ID: "slack " + ev.Timestamp, Extra: make(map[string][]interface{})}
|
||||||
|
|
||||||
|
if b.UseChannelID {
|
||||||
|
rmsg.Channel = "ID:" + channel.ID
|
||||||
|
}
|
||||||
|
|
||||||
// find the user id and name
|
// find the user id and name
|
||||||
if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
|
if ev.User != "" && ev.SubType != messageDeleted && ev.SubType != "file_comment" {
|
||||||
user, err := b.rtm.GetUserInfo(ev.User)
|
user, err := b.rtm.GetUserInfo(ev.User)
|
||||||
@ -682,3 +699,16 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bslack) getChannelID(name string) string {
|
||||||
|
idcheck := strings.Split(name, "ID:")
|
||||||
|
if len(idcheck) > 1 {
|
||||||
|
return idcheck[1]
|
||||||
|
}
|
||||||
|
for _, channel := range b.channels {
|
||||||
|
if channel.Name == name {
|
||||||
|
return channel.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
16
changelog.md
16
changelog.md
@ -1,3 +1,19 @@
|
|||||||
|
# v1.11.1
|
||||||
|
|
||||||
|
## New features
|
||||||
|
* slack: Add support for slack channels by ID. Closes #436
|
||||||
|
* discord: Clip too long messages sent to discord (discord). Closes #440
|
||||||
|
|
||||||
|
## Bugfix
|
||||||
|
* general: fix possible panic on downloads that are too big #448
|
||||||
|
* general: Fix avatar uploads to work with MediaDownloadPath. Closes #454
|
||||||
|
* discord: allow receiving of topic changes/channel leave/joins from other bridges through the webhook
|
||||||
|
* discord: Add a space before url in file uploads (discord). Closes #461
|
||||||
|
* discord: Skip empty messages being sent with the webhook (discord). #469
|
||||||
|
* mattermost: Use nickname instead of username if defined (mattermost). Closes #452
|
||||||
|
* irc: Stop numbers being stripped after non-color control codes (irc) (#465)
|
||||||
|
* slack: Use UserID to look for avatar instead of username (slack). Closes #472
|
||||||
|
|
||||||
# v1.11.0
|
# v1.11.0
|
||||||
|
|
||||||
## New features
|
## New features
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "1.11.0"
|
version = "1.11.1"
|
||||||
githash string
|
githash string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1377,6 +1377,7 @@ enable=true
|
|||||||
#gitter - username/room
|
#gitter - username/room
|
||||||
#xmpp - channel
|
#xmpp - channel
|
||||||
#slack - channel (without the #)
|
#slack - channel (without the #)
|
||||||
|
# - ID:C123456 (where C123456 is the channel ID) does not work with webhook
|
||||||
#discord - channel (without the #)
|
#discord - channel (without the #)
|
||||||
# - 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)
|
||||||
|
@ -310,6 +310,11 @@ func (m *MMClient) parseMessage(rmsg *Message) {
|
|||||||
switch rmsg.Raw.Event {
|
switch rmsg.Raw.Event {
|
||||||
case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED, model.WEBSOCKET_EVENT_POST_DELETED:
|
case model.WEBSOCKET_EVENT_POSTED, model.WEBSOCKET_EVENT_POST_EDITED, model.WEBSOCKET_EVENT_POST_DELETED:
|
||||||
m.parseActionPost(rmsg)
|
m.parseActionPost(rmsg)
|
||||||
|
case "user_updated":
|
||||||
|
user := rmsg.Raw.Data["user"].(map[string]interface{})
|
||||||
|
if _, ok := user["id"].(string); ok {
|
||||||
|
m.UpdateUser(user["id"].(string))
|
||||||
|
}
|
||||||
/*
|
/*
|
||||||
case model.ACTION_USER_REMOVED:
|
case model.ACTION_USER_REMOVED:
|
||||||
m.handleWsActionUserRemoved(&rmsg)
|
m.handleWsActionUserRemoved(&rmsg)
|
||||||
@ -750,6 +755,16 @@ func (m *MMClient) GetUser(userId string) *model.User {
|
|||||||
return m.Users[userId]
|
return m.Users[userId]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MMClient) UpdateUser(userId string) {
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
res, resp := m.Client.GetUser(userId, "")
|
||||||
|
if resp.Error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Users[userId] = res
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MMClient) GetUserName(userId string) string {
|
func (m *MMClient) GetUserName(userId string) string {
|
||||||
user := m.GetUser(userId)
|
user := m.GetUser(userId)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
@ -758,6 +773,14 @@ func (m *MMClient) GetUserName(userId string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MMClient) GetNickName(userId string) string {
|
||||||
|
user := m.GetUser(userId)
|
||||||
|
if user != nil {
|
||||||
|
return user.Nickname
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (m *MMClient) GetStatus(userId string) string {
|
func (m *MMClient) GetStatus(userId string) string {
|
||||||
res, resp := m.Client.GetUserStatus(userId, "")
|
res, resp := m.Client.GetUserStatus(userId, "")
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
|
Reference in New Issue
Block a user