diff --git a/assets/openapi.json b/assets/openapi.json
index 8941e40c..2622eb82 100644
--- a/assets/openapi.json
+++ b/assets/openapi.json
@@ -2649,40 +2649,10 @@
"type": "object",
"properties": {
"categories": {
- "type": "object",
- "properties": {
- "tags": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "searchterm": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "image": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- },
- "additionalProperties": false,
- "required": [
- "image",
- "name",
- "path",
- "searchterm"
- ]
- }
- }
- },
- "additionalProperties": false,
- "required": [
- "tags"
- ]
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GifTrendingCategory"
+ }
},
"gifs": {
"type": "array",
@@ -13730,6 +13700,21 @@
}
]
},
+ "GifTrendingCategory": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "src": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "name",
+ "src"
+ ]
+ },
"BackupCode": {
"type": "object",
"properties": {
@@ -23580,6 +23565,14 @@
"type": "string"
},
"description": "Locale"
+ },
+ {
+ "name": "provider",
+ "in": "query",
+ "schema": {
+ "type": "string"
+ },
+ "description": "Provider to use"
}
],
"tags": [
diff --git a/assets/schemas.json b/assets/schemas.json
index 0c883c93..b195be35 100644
--- a/assets/schemas.json
+++ b/assets/schemas.json
@@ -2824,40 +2824,10 @@
"type": "object",
"properties": {
"categories": {
- "type": "object",
- "properties": {
- "tags": {
- "type": "array",
- "items": {
- "type": "object",
- "properties": {
- "searchterm": {
- "type": "string"
- },
- "path": {
- "type": "string"
- },
- "image": {
- "type": "string"
- },
- "name": {
- "type": "string"
- }
- },
- "additionalProperties": false,
- "required": [
- "image",
- "name",
- "path",
- "searchterm"
- ]
- }
- }
- },
- "additionalProperties": false,
- "required": [
- "tags"
- ]
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/GifTrendingCategory"
+ }
},
"gifs": {
"type": "array",
@@ -14667,6 +14637,23 @@
],
"$schema": "http://json-schema.org/draft-07/schema#"
},
+ "GifTrendingCategory": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "src": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "name",
+ "src"
+ ],
+ "$schema": "http://json-schema.org/draft-07/schema#"
+ },
"BackupCode": {
"type": "object",
"properties": {
diff --git a/extra/admin-api/Models/Spacebar.Models.Api/GifResponse.cs b/extra/admin-api/Models/Spacebar.Models.Api/GifResponse.cs
index 4cf3b51e..dca2f08d 100644
--- a/extra/admin-api/Models/Spacebar.Models.Api/GifResponse.cs
+++ b/extra/admin-api/Models/Spacebar.Models.Api/GifResponse.cs
@@ -26,4 +26,20 @@ public class GifItem {
[JsonPropertyName("height")]
public int Height { get; set; }
+}
+
+public class TrendingGifsResult {
+ [JsonPropertyName("categories")]
+ public List<CategoryEntry> Categories { get; set; }
+
+ [JsonPropertyName("gifs")]
+ public List<GifItem> Gifs { get; set; }
+
+ public class CategoryEntry {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("src")]
+ public string Source { get; set; }
+ }
}
\ No newline at end of file
diff --git a/extra/admin-api/Tests/Spacebar.Tests/Tests/GifTests.cs b/extra/admin-api/Tests/Spacebar.Tests/Tests/GifTests.cs
index 8c4eff33..e122a030 100644
--- a/extra/admin-api/Tests/Spacebar.Tests/Tests/GifTests.cs
+++ b/extra/admin-api/Tests/Spacebar.Tests/Tests/GifTests.cs
@@ -28,10 +28,13 @@ public class GifTests(ITestOutputHelper testOutputHelper, TestFixture fixture) :
Client = await _userAbstraction.GetSharedUser();
}
+ // wish this could just be a single string.... MEmberData requires returning an array spanning the argument count...
+ public static IEnumerable<string[]> GifProviders() => [["tenor"]];
+
public static IEnumerable<object[]> GifSearchTestMatrix() {
foreach (var query in (string[])["meow", "meowmeow"])
- foreach (var provider in (string[])["tenor"])
- yield return [query, provider];
+ foreach (var provider in GifProviders())
+ yield return [query, provider[0]];
}
[Theory, MemberData(nameof(GifSearchTestMatrix))]
@@ -50,4 +53,28 @@ public class GifTests(ITestOutputHelper testOutputHelper, TestFixture fixture) :
Assert.NotEqual(0, gif.Height);
});
}
+
+ [Theory, MemberData(nameof(GifProviders))]
+ public async Task GetTrendingCategories(string provider) {
+ var resp = await Assert.HttpSuccess(await Client.ApiHttpClient.GetAsync($"gifs/trending?provider={provider}", TestContext.Current.CancellationToken));
+ var respContent = await resp.Content.ReadFromJsonAsync<TrendingGifsResult>(cancellationToken: TestContext.Current.CancellationToken);
+
+ Assert.True(respContent!.Categories.Count > 0, "respContent.Categories.Count > 0");
+ Assert.True(respContent.Gifs.Count > 0, "respContent.Gifs.Count > 0");
+
+ Assert.All(respContent.Categories, cat => {
+ Assert.StringNotNullOrWhitespace(cat.Name);
+ Assert.StringNotNullOrWhitespace(cat.Source);
+ });
+
+ Assert.All(respContent.Gifs, gif => {
+ Assert.StringNotNullOrWhitespace(gif.Id);
+ Assert.StringNotNullOrWhitespace(gif.GifSource);
+ Assert.StringNotNullOrWhitespace(gif.Preview);
+ Assert.StringNotNullOrWhitespace(gif.Source);
+ Assert.StringNotNullOrWhitespace(gif.Url);
+ Assert.NotEqual(0, gif.Width);
+ Assert.NotEqual(0, gif.Height);
+ });
+ }
}
\ No newline at end of file
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index 1393d6b5..9839d327 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -19,7 +19,9 @@
import { route } from "@spacebar/api/util/handlers/route";
import { getGifApiKey, parseGifResult } from "@spacebar/util";
import { Request, Response, Router } from "express";
-import { TenorCategoriesResults, TenorTrendingResults } from "@spacebar/schemas";
+import { TenorCategoriesResults, TenorTrendingResponse, TenorTrendingResults } from "@spacebar/schemas";
+import { GifProviderManager } from "@spacebar/util/util/integrations/gifProviders/GifProviderManager";
+import trendingGifs from "@spacebar/api/routes/gifs/trending-gifs";
const router = Router({ mergeParams: true });
@@ -31,6 +33,10 @@ router.get(
type: "string",
description: "Locale",
},
+ provider: {
+ type: "string",
+ description: "Provider to use",
+ },
},
responses: {
200: {
@@ -39,34 +45,17 @@ router.get(
},
}),
async (req: Request, res: Response) => {
- // TODO: Custom providers
- // TODO: return gifs as mp4
- // const { media_format, locale } = req.query;
- const { locale } = req.query;
+ const provider = GifProviderManager.getProvider((req.query.provider as string) ?? "tenor");
- const apiKey = getGifApiKey();
-
- const [responseSource, trendGifSource] = await Promise.all([
- fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
- fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, {
- method: "get",
- headers: { "Content-Type": "application/json" },
- }),
+ const [trendingCategories, trendingGifs] = await Promise.all([
+ provider.getTrendingCategories(req.query as typeof provider.getTrendingCategories.arguments),
+ provider.getTrendingGifs(req.query as typeof provider.getTrendingCategories.arguments),
]);
- 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);
+ categories: trendingCategories,
+ gifs: trendingGifs,
+ } satisfies TenorTrendingResponse).status(200);
},
);
diff --git a/src/schemas/responses/Tenor.ts b/src/schemas/responses/Tenor.ts
index 61d7c2bc..ac44899c 100644
--- a/src/schemas/responses/Tenor.ts
+++ b/src/schemas/responses/Tenor.ts
@@ -82,8 +82,13 @@ export interface GifResponse {
preview: string;
}
+export interface GifTrendingCategory {
+ name: string;
+ src: string;
+}
+
export interface TenorTrendingResponse {
- categories: TenorCategoriesResults;
+ categories: GifTrendingCategory[];
gifs: GifResponse[];
}
diff --git a/src/util/util/integrations/gifProviders/IGifProvider.ts b/src/util/util/integrations/gifProviders/IGifProvider.ts
index e9726c72..024f33f5 100644
--- a/src/util/util/integrations/gifProviders/IGifProvider.ts
+++ b/src/util/util/integrations/gifProviders/IGifProvider.ts
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { GifsResponse } from "@spacebar/schemas";
+import { GifsResponse, GifTrendingCategory } from "@spacebar/schemas";
export interface IGifProvider {
id: string;
@@ -24,4 +24,6 @@ export interface IGifProvider {
init(): Promise<void>;
search(query: { q: string; limit?: number; media_format: string; locale: string }): Promise<GifsResponse>;
+ getTrendingCategories(query: { media_format: string; locale: string }): Promise<GifTrendingCategory[]>;
+ getTrendingGifs(query: { q: string; limit?: number; media_format: string; locale: string }): Promise<GifsResponse>;
}
diff --git a/src/util/util/integrations/gifProviders/providers/TenorGifProvider.ts b/src/util/util/integrations/gifProviders/providers/TenorGifProvider.ts
index 2863843b..e771feec 100644
--- a/src/util/util/integrations/gifProviders/providers/TenorGifProvider.ts
+++ b/src/util/util/integrations/gifProviders/providers/TenorGifProvider.ts
@@ -17,7 +17,7 @@
*/
import type { IGifProvider } from "../IGifProvider";
-import { TenorGif, GifsResponse } from "@spacebar/schemas";
+import { TenorGif, GifsResponse, TenorCategoriesResults, GifTrendingCategory } from "@spacebar/schemas";
import { Config, getGifApiKey, parseGifResult } from "@spacebar/util";
export default class TenorGifProvider implements IGifProvider {
@@ -45,6 +45,30 @@ export default class TenorGifProvider implements IGifProvider {
return results.map(this.convertGifResult);
}
+ async getTrendingCategories(query: { media_format: string; locale: string }): Promise<GifTrendingCategory[]> {
+ const response = await fetch(`https://g.tenor.com/v1/categories?locale=${query.locale}&key=${this.#apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
+
+ const { tags } = (await response.json()) as TenorCategoriesResults;
+
+ return tags.map((x) => ({
+ name: x.searchterm,
+ src: x.image,
+ })) satisfies GifTrendingCategory[];
+ }
+
+ async getTrendingGifs(query: { media_format: string; locale: string }): Promise<GifsResponse> {
+ const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${query.media_format}&locale=${query.locale}&key=${this.#apiKey}`, {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ });
+
+ const { results } = (await response.json()) as { results: TenorGif[] };
+ return results.map(this.convertGifResult);
+ }
+
private convertGifResult(result: TenorGif) {
return {
id: result.id,
|