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, "") if s < 0 { break } message.Message += message.RawText[:s] message.RawText = message.RawText[s+4:] e := strings.Index(message.RawText, "") 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(`%s`, 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]** %s`, m.Prefix, m.From.Name) } else { mentionText = fmt.Sprintf(`%s`, 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...)) }