457 lines
13 KiB
Go
457 lines
13 KiB
Go
|
|
package azure
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type ChannelAccount struct {
|
||
|
|
Id string `json:"id"`
|
||
|
|
Name string `json:"name,omitempty"`
|
||
|
|
AadObjectId string `json:"aadObjectId,omitempty"`
|
||
|
|
Role string `json:"role,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type AttachmentData struct {
|
||
|
|
Name string `json:"name,omitempty"`
|
||
|
|
OriginalBase64 string `json:"originalBase64,omitempty"`
|
||
|
|
ThumbnailBase64 string `json:"thumbnailBase64,omitempty"`
|
||
|
|
Type string `json:"type,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type MessageActivity struct {
|
||
|
|
Type string `json:"type,omitempty"`
|
||
|
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
||
|
|
LocalTimestamp time.Time `json:"localTimestamp,omitempty"`
|
||
|
|
Id string `json:"id"`
|
||
|
|
ChannelId string `json:"channelId,omitempty"`
|
||
|
|
ServiceUrl string `json:"serviceUrl,omitempty"`
|
||
|
|
Message string
|
||
|
|
Mentions []string
|
||
|
|
From ChannelAccount `json:"from,omitempty"`
|
||
|
|
Conversation *struct {
|
||
|
|
ConversationType string `json:"conversationType,omitempty"`
|
||
|
|
TenantId string `json:"tenantId,omitempty"`
|
||
|
|
Id string `json:"id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
} `json:"conversation,omitempty"`
|
||
|
|
Recipient *struct {
|
||
|
|
Name string `json:"name,omitempty"`
|
||
|
|
Id string `json:"id"`
|
||
|
|
} `json:"recipient,omitempty"`
|
||
|
|
Entities []interface{} `json:"entities,omitempty"`
|
||
|
|
ChannelData *struct {
|
||
|
|
TeamsChannelId string `json:"teamsChannelId,omitempty"`
|
||
|
|
TeamsTeamId string `json:"teamsTeamId,omitempty"`
|
||
|
|
Channel *struct {
|
||
|
|
Id string `json:"id"`
|
||
|
|
} `json:"channel,omitempty"`
|
||
|
|
Team *struct {
|
||
|
|
Id string `json:"id"`
|
||
|
|
} `json:"team,omitempty"`
|
||
|
|
Tenant *struct {
|
||
|
|
Id string `json:"id,omitempty"`
|
||
|
|
} `json:"tenant,omitempty"`
|
||
|
|
} `json:"channelData,omitempty"`
|
||
|
|
ReplyToId string `json:"replyToId,omitempty"`
|
||
|
|
Value map[string]string `json:"value,omitempty"`
|
||
|
|
Locale string `json:"Locale,omitempty"`
|
||
|
|
LocalTimezone string `json:"LocalTimezone,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
type AdaptiveCard struct {
|
||
|
|
Body []interface{}
|
||
|
|
Actions []interface{}
|
||
|
|
}
|
||
|
|
|
||
|
|
type tempMessageActivity struct {
|
||
|
|
MessageActivity
|
||
|
|
RawText string `json:"text,omitempty"`
|
||
|
|
}
|
||
|
|
|
||
|
|
var botFrameworkAuth = accessToken{}
|
||
|
|
|
||
|
|
func ParseMessageActivity(src []byte) (MessageActivity, error) {
|
||
|
|
var message tempMessageActivity
|
||
|
|
err := json.Unmarshal(src, &message)
|
||
|
|
if err != nil {
|
||
|
|
return MessageActivity{}, err
|
||
|
|
}
|
||
|
|
|
||
|
|
for len(message.RawText) > 0 {
|
||
|
|
s := strings.Index(message.RawText, "<at>")
|
||
|
|
if s < 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
message.Message += message.RawText[:s]
|
||
|
|
message.RawText = message.RawText[s+4:]
|
||
|
|
e := strings.Index(message.RawText, "</at>")
|
||
|
|
if e < 0 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
mention := message.RawText[:e]
|
||
|
|
message.RawText = message.RawText[e+5:]
|
||
|
|
message.Mentions = append(message.Mentions, mention)
|
||
|
|
}
|
||
|
|
message.Message += strings.Trim(message.RawText, " ")
|
||
|
|
message.Message = strings.TrimSpace(message.Message)
|
||
|
|
if !strings.HasSuffix(message.ServiceUrl, "/") {
|
||
|
|
message.ServiceUrl += "/"
|
||
|
|
}
|
||
|
|
return message.MessageActivity, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func sendRequireMessage(requrl string, method string, stream io.Reader) (string, error) {
|
||
|
|
req, err := http.NewRequest(method, requrl, stream)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
auth, err := botFrameworkAuth.getAuthoizationToken()
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
req.Header.Set("Authorization", auth)
|
||
|
|
req.Header.Set("Content-type", "application/json")
|
||
|
|
resp, err := http.DefaultClient.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
bt, _ := io.ReadAll(resp.Body)
|
||
|
|
var resultdoc map[string]interface{}
|
||
|
|
json.Unmarshal(bt, &resultdoc)
|
||
|
|
if len(resultdoc) > 0 {
|
||
|
|
if idraw, ok := resultdoc["id"]; ok {
|
||
|
|
return idraw.(string), nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
func putMessage(requrl string, stream io.Reader) (string, error) {
|
||
|
|
return sendRequireMessage(requrl, "PUT", stream)
|
||
|
|
}
|
||
|
|
|
||
|
|
func postMessage(requrl string, stream io.Reader) (string, error) {
|
||
|
|
return sendRequireMessage(requrl, "POST", stream)
|
||
|
|
}
|
||
|
|
|
||
|
|
func unescapeUnicodeCharactersInJSON(src []byte) (string, error) {
|
||
|
|
str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(src)), `\\u`, `\u`, -1))
|
||
|
|
if err != nil {
|
||
|
|
return "", err
|
||
|
|
}
|
||
|
|
return str, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageActivity) SendStreamOnChannel(reader io.Reader) (string, error) {
|
||
|
|
serviceUrl := m.ServiceUrl
|
||
|
|
|
||
|
|
// mention 없이 여기 왔으면 private 채팅이다. reply를 해야함
|
||
|
|
var conversationId string
|
||
|
|
if m.ChannelData.Channel == nil {
|
||
|
|
conversationId = m.Conversation.Id
|
||
|
|
} else {
|
||
|
|
conversationId = m.ChannelData.Channel.Id
|
||
|
|
}
|
||
|
|
|
||
|
|
requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||
|
|
|
||
|
|
return postMessage(requrl, reader)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageActivity) UploadFile(attachment AttachmentData) (string, error) {
|
||
|
|
// POST /v3/conversations/{conversationId}/attachments
|
||
|
|
// Request body An AttachmentData object.
|
||
|
|
// Returns A ResourceResponse object. The id property specifies the attachment ID that can be used with the Get Attachment Info operation and the Get Attachment operation.
|
||
|
|
serviceUrl := m.ServiceUrl
|
||
|
|
conversationId := m.Conversation.Id
|
||
|
|
bt, _ := json.Marshal(attachment)
|
||
|
|
|
||
|
|
requrl := fmt.Sprintf("%sv3/conversations/%s/attachments", serviceUrl, conversationId)
|
||
|
|
return postMessage(requrl, bytes.NewReader([]byte(bt)))
|
||
|
|
}
|
||
|
|
|
||
|
|
func makeAttachmentDocs(attachments []AdaptiveCard) []interface{} {
|
||
|
|
var attachconv []interface{}
|
||
|
|
for _, attachment := range attachments {
|
||
|
|
attachconv = append(attachconv, map[string]interface{}{
|
||
|
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
||
|
|
"content": map[string]interface{}{
|
||
|
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||
|
|
"type": "AdaptiveCard",
|
||
|
|
"version": "1.3",
|
||
|
|
"body": attachment.Body,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return attachconv
|
||
|
|
}
|
||
|
|
|
||
|
|
// func (m *MessageActivity) update(activityId string, message string, attachments ...AdaptiveCard) (string, error) {
|
||
|
|
// serviceUrl := m.ServiceUrl
|
||
|
|
// conversationId := m.Conversation.Id
|
||
|
|
|
||
|
|
// attachconv := makeAttachmentDocs(attachments)
|
||
|
|
// activity := map[string]interface{}{
|
||
|
|
// "type": "message",
|
||
|
|
// "text": strings.ReplaceAll(message, `"`, `\"`),
|
||
|
|
// }
|
||
|
|
// if len(attachconv) > 0 {
|
||
|
|
// activity["attachments"] = attachconv
|
||
|
|
// }
|
||
|
|
|
||
|
|
// reqdoc, _ := json.Marshal(activity)
|
||
|
|
// unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||
|
|
|
||
|
|
// requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||
|
|
// return putMessage(requrl, bytes.NewReader([]byte(unq)))
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) reply(message string, mention bool, attachments ...AdaptiveCard) (string, error) {
|
||
|
|
// serviceUrl := m.ServiceUrl
|
||
|
|
// conversationId := m.Conversation.Id
|
||
|
|
|
||
|
|
// attachconv := makeAttachmentDocs(attachments)
|
||
|
|
|
||
|
|
// var activity map[string]interface{}
|
||
|
|
// if mention {
|
||
|
|
// mentionText := fmt.Sprintf(`<at>%s</at>`, m.From.Name)
|
||
|
|
// activity = map[string]interface{}{
|
||
|
|
// "type": "message",
|
||
|
|
// "text": mentionText + " " + strings.ReplaceAll(message, `"`, `\"`),
|
||
|
|
// "entities": []interface{}{
|
||
|
|
// map[string]interface{}{
|
||
|
|
// "mentioned": m.From,
|
||
|
|
// "text": mentionText,
|
||
|
|
// "type": "mention",
|
||
|
|
// },
|
||
|
|
// },
|
||
|
|
// }
|
||
|
|
// } else {
|
||
|
|
// activity = map[string]interface{}{
|
||
|
|
// "type": "message",
|
||
|
|
// "text": strings.ReplaceAll(message, `"`, `\"`),
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
|
||
|
|
// activity["replyToId"] = m.Id
|
||
|
|
// if len(attachconv) > 0 {
|
||
|
|
// activity["attachments"] = attachconv
|
||
|
|
// }
|
||
|
|
|
||
|
|
// reqdoc, _ := json.Marshal(activity)
|
||
|
|
// unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||
|
|
|
||
|
|
// requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||
|
|
// return postMessage(requrl, bytes.NewReader([]byte(unq)))
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) ReplyWithMentionf(format string, val ...interface{}) (string, error) {
|
||
|
|
// return m.ReplyWithMention(fmt.Sprintf(format, val...))
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) ReplyWithMention(message string, attachments ...AdaptiveCard) (string, error) {
|
||
|
|
// return m.reply(message, true, attachments...)
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) Reply(message string, attachments ...AdaptiveCard) (string, error) {
|
||
|
|
// return m.reply(message, false, attachments...)
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) Replyf(format string, val ...interface{}) (string, error) {
|
||
|
|
// return m.Reply(fmt.Sprintf(format, val...))
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) Update(activityId string, message string, attachments ...AdaptiveCard) (string, error) {
|
||
|
|
// return m.update(activityId, message, attachments...)
|
||
|
|
// }
|
||
|
|
|
||
|
|
// func (m *MessageActivity) Updatef(activityId string, format string, val ...interface{}) (string, error) {
|
||
|
|
// return m.Update(activityId, fmt.Sprintf(format, val...))
|
||
|
|
// }
|
||
|
|
|
||
|
|
type MessageWrap struct {
|
||
|
|
*MessageActivity
|
||
|
|
Prefix string
|
||
|
|
Mention bool
|
||
|
|
Attachments []AdaptiveCard
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageActivity) MakeWrap() *MessageWrap {
|
||
|
|
return &MessageWrap{MessageActivity: m}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) WithPrefix(prefix string) *MessageWrap {
|
||
|
|
m.Prefix = prefix
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) WithMention() *MessageWrap {
|
||
|
|
m.Mention = true
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) WithAttachments(attachments ...AdaptiveCard) *MessageWrap {
|
||
|
|
m.Attachments = attachments
|
||
|
|
return m
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) Reply(text string) (string, error) {
|
||
|
|
serviceUrl := m.ServiceUrl
|
||
|
|
conversationId := m.Conversation.Id
|
||
|
|
attachconv := makeAttachmentDocs(m.Attachments)
|
||
|
|
|
||
|
|
var activity map[string]interface{}
|
||
|
|
if m.Mention {
|
||
|
|
var mentionText string
|
||
|
|
if len(m.Prefix) > 0 {
|
||
|
|
mentionText = fmt.Sprintf(`**[%s]** <at>%s</at>`, m.Prefix, m.From.Name)
|
||
|
|
} else {
|
||
|
|
mentionText = fmt.Sprintf(`<at>%s</at>`, m.From.Name)
|
||
|
|
}
|
||
|
|
activity = map[string]interface{}{
|
||
|
|
"type": "message",
|
||
|
|
"text": mentionText + " " + strings.ReplaceAll(text, `"`, `\"`),
|
||
|
|
"entities": []interface{}{
|
||
|
|
map[string]interface{}{
|
||
|
|
"mentioned": m.From,
|
||
|
|
"text": mentionText,
|
||
|
|
"type": "mention",
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
if len(m.Prefix) > 0 {
|
||
|
|
text = fmt.Sprintf("**[%s]** %s", m.Prefix, text)
|
||
|
|
}
|
||
|
|
|
||
|
|
activity = map[string]interface{}{
|
||
|
|
"type": "message",
|
||
|
|
"text": strings.ReplaceAll(text, `"`, `\"`),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
activity["replyToId"] = m.Id
|
||
|
|
if len(attachconv) > 0 {
|
||
|
|
activity["attachments"] = attachconv
|
||
|
|
}
|
||
|
|
|
||
|
|
activity["from"] = m.Recipient
|
||
|
|
activity["channelId"] = m.ChannelId
|
||
|
|
reqdoc, _ := json.Marshal(activity)
|
||
|
|
unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||
|
|
|
||
|
|
requrl := fmt.Sprintf("%sv3/conversations/%s/activities", serviceUrl, conversationId)
|
||
|
|
return postMessage(requrl, bytes.NewReader([]byte(unq)))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) Replyf(format string, args ...interface{}) (string, error) {
|
||
|
|
return m.Reply(fmt.Sprintf(format, args...))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageActivity) Update(reader io.Reader) {
|
||
|
|
serviceUrl := m.ServiceUrl
|
||
|
|
conversationId := m.Conversation.Id
|
||
|
|
activityId := m.ReplyToId
|
||
|
|
|
||
|
|
requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||
|
|
putMessage(requrl, reader)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) Update(activityId string, text string) (string, error) {
|
||
|
|
serviceUrl := m.ServiceUrl
|
||
|
|
conversationId := m.Conversation.Id
|
||
|
|
if len(m.Prefix) > 0 {
|
||
|
|
text = fmt.Sprintf("**[%s]** %s", m.Prefix, text)
|
||
|
|
}
|
||
|
|
attachconv := makeAttachmentDocs(m.Attachments)
|
||
|
|
activity := map[string]interface{}{
|
||
|
|
"type": "message",
|
||
|
|
"text": strings.ReplaceAll(text, `"`, `\"`),
|
||
|
|
}
|
||
|
|
if len(attachconv) > 0 {
|
||
|
|
activity["attachments"] = attachconv
|
||
|
|
}
|
||
|
|
|
||
|
|
reqdoc, _ := json.Marshal(activity)
|
||
|
|
unq, _ := unescapeUnicodeCharactersInJSON(reqdoc)
|
||
|
|
|
||
|
|
requrl := fmt.Sprintf("%sv3/conversations/%s/activities/%s", serviceUrl, conversationId, activityId)
|
||
|
|
return putMessage(requrl, bytes.NewReader([]byte(unq)))
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *MessageWrap) Updatef(activityId string, format string, args ...interface{}) (string, error) {
|
||
|
|
return m.Update(activityId, fmt.Sprintf(format, args...))
|
||
|
|
}
|
||
|
|
|
||
|
|
type MessageReplyCache struct {
|
||
|
|
sourceActivity *MessageActivity
|
||
|
|
ReplyWrap *MessageWrap
|
||
|
|
Replyaid string
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *MessageReplyCache) Serialize() string {
|
||
|
|
bt, err := json.Marshal(c)
|
||
|
|
if err != nil {
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
linkfilename := fmt.Sprintf("updatelink_%s.json", time.Now().Format("2006-01-02T15-04-05"))
|
||
|
|
if os.WriteFile(linkfilename, bt, 0666) == nil {
|
||
|
|
return linkfilename
|
||
|
|
}
|
||
|
|
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
|
||
|
|
func DeserializeMessageReplyCache(filename string) *MessageReplyCache {
|
||
|
|
bt, err := os.ReadFile(filename)
|
||
|
|
if err != nil {
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
var out MessageReplyCache
|
||
|
|
if json.Unmarshal(bt, &out) == nil {
|
||
|
|
return &out
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func MakeMessageReplyCache(src *MessageActivity) MessageReplyCache {
|
||
|
|
return MessageReplyCache{
|
||
|
|
sourceActivity: src,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ch *MessageReplyCache) SourceActivity() *MessageActivity {
|
||
|
|
return ch.sourceActivity
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ch *MessageReplyCache) Reply(text string) {
|
||
|
|
if ch.ReplyWrap == nil {
|
||
|
|
hostname, _ := os.Hostname()
|
||
|
|
ch.ReplyWrap = ch.sourceActivity.MakeWrap().WithPrefix(hostname)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(ch.Replyaid) == 0 {
|
||
|
|
ch.Replyaid, _ = ch.ReplyWrap.Reply(text)
|
||
|
|
} else {
|
||
|
|
ch.ReplyWrap.Update(ch.Replyaid, text)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (ch *MessageReplyCache) Replyf(format string, args ...interface{}) {
|
||
|
|
ch.Reply(fmt.Sprintf(format, args...))
|
||
|
|
}
|