From 0dc5b19bd87875f146362e042e976958a047192e Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Fri, 24 Mar 2023 18:57:44 -0400 Subject: [PATCH] gifs and query params --- assets/openapi.json | Bin 0 -> 332131 bytes assets/schemas.json | Bin 8832754 -> 9267985 bytes scripts/openapi.js | 13 +++ src/api/routes/gifs/search.ts | 69 ++++++++---- src/api/routes/gifs/trending-gifs.ts | 64 +++++++---- src/api/routes/gifs/trending.ts | 155 +++++++++++---------------- src/api/util/handlers/route.ts | 8 ++ src/util/schemas/responses/Tenor.ts | 72 +++++++++++++ src/util/schemas/responses/index.ts | 1 + 9 files changed, 251 insertions(+), 131 deletions(-) create mode 100644 assets/openapi.json create mode 100644 src/util/schemas/responses/Tenor.ts diff --git a/assets/openapi.json b/assets/openapi.json new file mode 100644 index 0000000000000000000000000000000000000000..e95e46e6424a5820da7e155af54ae93076ac7681 GIT binary patch literal 332131 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 DuP&w? delta 1613 zcmZ`(YfO_@7*4sA%1298rlplyrl3QLfH!1Zopugv%h1t^PUr+SkF??BR#IH!L!;tzkAXpG^Ku)64y{lFpYwBM=gpZz(>^E~glywCf- zr~Bdcu)n^tg&D7L)OV5a0SAN+LaGMI%86i5&e>4NIUCmL_F)c#I+fwjspO8kD3*jr zfFzvPbD83%F&;FHEh-PhpKp$Y^UcinE*II)ptt~5n6K-dH8;-FzG)Emz`P`ji0_2i-Wj7!#K%7P!PjO@tqs@4OfT- zAsktdCf&oVdZVfOwk5m)Uu&x4aFMDw2A_%5lKV>w7UR>&+|@K-2{z^x$NB*>oLmNm zlgnoUCJ`$@mL2QpM@DZf7`U|-D-WT!p><&MN3CFc=i!8t%6IcAd-aVOH#Jc9&dn%K zvJ3Jg%1WpIc04aij6k!x)iA3|*iOq&IG7i$8NC)1((!l=bUa>L!*b}#g?He}g`_6B zV_LfornSjh_bIM?h?`;_7~;xF;>tTIpuDrbjYe78Q^C@n=ICb=_ijxG@74|9u_`8z zkF+m0fcE7^1KnYAZvvBh^FOprcj&gj4qe79RTo5CVL_Dn!&SJSokOmxe3S{5kKV0! zQEcnZ0$X=>y$cbYaJdNsJ|&DnBMZ4OvXD3Ak0f{#)gnY_2}WM?({PVQ8ZCI1PDa+Xt^~`>hnJc7F`2-3JPPq<_)A=rY(B zT`r<6O=KR*x?K)gw{;?Vs(q%CL9(__K-PA!iT0wOJGij|4sNXclI0NLz&=ye5*#ww z`zcKJe&%w5?CuGYo=S5FRGR9G{`AP#IV?B~!-5*O)At-6gOYC5K+>(x`<&>Ep%%^< z^j8DMgr*xHH2sLHpBh!;Jpwh}I+=f_eJ5Z10#q04%{0%R(gx@$Z8VdbW#@!2s5!p{ zn)6#RX|U(5q;74$4x%DwI}|yMN9mvt_rP4;L7N=S#2<&5_!A9IM4nEbf3j_WtuGmS sC_9akT ({ + name: k, + in: "query", + required: v.required, + schema: { type: v.type }, + description: v.description, + })); + + obj.parameters = [...(obj.parameters || []), ...query]; + } + obj.tags = [...(obj.tags || []), getTag(p)].unique(); specification.paths[path] = { diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts index fb99374b..b51bba37 100644 --- a/src/api/routes/gifs/search.ts +++ b/src/api/routes/gifs/search.ts @@ -16,34 +16,63 @@ along with this program. If not, see . */ -import { Router, Response, Request } from "express"; +import { route } from "@spacebar/api"; +import { TenorMediaTypes } from "@spacebar/util"; +import { Request, Response, Router } from "express"; import fetch from "node-fetch"; import ProxyAgent from "proxy-agent"; -import { route } from "@spacebar/api"; import { getGifApiKey, parseGifResult } from "./trending"; const router = Router(); -router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: Custom providers - const { q, media_format, locale } = req.query; - - const apiKey = getGifApiKey(); - - const agent = new ProxyAgent(); - - const response = await fetch( - `https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, - { - agent, - method: "get", - headers: { "Content-Type": "application/json" }, +router.get( + "/", + route({ + query: { + q: { + type: "string", + required: true, + description: "Search query", + }, + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => + isNaN(Number(key)), + ), + }, + locale: { + type: "string", + description: "Locale", + }, }, - ); + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { q, media_format, locale } = req.query; - const { results } = await response.json(); + const apiKey = getGifApiKey(); - res.json(results.map(parseGifResult)).status(200); -}); + const agent = new ProxyAgent(); + + const response = await fetch( + `https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, + { + agent, + method: "get", + headers: { "Content-Type": "application/json" }, + }, + ); + + const { results } = await response.json(); + + res.json(results.map(parseGifResult)).status(200); + }, +); export default router; diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts index 238a2abd..899250cf 100644 --- a/src/api/routes/gifs/trending-gifs.ts +++ b/src/api/routes/gifs/trending-gifs.ts @@ -16,34 +16,58 @@ along with this program. If not, see . */ -import { Router, Response, Request } from "express"; +import { route } from "@spacebar/api"; +import { TenorMediaTypes } from "@spacebar/util"; +import { Request, Response, Router } from "express"; import fetch from "node-fetch"; import ProxyAgent from "proxy-agent"; -import { route } from "@spacebar/api"; import { getGifApiKey, parseGifResult } from "./trending"; const router = Router(); -router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: Custom providers - const { media_format, locale } = req.query; - - const apiKey = getGifApiKey(); - - const agent = new ProxyAgent(); - - const response = await fetch( - `https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, - { - agent, - method: "get", - headers: { "Content-Type": "application/json" }, +router.get( + "/", + route({ + query: { + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => + isNaN(Number(key)), + ), + }, + locale: { + type: "string", + description: "Locale", + }, }, - ); + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { media_format, locale } = req.query; - const { results } = await response.json(); + const apiKey = getGifApiKey(); - res.json(results.map(parseGifResult)).status(200); -}); + const agent = new ProxyAgent(); + + const response = await fetch( + `https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, + { + agent, + method: "get", + headers: { "Content-Type": "application/json" }, + }, + ); + + const { results } = await response.json(); + + res.json(results.map(parseGifResult)).status(200); + }, +); export default router; diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts index 5cccdb2d..3c2ab6ab 100644 --- a/src/api/routes/gifs/trending.ts +++ b/src/api/routes/gifs/trending.ts @@ -16,66 +16,21 @@ along with this program. If not, see . */ -import { Router, Response, Request } from "express"; +import { route } from "@spacebar/api"; +import { + Config, + TenorCategoriesResults, + TenorGif, + TenorTrendingResults, +} from "@spacebar/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import ProxyAgent from "proxy-agent"; -import { route } from "@spacebar/api"; -import { Config } from "@spacebar/util"; -import { HTTPError } from "lambert-server"; const router = Router(); -// TODO: Move somewhere else -enum TENOR_GIF_TYPES { - gif, - mediumgif, - tinygif, - nanogif, - mp4, - loopedmp4, - tinymp4, - nanomp4, - webm, - tinywebm, - nanowebm, -} - -type TENOR_MEDIA = { - preview: string; - url: string; - dims: number[]; - size: number; -}; - -type TENOR_GIF = { - created: number; - hasaudio: boolean; - id: string; - media: { [type in keyof typeof TENOR_GIF_TYPES]: TENOR_MEDIA }[]; - tags: string[]; - title: string; - itemurl: string; - hascaption: boolean; - url: string; -}; - -type TENOR_CATEGORY = { - searchterm: string; - path: string; - image: string; - name: string; -}; - -type TENOR_CATEGORIES_RESULTS = { - tags: TENOR_CATEGORY[]; -}; - -type TENOR_TRENDING_RESULTS = { - next: string; - results: TENOR_GIF[]; -}; - -export function parseGifResult(result: TENOR_GIF) { +export function parseGifResult(result: TenorGif) { return { id: result.id, title: result.title, @@ -97,45 +52,63 @@ export function getGifApiKey() { return apiKey; } -router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: Custom providers - // TODO: return gifs as mp4 - // const { media_format, locale } = req.query; - const { locale } = req.query; - - const apiKey = getGifApiKey(); - - const agent = new ProxyAgent(); - - const [responseSource, trendGifSource] = await Promise.all([ - fetch( - `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, - { - agent, - method: "get", - headers: { "Content-Type": "application/json" }, +router.get( + "/", + route({ + query: { + locale: { + type: "string", + description: "Locale", }, - ), - fetch( - `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, - { - agent, - method: "get", - headers: { "Content-Type": "application/json" }, + }, + responses: { + 200: { + body: "TenorTrendingResponse", }, - ), - ]); + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + // TODO: return gifs as mp4 + // const { media_format, locale } = req.query; + const { locale } = req.query; - const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS; - const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS; + const apiKey = getGifApiKey(); - res.json({ - categories: tags.map((x) => ({ - name: x.searchterm, - src: x.image, - })), - gifs: [parseGifResult(results[0])], - }).status(200); -}); + const agent = new ProxyAgent(); + + const [responseSource, trendGifSource] = await Promise.all([ + fetch( + `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, + { + agent, + method: "get", + headers: { "Content-Type": "application/json" }, + }, + ), + fetch( + `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, + { + agent, + method: "get", + headers: { "Content-Type": "application/json" }, + }, + ), + ]); + + const { tags } = + (await responseSource.json()) as TenorCategoriesResults; + const { results } = + (await trendGifSource.json()) as TenorTrendingResults; + + res.json({ + categories: tags.map((x) => ({ + name: x.searchterm, + src: x.image, + })), + gifs: [parseGifResult(results[0])], + }).status(200); + }, +); export default router; diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts index 331ac0c2..2416b73f 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts @@ -62,6 +62,14 @@ export interface RouteOptions { event?: EVENT | EVENT[]; summary?: string; description?: string; + query?: { + [key: string]: { + type: string; + required?: boolean; + description?: string; + values?: string[]; + }; + }; // test?: { // response?: RouteResponse; // body?: unknown; diff --git a/src/util/schemas/responses/Tenor.ts b/src/util/schemas/responses/Tenor.ts new file mode 100644 index 00000000..9dddf9d0 --- /dev/null +++ b/src/util/schemas/responses/Tenor.ts @@ -0,0 +1,72 @@ +export enum TenorMediaTypes { + gif, + mediumgif, + tinygif, + nanogif, + mp4, + loopedmp4, + tinymp4, + nanomp4, + webm, + tinywebm, + nanowebm, +} + +export type TenorMedia = { + preview: string; + url: string; + dims: number[]; + size: number; +}; + +export type TenorGif = { + created: number; + hasaudio: boolean; + id: string; + media: { [type in keyof typeof TenorMediaTypes]: TenorMedia }[]; + tags: string[]; + title: string; + itemurl: string; + hascaption: boolean; + url: string; +}; + +export type TenorCategory = { + searchterm: string; + path: string; + image: string; + name: string; +}; + +export type TenorCategoriesResults = { + tags: TenorCategory[]; +}; + +export type TenorTrendingResults = { + next: string; + results: TenorGif[]; + locale: string; +}; + +export type TenorSearchResults = { + next: string; + results: TenorGif[]; +}; + +export interface TenorGifResponse { + id: string; + title: string; + url: string; + src: string; + gif_src: string; + width: number; + height: number; + preview: string; +} + +export interface TenorTrendingResponse { + categories: TenorCategoriesResults; + gifs: TenorGifResponse[]; +} + +export type TenorGifsResponse = TenorGifResponse[]; diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts index 49e8053b..30949f7f 100644 --- a/src/util/schemas/responses/index.ts +++ b/src/util/schemas/responses/index.ts @@ -13,6 +13,7 @@ export * from "./GatewayBotResponse"; export * from "./GatewayResponse"; export * from "./GenerateRegistrationTokensResponse"; export * from "./LocationMetadataResponse"; +export * from "./Tenor"; export * from "./TokenResponse"; export * from "./UserProfileResponse"; export * from "./UserRelationsResponse";