Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions shortcuts/common/resource_url.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ func BuildResourceURL(brand core.LarkBrand, kind, token string) string {
return host + "/drive/folder/" + token
case "mindnote":
return host + "/mindnote/" + token
case "minutes":
return host + "/minutes/" + token
case "slides":
return host + "/slides/" + token
default:
Expand Down Expand Up @@ -84,6 +86,7 @@ var urlPathToType = []struct {
{"/wiki/", "wiki"},
{"/file/", "file"},
{"/mindnote/", "mindnote"},
{"/minutes/", "minutes"},
{"/slides/", "slides"},
}

Expand All @@ -100,6 +103,7 @@ var urlPathToType = []struct {
// /file/TOKEN -> {Type: "file", Token: TOKEN}
// /drive/folder/TOKEN -> {Type: "folder", Token: TOKEN}
// /mindnote/TOKEN -> {Type: "mindnote", Token: TOKEN}
// /minutes/TOKEN -> {Type: "minutes", Token: TOKEN}
// /slides/TOKEN -> {Type: "slides", Token: TOKEN}
//
// Returns (ResourceRef{}, false) when the URL does not match any known pattern.
Expand Down Expand Up @@ -135,3 +139,41 @@ func ParseResourceURL(rawURL string) (ResourceRef, bool) {

return ResourceRef{}, false
}

// tokenPrefixToType maps bare Drive token prefixes to resource types.
// The prefixes are stable conventions used across Lark/Feishu tokens.
var tokenPrefixToType = []struct {
Prefix string
Type string
}{
{"doxcn", "docx"},
{"doccn", "doc"},
{"shtcn", "sheet"},
{"bascn", "bitable"},
{"fldcn", "folder"},
{"wikcn", "wiki"},
{"mncn", "mindnote"},
{"mincn", "minutes"},
{"slkcn", "slides"},
{"boxcn", "file"},
}

// InferResourceTypeFromToken maps a bare Drive token prefix to its resource
// type. Returns ("", false) when the prefix is not recognized.
//
// Prefix mapping: doxcn→docx, doccn→doc, shtcn→sheet, bascn→bitable,
// fldcn→folder, wikcn→wiki, mncn→mindnote, mincn→minutes,
// slkcn→slides, boxcn→file.
func InferResourceTypeFromToken(token string) (string, bool) {
token = strings.TrimSpace(token)
if token == "" {
return "", false
}
lower := strings.ToLower(token)
for _, mapping := range tokenPrefixToType {
if strings.HasPrefix(lower, mapping.Prefix) {
return mapping.Type, true
}
}
return "", false
}
44 changes: 43 additions & 1 deletion shortcuts/common/resource_url_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestParseResourceURL(t *testing.T) {
{"folder via /chat/drive/", "https://feishu.doubao.com/chat/drive/fldcnABC", "folder", "fldcnABC", true},
{"folder via /drive/shr/", "https://feishu.doubao.com/drive/shr/fldcnABC", "folder", "fldcnABC", true},
{"mindnote", "https://xxx.feishu.cn/mindnote/mncnABC", "mindnote", "mncnABC", true},
{"minutes", "https://xxx.feishu.cn/minutes/mincnABC", "minutes", "mincnABC", true},
{"slides", "https://xxx.feishu.cn/slides/slkcnABC", "slides", "slkcnABC", true},

// Lark domain
Expand Down Expand Up @@ -85,7 +86,7 @@ func TestParseResourceURL(t *testing.T) {
func TestParseResourceURL_RoundTrip(t *testing.T) {
t.Parallel()

types := []string{"docx", "doc", "sheet", "bitable", "wiki", "file", "folder", "mindnote", "slides"}
types := []string{"docx", "doc", "sheet", "bitable", "wiki", "file", "folder", "mindnote", "minutes", "slides"}
token := "testTOKEN123"

for _, kind := range types {
Expand All @@ -108,6 +109,46 @@ func TestParseResourceURL_RoundTrip(t *testing.T) {
}
}

func TestInferResourceTypeFromToken(t *testing.T) {
t.Parallel()

tests := []struct {
name string
token string
wantType string
wantOK bool
}{
{"docx", "doxcnABC", "docx", true},
{"doc", "doccnABC", "doc", true},
{"sheet", "shtcnABC", "sheet", true},
{"bitable", "bascnABC", "bitable", true},
{"folder", "fldcnABC", "folder", true},
{"wiki", "wikcnABC", "wiki", true},
{"mindnote", "mncnABC", "mindnote", true},
{"minutes", "mincnABC", "minutes", true},
{"slides", "slkcnABC", "slides", true},
{"file", "boxcnABC", "file", true},
{"case insensitive", "DOXCNabc", "docx", true},
{"whitespace trimmed", " doxcnABC ", "docx", true},
{"empty", "", "", false},
{"whitespace only", " ", "", false},
{"unrecognized prefix", "abc123", "", false},
{"ou_ prefix not a resource", "ou_xxx", "", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotType, gotOK := InferResourceTypeFromToken(tt.token)
if gotOK != tt.wantOK {
t.Errorf("InferResourceTypeFromToken(%q) ok = %v, want %v", tt.token, gotOK, tt.wantOK)
}
if gotType != tt.wantType {
t.Errorf("InferResourceTypeFromToken(%q) type = %q, want %q", tt.token, gotType, tt.wantType)
}
})
}
}

func TestBuildResourceURL(t *testing.T) {
t.Parallel()

Expand All @@ -126,6 +167,7 @@ func TestBuildResourceURL(t *testing.T) {
{"feishu file", core.BrandFeishu, "file", "boxcnABC", "https://www.feishu.cn/file/boxcnABC"},
{"feishu folder", core.BrandFeishu, "folder", "fldcnABC", "https://www.feishu.cn/drive/folder/fldcnABC"},
{"feishu mindnote", core.BrandFeishu, "mindnote", "mncnABC", "https://www.feishu.cn/mindnote/mncnABC"},
{"feishu minutes", core.BrandFeishu, "minutes", "mincnABC", "https://www.feishu.cn/minutes/mincnABC"},
{"feishu slides", core.BrandFeishu, "slides", "slkcnABC", "https://www.feishu.cn/slides/slkcnABC"},
{"lark docx", core.BrandLark, "docx", "doxcnABC", "https://www.larksuite.com/docx/doxcnABC"},
{"lark wiki", core.BrandLark, "wiki", "wikcnABC", "https://www.larksuite.com/wiki/wikcnABC"},
Expand Down
Loading
Loading