Files
gocommon/azure/bot.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...))
}