mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
The server module named github.com/hanzoai/team/server/v8 — a path that resolves to the retired workspace's repository, not this one — while server/public still published github.com/mattermost/mattermost/server/public. Both are github.com/hanzoteam/server/... now, which is where this code actually lives. That second half is what made the binary unbootable. Upstream's public path stayed claimed by upstream, mattermost-plugin-ai imported it, and the build linked public/model twice; public/plugin's gob.Register then ran twice under one name and panicked at init, before main. Reverting the rename traded the brand for a working binary. Rebranding the plugin alongside it needs no such trade: the agents fork is github.com/hanzoteam/agents/v2 against our public module, so no module named mattermost/mattermost is in the graph at all. Measured: `go list -deps ./cmd/mattermost` names exactly one public/model, and it is ours.
797 lines
20 KiB
Go
797 lines
20 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"strings"
|
|
|
|
"github.com/hanzoteam/server/server/public/shared/markdown"
|
|
)
|
|
|
|
func appendHumanReadableInteractiveStrings(o *Post, out []string) []string {
|
|
props := o.GetProps()
|
|
if props == nil {
|
|
return out
|
|
}
|
|
if raw, ok := props[PostPropsMmBlocks]; ok {
|
|
out = appendHumanStringsFromMmBlocks(raw, out)
|
|
}
|
|
if raw, ok := props[PostPropsBlockKitBlocks]; ok {
|
|
out = appendHumanStringsFromBlockKitTree(raw, out)
|
|
}
|
|
if raw, ok := props[PostPropsAdaptiveCards]; ok {
|
|
out = appendHumanStringsFromAdaptiveCardsTree(raw, out)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func interactivePropJSONArray(raw any) ([]any, bool) {
|
|
switch v := raw.(type) {
|
|
case []any:
|
|
return v, true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
func interactivePropJSONArrayNonEmpty(raw any) bool {
|
|
arr, ok := interactivePropJSONArray(raw)
|
|
return ok && len(arr) > 0
|
|
}
|
|
|
|
func appendHumanStringsFromMmBlocks(raw any, out []string) []string {
|
|
blocks, ok := interactivePropJSONArray(raw)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, b := range blocks {
|
|
m, ok := b.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = appendHumanStringsFromMmBlockMap(m, out)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendHumanStringsFromMmBlockMap(m map[string]any, out []string) []string {
|
|
typ, _ := m["type"].(string)
|
|
switch typ {
|
|
case "text":
|
|
if s, ok := m["text"].(string); ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, s)
|
|
}
|
|
case "container":
|
|
out = appendHumanStringsFromMmBlocksArray(m["content"], out)
|
|
case "collapsible":
|
|
out = appendHumanStringsFromMmBlocksArray(m["header"], out)
|
|
out = appendHumanStringsFromMmBlocksArray(m["content"], out)
|
|
case "column_set":
|
|
if cols, ok := m["columns"].([]any); ok {
|
|
for _, col := range cols {
|
|
cm, ok := col.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
colItems, ok := cm["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = appendHumanStringsFromMmBlocksArray(colItems, out)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendHumanStringsFromMmBlocksArray(raw any, out []string) []string {
|
|
arr, ok := interactivePropJSONArray(raw)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, el := range arr {
|
|
m, ok := el.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = appendHumanStringsFromMmBlockMap(m, out)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendHumanStringsFromBlockKitTree(v any, out []string) []string {
|
|
blocks, ok := v.([]any)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, block := range blocks {
|
|
blockMap, ok := block.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
typ, _ := blockMap["type"].(string)
|
|
switch typ {
|
|
case "markdown":
|
|
if s, ok := blockMap["text"].(string); ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, s)
|
|
}
|
|
case "section":
|
|
if textBlock, ok := blockMap["text"].(map[string]any); ok {
|
|
if s, ok := textBlock["text"].(string); ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, s)
|
|
}
|
|
}
|
|
if fields, ok := blockMap["fields"].([]any); ok {
|
|
for _, field := range fields {
|
|
fieldMap, ok := field.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
fieldText, ok := fieldMap["text"].(string)
|
|
if ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, fieldText)
|
|
}
|
|
}
|
|
}
|
|
case "header":
|
|
if textBlock, ok := blockMap["text"].(map[string]any); ok {
|
|
if s, ok := textBlock["text"].(string); ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, s)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendHumanStringsFromAdaptiveCardsTree(v any, out []string) []string {
|
|
cards, ok := v.([]any)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, card := range cards {
|
|
cardMap, ok := card.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
body, ok := cardMap["body"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, item := range body {
|
|
out = appendHumanStringsFromAdaptiveCardsItem(item, out)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendHumanStringsFromAdaptiveCardsItem(item any, out []string) []string {
|
|
itemMap, ok := item.(map[string]any)
|
|
if !ok {
|
|
return out
|
|
}
|
|
typ, _ := itemMap["type"].(string)
|
|
switch typ {
|
|
case "TextBlock":
|
|
if s, ok := itemMap["text"].(string); ok {
|
|
out = appendNonWhitespaceOnlyMessage(out, s)
|
|
}
|
|
case "Container":
|
|
if items, ok := itemMap["items"].([]any); ok {
|
|
for _, item := range items {
|
|
out = appendHumanStringsFromAdaptiveCardsItem(item, out)
|
|
}
|
|
}
|
|
case "ColumnSet":
|
|
if columns, ok := itemMap["columns"].([]any); ok {
|
|
for _, column := range columns {
|
|
columnMap, ok := column.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
itemMap, ok := columnMap["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, item := range itemMap {
|
|
out = appendHumanStringsFromAdaptiveCardsItem(item, out)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendMmBlockImageURLs(out []string, raw any) []string {
|
|
blocks, ok := interactivePropJSONArray(raw)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, b := range blocks {
|
|
m, ok := b.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = appendMmBlockMapImageURLs(out, m)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendMmBlockMapImageURLs(out []string, m map[string]any) []string {
|
|
typ, _ := m["type"].(string)
|
|
switch typ {
|
|
case "image":
|
|
if u, ok := m["url"].(string); ok {
|
|
out = append(out, u)
|
|
}
|
|
case "container":
|
|
out = appendMmBlocksArrayImageURLs(out, m["content"])
|
|
case "collapsible":
|
|
out = appendMmBlocksArrayImageURLs(out, m["header"])
|
|
out = appendMmBlocksArrayImageURLs(out, m["content"])
|
|
case "column_set":
|
|
if cols, ok := m["columns"].([]any); ok {
|
|
for _, col := range cols {
|
|
cm, ok := col.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
colItems, ok := cm["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, item := range colItems {
|
|
out = appendMmBlocksArrayImageURLs(out, item)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendMmBlocksArrayImageURLs(out []string, raw any) []string {
|
|
arr, ok := interactivePropJSONArray(raw)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, el := range arr {
|
|
m, ok := el.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
out = appendMmBlockMapImageURLs(out, m)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendBlockKitImageURLs(out []string, v any) []string {
|
|
blocks, ok := interactivePropJSONArray(v)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, block := range blocks {
|
|
blockMap, ok := block.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
typ, _ := blockMap["type"].(string)
|
|
switch typ {
|
|
case "section":
|
|
if accessory, ok := blockMap["accessory"].(map[string]any); ok {
|
|
accessoryType, _ := accessory["type"].(string)
|
|
if accessoryType != "image" {
|
|
continue
|
|
}
|
|
|
|
if u, ok := accessory["image_url"].(string); ok {
|
|
out = append(out, u)
|
|
}
|
|
}
|
|
case "image":
|
|
if u, ok := blockMap["image_url"].(string); ok {
|
|
out = append(out, u)
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendAdaptiveCardImageURLs(out []string, v any) []string {
|
|
cards, ok := interactivePropJSONArray(v)
|
|
if !ok {
|
|
return out
|
|
}
|
|
for _, card := range cards {
|
|
cardMap, ok := card.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
body, ok := cardMap["body"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, item := range body {
|
|
out = appendAdaptiveCardImageURLsFromItem(out, item)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendAdaptiveCardImageURLsFromItem(out []string, item any) []string {
|
|
itemMap, ok := item.(map[string]any)
|
|
if !ok {
|
|
return out
|
|
}
|
|
typ, _ := itemMap["type"].(string)
|
|
switch typ {
|
|
case "Container":
|
|
if items, ok := itemMap["items"].([]any); ok {
|
|
for _, item := range items {
|
|
out = appendAdaptiveCardImageURLsFromItem(out, item)
|
|
}
|
|
}
|
|
case "ColumnSet":
|
|
if columns, ok := itemMap["columns"].([]any); ok {
|
|
for _, column := range columns {
|
|
columnMap, ok := column.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
items, ok := columnMap["items"].([]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
for _, item := range items {
|
|
out = appendAdaptiveCardImageURLsFromItem(out, item)
|
|
}
|
|
}
|
|
}
|
|
case "Image":
|
|
if u, ok := itemMap["url"].(string); ok {
|
|
out = append(out, u)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendAttachmentsImageURLs(out []string, attachments []*MessageAttachment) []string {
|
|
for _, attachment := range attachments {
|
|
if attachment == nil {
|
|
continue
|
|
}
|
|
if attachment.ImageURL != "" {
|
|
out = append(out, attachment.ImageURL)
|
|
}
|
|
if attachment.ThumbURL != "" {
|
|
out = append(out, attachment.ThumbURL)
|
|
}
|
|
if attachment.AuthorIcon != "" {
|
|
out = append(out, attachment.AuthorIcon)
|
|
}
|
|
if attachment.FooterIcon != "" {
|
|
out = append(out, attachment.FooterIcon)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
const mmactionScheme = "mmaction://"
|
|
|
|
// appendMmactionIDsFromText appends action ids from mmaction:// markdown links only.
|
|
// Inline code, fenced code blocks, and other non-link text are ignored (same approach as mentions).
|
|
func appendMmactionIDsFromText(ids map[string]struct{}, text string) map[string]struct{} {
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
markdown.Inspect(text, func(blockOrInline any) bool {
|
|
switch v := blockOrInline.(type) {
|
|
case *markdown.InlineLink:
|
|
ids = appendMmactionIDFromURL(ids, v.Destination())
|
|
case *markdown.ReferenceLink:
|
|
if v.ReferenceDefinition != nil {
|
|
ids = appendMmactionIDFromURL(ids, v.ReferenceDefinition.Destination())
|
|
}
|
|
case *markdown.Autolink:
|
|
ids = appendMmactionIDFromURL(ids, v.Destination())
|
|
}
|
|
return true
|
|
})
|
|
return ids
|
|
}
|
|
|
|
func appendMmactionIDFromURL(ids map[string]struct{}, url string) map[string]struct{} {
|
|
if !strings.HasPrefix(url, mmactionScheme) {
|
|
return ids
|
|
}
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
withoutScheme := url[len(mmactionScheme):]
|
|
actionID := withoutScheme
|
|
if i := strings.IndexAny(withoutScheme, "/?#"); i >= 0 {
|
|
actionID = withoutScheme[:i]
|
|
}
|
|
if actionID != "" && mmBlocksActionIDRegex.MatchString(actionID) {
|
|
ids[actionID] = struct{}{}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func mergeActionIDs(into, from map[string]struct{}) {
|
|
maps.Copy(into, from)
|
|
}
|
|
|
|
func interactiveControlDisabled(m map[string]any) bool {
|
|
disabled, ok := m["disabled"].(bool)
|
|
return ok && disabled
|
|
}
|
|
|
|
func appendMmBlockActionIDsFromMap(ids map[string]struct{}, m map[string]any) map[string]struct{} {
|
|
typ, _ := m["type"].(string)
|
|
switch typ {
|
|
case "text":
|
|
if s, ok := m["text"].(string); ok {
|
|
ids = appendMmactionIDsFromText(ids, s)
|
|
}
|
|
case "button", "static_select":
|
|
if interactiveControlDisabled(m) {
|
|
break
|
|
}
|
|
if id, ok := m["action_id"].(string); ok && id != "" {
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
ids[id] = struct{}{}
|
|
}
|
|
case "container":
|
|
ids = appendMmBlockActionIDsFromArray(ids, m["content"])
|
|
case "collapsible":
|
|
ids = appendMmBlockActionIDsFromArray(ids, m["header"])
|
|
ids = appendMmBlockActionIDsFromArray(ids, m["content"])
|
|
case "column_set":
|
|
if cols, ok := m["columns"].([]any); ok {
|
|
for _, col := range cols {
|
|
cm, ok := col.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
colTyp, _ := cm["type"].(string)
|
|
if colTyp != "column" {
|
|
continue
|
|
}
|
|
ids = appendMmBlockActionIDsFromArray(ids, cm["items"])
|
|
}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendMmBlockActionIDsFromArray(ids map[string]struct{}, raw any) map[string]struct{} {
|
|
arr, ok := interactivePropJSONArray(raw)
|
|
if !ok {
|
|
return ids
|
|
}
|
|
for _, el := range arr {
|
|
m, ok := el.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ids = appendMmBlockActionIDsFromMap(ids, m)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectMmBlockActionIDs returns action_id values referenced by interactive mm_blocks controls.
|
|
func CollectMmBlockActionIDs(blocks []any) map[string]struct{} {
|
|
var ids map[string]struct{}
|
|
for _, b := range blocks {
|
|
m, ok := b.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ids = appendMmBlockActionIDsFromMap(ids, m)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendBlockKitTextMmaction(ids map[string]struct{}, raw any) map[string]struct{} {
|
|
if raw == nil {
|
|
return ids
|
|
}
|
|
switch v := raw.(type) {
|
|
case string:
|
|
return appendMmactionIDsFromText(ids, v)
|
|
case map[string]any:
|
|
if s, ok := v["text"].(string); ok {
|
|
return appendMmactionIDsFromText(ids, s)
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendBlockKitAccessory(ids map[string]struct{}, accessory map[string]any) map[string]struct{} {
|
|
typ, _ := accessory["type"].(string)
|
|
switch typ {
|
|
case "button", "static_select":
|
|
if interactiveControlDisabled(accessory) {
|
|
return ids
|
|
}
|
|
if id, ok := accessory["action_id"].(string); ok && id != "" {
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
ids[id] = struct{}{}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendBlockKitActionElement(ids map[string]struct{}, el any) map[string]struct{} {
|
|
e, ok := el.(map[string]any)
|
|
if !ok {
|
|
return ids
|
|
}
|
|
typ, _ := e["type"].(string)
|
|
switch typ {
|
|
case "button", "static_select":
|
|
if interactiveControlDisabled(e) {
|
|
return ids
|
|
}
|
|
if id, ok := e["action_id"].(string); ok && id != "" {
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
ids[id] = struct{}{}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendBlockKitActionIDsFromBlock(ids map[string]struct{}, m map[string]any) map[string]struct{} {
|
|
typ, _ := m["type"].(string)
|
|
switch typ {
|
|
case "actions":
|
|
if elements, ok := m["elements"].([]any); ok {
|
|
for _, el := range elements {
|
|
ids = appendBlockKitActionElement(ids, el)
|
|
}
|
|
}
|
|
case "section":
|
|
ids = appendBlockKitTextMmaction(ids, m["text"])
|
|
if accessory, ok := m["accessory"].(map[string]any); ok {
|
|
ids = appendBlockKitAccessory(ids, accessory)
|
|
}
|
|
if fields, ok := m["fields"].([]any); ok {
|
|
for _, field := range fields {
|
|
ids = appendBlockKitTextMmaction(ids, field)
|
|
}
|
|
}
|
|
case "markdown":
|
|
ids = appendBlockKitTextMmaction(ids, m["text"])
|
|
case "header":
|
|
ids = appendBlockKitTextMmaction(ids, m["text"])
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectBlockKitActionIDs returns action_id values from Block Kit blocks (props.blocks).
|
|
func CollectBlockKitActionIDs(blocks []any) map[string]struct{} {
|
|
var ids map[string]struct{}
|
|
for _, b := range blocks {
|
|
m, ok := b.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
ids = appendBlockKitActionIDsFromBlock(ids, m)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendAdaptiveCardActionElement(ids map[string]struct{}, action any) map[string]struct{} {
|
|
ac, ok := action.(map[string]any)
|
|
if !ok {
|
|
return ids
|
|
}
|
|
typ, _ := ac["type"].(string)
|
|
if typ == "Action.Submit" {
|
|
if id, ok := ac["id"].(string); ok && id != "" {
|
|
if ids == nil {
|
|
ids = make(map[string]struct{})
|
|
}
|
|
ids[id] = struct{}{}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
func appendAdaptiveCardActionIDsFromItem(ids map[string]struct{}, item any) map[string]struct{} {
|
|
itemMap, ok := item.(map[string]any)
|
|
if !ok {
|
|
return ids
|
|
}
|
|
typ, _ := itemMap["type"].(string)
|
|
switch typ {
|
|
case "TextBlock":
|
|
if s, ok := itemMap["text"].(string); ok {
|
|
ids = appendMmactionIDsFromText(ids, s)
|
|
}
|
|
case "Container":
|
|
if items, ok := itemMap["items"].([]any); ok {
|
|
for _, nested := range items {
|
|
ids = appendAdaptiveCardActionIDsFromItem(ids, nested)
|
|
}
|
|
}
|
|
case "ColumnSet":
|
|
if columns, ok := itemMap["columns"].([]any); ok {
|
|
for _, column := range columns {
|
|
columnMap, ok := column.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if items, ok := columnMap["items"].([]any); ok {
|
|
for _, nested := range items {
|
|
ids = appendAdaptiveCardActionIDsFromItem(ids, nested)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case "ActionSet":
|
|
if actions, ok := itemMap["actions"].([]any); ok {
|
|
for _, action := range actions {
|
|
ids = appendAdaptiveCardActionElement(ids, action)
|
|
}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectAdaptiveCardActionIDs returns action ids from Adaptive Cards (props.cards).
|
|
func CollectAdaptiveCardActionIDs(cards []any) map[string]struct{} {
|
|
var ids map[string]struct{}
|
|
for _, card := range cards {
|
|
cardMap, ok := card.(map[string]any)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if body, ok := cardMap["body"].([]any); ok {
|
|
for _, item := range body {
|
|
ids = appendAdaptiveCardActionIDsFromItem(ids, item)
|
|
}
|
|
}
|
|
if actions, ok := cardMap["actions"].([]any); ok {
|
|
for _, action := range actions {
|
|
ids = appendAdaptiveCardActionElement(ids, action)
|
|
}
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectInteractiveActionIDs returns action ids referenced by interactive post props.
|
|
func CollectInteractiveActionIDs(props map[string]any) map[string]struct{} {
|
|
ids := make(map[string]struct{})
|
|
if props == nil {
|
|
return ids
|
|
}
|
|
if raw, ok := props[PostPropsMmBlocks]; ok {
|
|
if blocks, ok := interactivePropJSONArray(raw); ok {
|
|
mergeActionIDs(ids, CollectMmBlockActionIDs(blocks))
|
|
}
|
|
}
|
|
if raw, ok := props[PostPropsBlockKitBlocks]; ok {
|
|
if blocks, ok := interactivePropJSONArray(raw); ok {
|
|
mergeActionIDs(ids, CollectBlockKitActionIDs(blocks))
|
|
}
|
|
}
|
|
if raw, ok := props[PostPropsAdaptiveCards]; ok {
|
|
if cards, ok := interactivePropJSONArray(raw); ok {
|
|
mergeActionIDs(ids, CollectAdaptiveCardActionIDs(cards))
|
|
}
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectInteractiveActionIDsFromPost includes mmaction:// links in the post message.
|
|
func CollectInteractiveActionIDsFromPost(o *Post) map[string]struct{} {
|
|
ids := CollectInteractiveActionIDs(o.GetProps())
|
|
if o.Message != "" {
|
|
ids = appendMmactionIDsFromText(ids, o.Message)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
// CollectMmactionIDsFromText returns action ids from mmaction:// links in a string.
|
|
func CollectMmactionIDsFromText(text string) map[string]struct{} {
|
|
return appendMmactionIDsFromText(nil, text)
|
|
}
|
|
|
|
// SubsetMmBlocksActions returns registry entries referenced by actionIDs.
|
|
func SubsetMmBlocksActions(allActions any, actionIDs map[string]struct{}) map[string]any {
|
|
if allActions == nil || len(actionIDs) == 0 {
|
|
return nil
|
|
}
|
|
top, ok := allActions.(map[string]any)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
out := make(map[string]any, len(actionIDs))
|
|
for id := range actionIDs {
|
|
if entry, ok := top[id]; ok {
|
|
out[id] = entry
|
|
}
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
// RefreshInteractiveActionsOnPost sets mm_blocks_actions to the subset needed by this post's interactive content.
|
|
func RefreshInteractiveActionsOnPost(o *Post, allActions any) {
|
|
ids := CollectInteractiveActionIDsFromPost(o)
|
|
props := o.GetProps()
|
|
if props == nil {
|
|
props = make(map[string]any)
|
|
}
|
|
if len(ids) == 0 {
|
|
delete(props, PostPropsMmBlocksActions)
|
|
} else if subset := SubsetMmBlocksActions(allActions, ids); len(subset) > 0 {
|
|
props[PostPropsMmBlocksActions] = subset
|
|
} else {
|
|
delete(props, PostPropsMmBlocksActions)
|
|
}
|
|
o.SetProps(props)
|
|
}
|
|
|
|
// ApplyMmBlocksWithActionsToProps sets mm_blocks and refreshes mm_blocks_actions for the props payload.
|
|
// When props is nil, a new map is allocated and returned.
|
|
func ApplyMmBlocksWithActionsToProps(props map[string]any, blocks []any, allActions any) StringInterface {
|
|
if props == nil {
|
|
props = make(map[string]any)
|
|
}
|
|
props[PostPropsMmBlocks] = blocks
|
|
RefreshInteractiveActionsOnPost(&Post{Props: props}, allActions)
|
|
return props
|
|
}
|
|
|
|
// validateMmBlocksActionsPairing requires mm_blocks_actions to define exactly the actions
|
|
// referenced by mm_blocks, blocks, cards, and mmaction:// links in the post message.
|
|
func validateMmBlocksActionsPairing(o *Post, actions map[string]any) error {
|
|
referenced := CollectInteractiveActionIDsFromPost(o)
|
|
if len(referenced) == 0 {
|
|
if len(actions) > 0 {
|
|
return fmt.Errorf("mm_blocks_actions must only define actions referenced by interactive content")
|
|
}
|
|
return nil
|
|
}
|
|
for id := range referenced {
|
|
if _, ok := actions[id]; !ok {
|
|
return fmt.Errorf("mm_blocks_actions missing entry for action_id %q", id)
|
|
}
|
|
}
|
|
for key := range actions {
|
|
if _, ok := referenced[key]; !ok {
|
|
return fmt.Errorf("mm_blocks_actions entry %q is not referenced by interactive content", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateInteractiveActionsForWebhook checks interactive payloads and mm_blocks_actions are paired.
|
|
func ValidateInteractiveActionsForWebhook(o *Post) error {
|
|
return ValidateMmBlocksActions(o)
|
|
}
|
|
|
|
// ValidateMmBlocksActionsForWebhook validates mm_blocks-only webhook payloads (legacy helper).
|
|
func ValidateMmBlocksActionsForWebhook(blocks []any, actions any) error {
|
|
return ValidateInteractiveActionsForWebhook(&Post{
|
|
Props: map[string]any{
|
|
PostPropsMmBlocks: blocks,
|
|
PostPropsMmBlocksActions: actions,
|
|
},
|
|
})
|
|
}
|