@@ -8,32 +8,40 @@ interface Config {
88interface GifInfo {
99 small : string ;
1010 medium : string ;
11+ alt : string | undefined ;
1112}
1213
1314interface GiphyResponseResource {
1415 url ?: string ;
16+ webp ?: string ;
1517}
1618
1719interface GiphyResponseGif {
20+ alt_text ?: string ;
1821 images : {
1922 original ?: GiphyResponseResource ;
2023 fixed_height ?: GiphyResponseResource ;
2124 fixed_height_small ?: GiphyResponseResource ;
25+ fixed_height_downsampled ?: GiphyResponseResource ;
26+ fixed_width ?: GiphyResponseResource ;
27+ fixed_width_small ?: GiphyResponseResource ;
28+ fixed_width_downsampled ?: GiphyResponseResource ;
2229 } ;
2330}
2431
25- interface GiphyResponse {
26- status : number ;
27- data : ReadonlyArray < GiphyResponseGif > ;
32+ export interface GiphyResponse {
33+ meta : { status : number } ;
34+ data : GiphyResponseGif [ ] ;
35+ pagination : { total_count ?: number } ;
2836}
2937
3038export class GiphyService {
3139 private readonly baseUrl : string ;
3240
3341 private readonly apiKey : string ;
3442
35- // memory used = ~120 bytes per gif entry * max limit * max cache size
36- private readonly searchCache = new LruCache < string , GifInfo [ ] > ( 1024 ) ;
43+ // memory used = ~200 bytes per gif entry * max limit * cache size
44+ private readonly searchCache = new LruCache < string , GifInfo [ ] > ( 256 ) ;
3745
3846 public constructor ( config : Config ) {
3947 this . baseUrl = config . baseUrl ;
@@ -42,49 +50,68 @@ export class GiphyService {
4250
4351 public async search (
4452 query : string ,
53+ offset : number ,
4554 limit : number ,
46- lang = 'en' ,
55+ lang ?: string | undefined ,
4756 ) : Promise < GifInfo [ ] > {
48- if ( ! this . apiKey || ! query || limit <= 0 ) {
57+ if ( ! this . apiKey || ! query || offset < 0 || limit <= 0 ) {
4958 return [ ] ;
5059 }
5160
52- const cached = await this . searchCache . cachedAsync (
53- `${ lang } :${ query } ` ,
54- async ( ) : Promise < GifInfo [ ] > => {
55- const params = new URLSearchParams ( ) ;
56- params . append ( 'api_key' , this . apiKey ) ;
57- params . append ( 'q' , query ) ;
58- params . append ( 'limit' , String ( limit ) ) ;
59- params . append ( 'rating' , 'g' ) ;
60- params . append ( 'lang' , lang ) ;
61- const result = await fetch (
62- `${ this . baseUrl } /gifs/search?${ params . toString ( ) } ` ,
63- ) ;
64- const resultJson = ( await result . json ( ) ) as GiphyResponse ;
65-
66- if ( resultJson . status === 400 ) {
67- throw new Error ( 'Giphy API returned Bad Request' ) ;
68- } else if ( resultJson . status === 403 ) {
69- throw new Error ( 'Giphy API key rejected' ) ;
70- } else if ( resultJson . status === 429 ) {
71- throw new Error ( 'Giphy API rate limit reached' ) ;
72- } else if ( resultJson . status >= 400 ) {
73- throw new Error ( `Unknown Giphy API response: ${ resultJson . status } ` ) ;
74- }
75-
76- return resultJson . data . map ( ( gif ) => {
77- const original = gif . images . original ?. url ?? '' ;
78- const fixed = gif . images . fixed_height ?. url ?? original ;
79- const small = gif . images . fixed_height_small ?. url ?? fixed ;
80- return {
81- small : small . split ( '?' ) [ 0 ] ?? '' ,
82- medium : fixed . split ( '?' ) [ 0 ] ?? '' ,
83- } ;
84- } ) ;
85- } ,
86- ( c ) => c . length >= limit ,
87- ) ;
88- return cached . slice ( 0 , limit ) ;
61+ const cacheKey = `${ lang } :${ query } :${ offset } :${ limit } ` ;
62+
63+ return this . searchCache . cachedAsync ( cacheKey , async ( ) => {
64+ const params = new URLSearchParams ( {
65+ api_key : this . apiKey ,
66+ q : query ,
67+ offset : String ( offset ) ,
68+ limit : String ( limit ) ,
69+ rating : 'g' ,
70+ bundle : 'messaging_non_clips' ,
71+ } ) ;
72+ if ( lang ) {
73+ params . set ( 'lang' , lang ) ;
74+ }
75+ const result = await fetch ( `${ this . baseUrl } /gifs/search?${ params } ` ) ;
76+ const resultJson = ( await result . json ( ) ) as GiphyResponse ;
77+ const status = resultJson . meta ?. status || result . status ;
78+
79+ if ( status === 400 ) {
80+ throw new Error ( 'Giphy API returned Bad Request' ) ;
81+ } else if ( status === 403 ) {
82+ throw new Error ( 'Giphy API key rejected' ) ;
83+ } else if ( status === 429 ) {
84+ throw new Error ( 'Giphy API rate limit reached' ) ;
85+ } else if ( status >= 400 ) {
86+ throw new Error ( `Unknown Giphy API response: ${ status } ` ) ;
87+ }
88+
89+ return resultJson . data . map ( ( gif ) => {
90+ const original = getResourceURL ( gif . images . original ) ;
91+ const medium = getResourceURL ( gif . images . fixed_height ) ;
92+ const small = getResourceURL ( gif . images . fixed_height_small ) ;
93+ return {
94+ small : trimQuery ( small ?? medium ?? original ) ?? '' ,
95+ medium : trimQuery ( medium ?? original ?? small ) ?? '' ,
96+ alt : gif . alt_text || undefined ,
97+ } ;
98+ } ) ;
99+ } ) ;
100+ }
101+ }
102+
103+ const getResourceURL = ( image : GiphyResponseResource | undefined ) =>
104+ image ?. webp ?? image ?. url ;
105+
106+ function trimQuery ( href : string | undefined ) {
107+ if ( ! href ) {
108+ return undefined ;
109+ }
110+ try {
111+ const url = new URL ( href ) ;
112+ url . search = '' ;
113+ return url . toString ( ) ;
114+ } catch {
115+ return href . split ( '?' ) [ 0 ] ?? '' ;
89116 }
90117}
0 commit comments