@@ -22,6 +22,7 @@ import {
22
22
Article ,
23
23
Create ,
24
24
CryptographicKey ,
25
+ Emoji ,
25
26
Follow ,
26
27
Image ,
27
28
Like as RawLike ,
@@ -40,8 +41,10 @@ import { assert } from "@std/assert/assert";
40
41
import { assertEquals } from "@std/assert/equals" ;
41
42
import { assertFalse } from "@std/assert/false" ;
42
43
import { assertInstanceOf } from "@std/assert/instance-of" ;
44
+ import { assertThrows } from "@std/assert/throws" ;
43
45
import { BotImpl } from "./bot-impl.ts" ;
44
46
import { parseSemVer } from "./bot.ts" ;
47
+ import type { CustomEmoji } from "./emoji.ts" ;
45
48
import type { FollowRequest } from "./follow.ts" ;
46
49
import type { Message , MessageClass , SharedMessage } from "./message.ts" ;
47
50
import type { Like } from "./reaction.ts" ;
@@ -1963,6 +1966,171 @@ Deno.test("BotImpl.fetch()", async () => {
1963
1966
assertEquals ( response2 . status , 200 ) ;
1964
1967
} ) ;
1965
1968
1969
+ // Test BotImpl.addCustomEmoji() and BotImpl.addCustomEmojis()
1970
+ Deno . test ( "BotImpl.addCustomEmoji(), BotImpl.addCustomEmojis()" , async ( t ) => {
1971
+ const bot = new BotImpl < void > ( { kv : new MemoryKvStore ( ) , username : "bot" } ) ;
1972
+
1973
+ await t . step ( "addCustomEmoji()" , ( ) => {
1974
+ const emojiData : CustomEmoji = {
1975
+ type : "image/png" ,
1976
+ url : "https://example.com/emoji.png" ,
1977
+ } ;
1978
+ const deferredEmoji = bot . addCustomEmoji ( "testEmoji" , emojiData ) ;
1979
+ assertEquals ( typeof deferredEmoji , "function" ) ;
1980
+ assertEquals ( bot . customEmojis [ "testEmoji" ] , emojiData ) ;
1981
+
1982
+ // Test invalid name
1983
+ assertThrows (
1984
+ ( ) => bot . addCustomEmoji ( "invalid name" , emojiData ) ,
1985
+ TypeError ,
1986
+ "Invalid custom emoji name" ,
1987
+ ) ;
1988
+
1989
+ // Test duplicate name
1990
+ assertThrows (
1991
+ ( ) => bot . addCustomEmoji ( "testEmoji" , emojiData ) ,
1992
+ TypeError ,
1993
+ "Duplicate custom emoji name" ,
1994
+ ) ;
1995
+
1996
+ // Test unsupported media type
1997
+ assertThrows (
1998
+ ( ) =>
1999
+ bot . addCustomEmoji ( "invalidType" , {
2000
+ // @ts -expect-error: Intended type error for testing runtime check
2001
+ type : "text/plain" ,
2002
+ url : "https://example.com/emoji.txt" ,
2003
+ } ) ,
2004
+ TypeError ,
2005
+ "Unsupported media type" ,
2006
+ ) ;
2007
+ } ) ;
2008
+
2009
+ await t . step ( "addCustomEmojis()" , ( ) => {
2010
+ const emojisData = {
2011
+ emoji1 : { type : "image/png" , url : "https://example.com/emoji1.png" } ,
2012
+ emoji2 : { type : "image/gif" , file : "/path/to/emoji2.gif" } ,
2013
+ } as const ;
2014
+ const deferredEmojis = bot . addCustomEmojis ( emojisData ) ;
2015
+
2016
+ assertEquals ( typeof deferredEmojis [ "emoji1" ] , "function" ) ;
2017
+ assertEquals ( typeof deferredEmojis [ "emoji2" ] , "function" ) ;
2018
+ assertEquals ( bot . customEmojis [ "emoji1" ] , emojisData . emoji1 ) ;
2019
+ assertEquals ( bot . customEmojis [ "emoji2" ] , emojisData . emoji2 ) ;
2020
+
2021
+ // Test duplicate name within the batch
2022
+ assertThrows (
2023
+ ( ) =>
2024
+ bot . addCustomEmojis ( {
2025
+ emoji1 : { type : "image/png" , url : "https://example.com/dup1.png" } ,
2026
+ } ) ,
2027
+ TypeError ,
2028
+ "Duplicate custom emoji name: emoji1" ,
2029
+ ) ;
2030
+ } ) ;
2031
+ } ) ;
2032
+
2033
+ // Test BotImpl.getEmoji()
2034
+ Deno . test ( "BotImpl.getEmoji()" , async ( ) => {
2035
+ const bot = new BotImpl < void > ( { kv : new MemoryKvStore ( ) , username : "bot" } ) ;
2036
+ const ctx = bot . federation . createContext (
2037
+ new URL ( "https://example.com" ) ,
2038
+ undefined ,
2039
+ ) ;
2040
+
2041
+ // Test with remote URL
2042
+ const remoteEmojiData : CustomEmoji = {
2043
+ type : "image/png" ,
2044
+ url : "https://remote.com/emoji.png" ,
2045
+ } ;
2046
+ bot . customEmojis [ "remoteEmoji" ] = remoteEmojiData ;
2047
+ const remoteEmoji = bot . getEmoji ( ctx , "remoteEmoji" , remoteEmojiData ) ;
2048
+ assertInstanceOf ( remoteEmoji , Emoji ) ;
2049
+ assertEquals (
2050
+ remoteEmoji . id ,
2051
+ new URL ( "https://example.com/ap/emoji/remoteEmoji" ) ,
2052
+ ) ;
2053
+ assertEquals ( remoteEmoji . name , ":remoteEmoji:" ) ;
2054
+ const icon = await remoteEmoji . getIcon ( ) ;
2055
+ assertInstanceOf ( icon , Image ) ;
2056
+ assertEquals ( icon . mediaType , "image/png" ) ;
2057
+ assertEquals ( icon . url ?. href , "https://remote.com/emoji.png" ) ;
2058
+
2059
+ // Test with local file
2060
+ const localEmojiData : CustomEmoji = {
2061
+ type : "image/gif" ,
2062
+ file : "/path/to/local/emoji.gif" ,
2063
+ } ;
2064
+ bot . customEmojis [ "localEmoji" ] = localEmojiData ;
2065
+ const localEmoji = bot . getEmoji ( ctx , "localEmoji" , localEmojiData ) ;
2066
+ assertInstanceOf ( localEmoji , Emoji ) ;
2067
+ assertEquals (
2068
+ localEmoji . id ,
2069
+ new URL ( "https://example.com/ap/emoji/localEmoji" ) ,
2070
+ ) ;
2071
+ assertEquals ( localEmoji . name , ":localEmoji:" ) ;
2072
+ const icon2 = await localEmoji . getIcon ( ) ;
2073
+ assertInstanceOf ( icon2 , Image ) ;
2074
+ assertEquals ( icon2 . mediaType , "image/gif" ) ;
2075
+ assertEquals (
2076
+ icon2 . url ?. href ,
2077
+ "https://example.com/emojis/localEmoji.gif" ,
2078
+ ) ;
2079
+
2080
+ // Test with local file without extension mapping
2081
+ const localEmojiDataNoExt : CustomEmoji = {
2082
+ type : "image/webp" ,
2083
+ file : "/path/to/local/emoji" ,
2084
+ } ;
2085
+ bot . customEmojis [ "localEmojiNoExt" ] = localEmojiDataNoExt ;
2086
+ const localEmojiNoExt = bot . getEmoji (
2087
+ ctx ,
2088
+ "localEmojiNoExt" ,
2089
+ localEmojiDataNoExt ,
2090
+ ) ;
2091
+ const icon3 = await localEmojiNoExt . getIcon ( ) ;
2092
+ assertInstanceOf ( icon3 , Image ) ;
2093
+ assertEquals ( icon3 . mediaType , "image/webp" ) ;
2094
+ assertEquals (
2095
+ icon3 . url ?. href ,
2096
+ "https://example.com/emojis/localEmojiNoExt.webp" ,
2097
+ ) ;
2098
+ } ) ;
2099
+
2100
+ // Test BotImpl.dispatchEmoji()
2101
+ Deno . test ( "BotImpl.dispatchEmoji()" , ( ) => {
2102
+ const bot = new BotImpl < void > ( { kv : new MemoryKvStore ( ) , username : "bot" } ) ;
2103
+ const ctx = bot . federation . createContext (
2104
+ new URL ( "https://example.com" ) ,
2105
+ undefined ,
2106
+ ) ;
2107
+ const emojiData : CustomEmoji = {
2108
+ type : "image/png" ,
2109
+ url : "https://example.com/emoji.png" ,
2110
+ } ;
2111
+ bot . customEmojis [ "testEmoji" ] = emojiData ;
2112
+
2113
+ // Test dispatching an existing emoji
2114
+ const emoji = bot . dispatchEmoji ( ctx , { name : "testEmoji" } ) ;
2115
+ assertInstanceOf ( emoji , Emoji ) ;
2116
+ assertEquals ( emoji . id , new URL ( "https://example.com/ap/emoji/testEmoji" ) ) ;
2117
+ assertEquals ( emoji . name , ":testEmoji:" ) ;
2118
+
2119
+ // Test dispatching a non-existent emoji
2120
+ const nonExistent = bot . dispatchEmoji ( ctx , { name : "nonExistent" } ) ;
2121
+ assertEquals ( nonExistent , null ) ;
2122
+ } ) ;
2123
+
2124
+ Deno . test ( "BotImpl.getFollowersFirstCursor()" , ( ) => {
2125
+ const bot = new BotImpl < void > ( { kv : new MemoryKvStore ( ) , username : "bot" } ) ;
2126
+ const ctx = bot . federation . createContext (
2127
+ new URL ( "https://example.com" ) ,
2128
+ undefined ,
2129
+ ) ;
2130
+ assertEquals ( bot . getFollowersFirstCursor ( ctx , "non-existent" ) , null ) ;
2131
+ assertEquals ( bot . getFollowersFirstCursor ( ctx , "bot" ) , "0" ) ;
2132
+ } ) ;
2133
+
1966
2134
interface SentActivity {
1967
2135
recipients : "followers" | Recipient [ ] ;
1968
2136
activity : Activity ;
0 commit comments