Compare commits

..
Author SHA1 Message Date
IAmTomahawkx 3768ee6c57 feat: replace tenor with gifbox
Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
2026-06-30 11:46:32 -07:00
10 changed files with 153 additions and 185 deletions
-30
View File
@@ -1,30 +0,0 @@
# DO NOT EDIT DIRECTLY IN REPOSITORY
# Managed in Terraform templates
name: Renovate
on:
workflow_dispatch:
schedule:
- cron: '0/15 * * * *'
jobs:
renovate:
runs-on: ubuntu-latest
steps:
- id: app-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.GH_STOAT_RELEASE_APP_ID }}
private-key: ${{ secrets.GH_STOAT_RELEASE_APP_PRIVATE_KEY }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
with:
github_token: ${{ steps.app-token.outputs.token }}
- name: Self-hosted Renovate
uses: renovatebot/github-action@v46.1.14
with:
token: '${{ steps.app-token.outputs.token }}'
env:
RENOVATE_PLATFORM_COMMIT: 'enabled'
RENOVATE_REPOSITORIES: '${{ github.repository }}'
@@ -1,3 +1,4 @@
*️⃣
0️⃣
1️⃣
@@ -241,7 +242,6 @@
🆘
🆙
🆚
🇦
🇦🇨
🇦🇩
🇦🇪
@@ -259,7 +259,6 @@
🇦🇼
🇦🇽
🇦🇿
🇧
🇧🇦
🇧🇧
🇧🇩
@@ -281,7 +280,6 @@
🇧🇼
🇧🇾
🇧🇿
🇨
🇨🇦
🇨🇨
🇨🇩
@@ -303,7 +301,6 @@
🇨🇽
🇨🇾
🇨🇿
🇩
🇩🇪
🇩🇬
🇩🇯
@@ -311,7 +308,6 @@
🇩🇲
🇩🇴
🇩🇿
🇪
🇪🇦
🇪🇨
🇪🇪
@@ -321,14 +317,12 @@
🇪🇸
🇪🇹
🇪🇺
🇫
🇫🇮
🇫🇯
🇫🇰
🇫🇲
🇫🇴
🇫🇷
🇬
🇬🇦
🇬🇧
🇬🇩
@@ -348,14 +342,12 @@
🇬🇺
🇬🇼
🇬🇾
🇭
🇭🇰
🇭🇲
🇭🇳
🇭🇷
🇭🇹
🇭🇺
🇮
🇮🇨
🇮🇩
🇮🇪
@@ -367,12 +359,10 @@
🇮🇷
🇮🇸
🇮🇹
🇯
🇯🇪
🇯🇲
🇯🇴
🇯🇵
🇰
🇰🇪
🇰🇬
🇰🇭
@@ -384,7 +374,6 @@
🇰🇼
🇰🇾
🇰🇿
🇱
🇱🇦
🇱🇧
🇱🇨
@@ -396,7 +385,6 @@
🇱🇺
🇱🇻
🇱🇾
🇲
🇲🇦
🇲🇨
🇲🇩
@@ -420,7 +408,6 @@
🇲🇽
🇲🇾
🇲🇿
🇳
🇳🇦
🇳🇨
🇳🇪
@@ -433,9 +420,7 @@
🇳🇷
🇳🇺
🇳🇿
🇴
🇴🇲
🇵
🇵🇦
🇵🇪
🇵🇫
@@ -450,15 +435,12 @@
🇵🇹
🇵🇼
🇵🇾
🇶
🇶🇦
🇷
🇷🇪
🇷🇴
🇷🇸
🇷🇺
🇷🇼
🇸
🇸🇦
🇸🇧
🇸🇨
@@ -480,7 +462,6 @@
🇸🇽
🇸🇾
🇸🇿
🇹
🇹🇦
🇹🇨
🇹🇩
@@ -498,7 +479,6 @@
🇹🇻
🇹🇼
🇹🇿
🇺
🇺🇦
🇺🇬
🇺🇲
@@ -506,7 +486,6 @@
🇺🇸
🇺🇾
🇺🇿
🇻
🇻🇦
🇻🇨
🇻🇪
@@ -514,15 +493,11 @@
🇻🇮
🇻🇳
🇻🇺
🇼
🇼🇫
🇼🇸
🇽
🇽🇰
🇾
🇾🇪
🇾🇹
🇿
🇿🇦
🇿🇲
🇿🇼
+3 -3
View File
@@ -30,7 +30,7 @@ pub async fn create_session(user_id: &str, flags: u8) -> (bool, u32) {
if let Ok(mut conn) = get_connection().await {
// Check whether this is the first session
let was_empty = __get_set_size(&mut conn, &format!("sessions:{user_id}")).await == 0;
let was_empty = __get_set_size(&mut conn, user_id).await == 0;
// A session ID is comprised of random data and any flags ORed to the end
let session_id = {
@@ -39,7 +39,7 @@ pub async fn create_session(user_id: &str, flags: u8) -> (bool, u32) {
};
// Add session to user's sessions and to the region
__add_to_set_u32(&mut conn, &format!("sessions:{user_id}"), session_id).await;
__add_to_set_u32(&mut conn, user_id, session_id).await;
__add_to_set_string(&mut conn, ONLINE_SET, user_id).await;
__add_to_set_string(&mut conn, &REGION_KEY, &format!("{user_id}:{session_id}")).await;
info!("Created session for {user_id}, assigned them a session ID of {session_id}.");
@@ -62,7 +62,7 @@ async fn delete_session_internal(user_id: &str, session_id: u32, skip_region: bo
if let Ok(mut conn) = get_connection().await {
// Remove the session
__remove_from_set_u32(&mut conn, &format!("sessions:{user_id}"), session_id).await;
__remove_from_set_u32(&mut conn, user_id, session_id).await;
// Remove from the region
if !skip_region {
+73 -25
View File
@@ -64,6 +64,47 @@ pub fn mount(config: Settings, mut rocket: Rocket<Build>) -> Rocket<Build> {
};
}
if config.features.webhooks_enabled {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
"/webhooks" => webhooks::routes()
};
} else {
mount_endpoints_and_merged_docs! {
rocket, "/0.8".to_owned(), settings,
"/" => (vec![], custom_openapi_spec()),
"" => openapi_get_routes_spec![root::root],
"/users" => users::routes(),
"/bots" => bots::routes(),
"/channels" => channels::routes(),
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes()
};
}
rocket
}
@@ -74,8 +115,8 @@ fn custom_openapi_spec() -> OpenApi {
extensions.insert(
"x-logo".to_owned(),
json!({
"url": "https://stoat.chat/header.png",
"altText": "Stoat Header"
"url": "https://revolt.chat/header.png",
"altText": "Revolt Header"
}),
);
@@ -83,7 +124,7 @@ fn custom_openapi_spec() -> OpenApi {
"x-tagGroups".to_owned(),
json!([
{
"name": "Stoat",
"name": "Revolt",
"tags": [
"Core"
]
@@ -164,21 +205,18 @@ fn custom_openapi_spec() -> OpenApi {
OpenApi {
openapi: OpenApi::default_version(),
info: Info {
title: "Stoat API".to_owned(),
title: "Revolt API".to_owned(),
description: Some("Open source user-first chat platform.".to_owned()),
terms_of_service: Some("https://stoat.chat/terms".to_owned()),
terms_of_service: Some("https://revolt.chat/terms".to_owned()),
contact: Some(Contact {
name: Some("Stoat".to_owned()),
url: Some("https://stoat.chat".to_owned()),
email: Some("contact@stoat.chat".to_owned()),
name: Some("Revolt Support".to_owned()),
url: Some("https://revolt.chat".to_owned()),
email: Some("contact@revolt.chat".to_owned()),
..Default::default()
}),
license: Some(License {
name: "AGPLv3".to_owned(),
url: Some(
"https://github.com/stoatchat/stoatchat/blob/main/crates/delta/LICENSE"
.to_owned(),
),
url: Some("https://github.com/stoatchat/stoatchat/blob/main/crates/delta/LICENSE".to_owned()),
..Default::default()
}),
version: env!("CARGO_PKG_VERSION").to_string(),
@@ -186,19 +224,29 @@ fn custom_openapi_spec() -> OpenApi {
},
servers: vec![
Server {
url: "https://api.stoat.chat".to_owned(),
description: Some("Stoat Production".to_owned()),
url: "https://api.revolt.chat".to_owned(),
description: Some("Revolt Production".to_owned()),
..Default::default()
},
Server {
url: "https://beta.stoat.chat/api".to_owned(),
description: Some("Stoat Beta".to_owned()),
url: "https://revolt.chat/api".to_owned(),
description: Some("Revolt Staging".to_owned()),
..Default::default()
},
Server {
url: "http://local.revolt.chat:14702".to_owned(),
description: Some("Local Revolt Environment".to_owned()),
..Default::default()
},
Server {
url: "http://local.revolt.chat:14702/0.8".to_owned(),
description: Some("Local Revolt Environment (v0.8)".to_owned()),
..Default::default()
},
],
external_docs: Some(ExternalDocs {
url: "https://developers.stoat.chat".to_owned(),
description: Some("Stoat Developer Documentation".to_owned()),
url: "https://developers.revolt.chat".to_owned(),
description: Some("Revolt Developer Documentation".to_owned()),
..Default::default()
}),
extensions,
@@ -206,19 +254,19 @@ fn custom_openapi_spec() -> OpenApi {
Tag {
name: "Core".to_owned(),
description: Some(
"Use in your applications to determine information about the Stoat node"
"Use in your applications to determine information about the Revolt node"
.to_owned(),
),
..Default::default()
},
Tag {
name: "User Information".to_owned(),
description: Some("Query and fetch users on Stoat".to_owned()),
description: Some("Query and fetch users on Revolt".to_owned()),
..Default::default()
},
Tag {
name: "Direct Messaging".to_owned(),
description: Some("Direct message other users on Stoat".to_owned()),
description: Some("Direct message other users on Revolt".to_owned()),
..Default::default()
},
Tag {
@@ -235,7 +283,7 @@ fn custom_openapi_spec() -> OpenApi {
},
Tag {
name: "Channel Information".to_owned(),
description: Some("Query and fetch channels on Stoat".to_owned()),
description: Some("Query and fetch channels on Revolt".to_owned()),
..Default::default()
},
Tag {
@@ -265,7 +313,7 @@ fn custom_openapi_spec() -> OpenApi {
},
Tag {
name: "Server Information".to_owned(),
description: Some("Query and fetch servers on Stoat".to_owned()),
description: Some("Query and fetch servers on Revolt".to_owned()),
..Default::default()
},
Tag {
@@ -301,7 +349,7 @@ fn custom_openapi_spec() -> OpenApi {
Tag {
name: "Onboarding".to_owned(),
description: Some(
"After signing up to Stoat, users must pick a unique username".to_owned(),
"After signing up to Revolt, users must pick a unique username".to_owned(),
),
..Default::default()
},
@@ -313,7 +361,7 @@ fn custom_openapi_spec() -> OpenApi {
Tag {
name: "Web Push".to_owned(),
description: Some(
"Subscribe to and receive Stoat push notifications while offline".to_owned(),
"Subscribe to and receive Revolt push notifications while offline".to_owned(),
),
..Default::default()
},
+49 -38
View File
@@ -10,7 +10,7 @@ use tokio::sync::RwLock;
pub mod types;
const TENOR_API_BASE_URL: &str = "https://tenor.googleapis.com/v2";
const TENOR_API_BASE_URL: &str = "https://compat.gifbox.me/v2";
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TenorError {
@@ -59,7 +59,11 @@ impl Tenor {
}
}
pub async fn request<T: DeserializeOwned>(&self, path: &str, query: &[Option<(&str, &str)>]) -> Result<Arc<T>, TenorError> {
pub async fn request<T: DeserializeOwned>(
&self,
path: &str,
query: &[Option<(&str, &str)>],
) -> Result<Arc<T>, TenorError> {
let response = self
.client
.get(format!("{TENOR_API_BASE_URL}{path}"))
@@ -95,24 +99,27 @@ impl Tenor {
}
}
let res = self.coalescion.execute(unique_key.clone(), || async move {
self.request::<types::PaginatedMediaResponse>(
"/search",
&[
Some(("key", &self.key)),
Some(("q", query)),
Some(("client_key", "Gifbox")),
Some(("media_filter", "webm,tinywebm")),
Some(("locale", locale)),
Some(("contentfilter", "high")),
Some(("limit", &limit.to_string())),
position.is_empty().then_some(("pos", position)),
is_category.then_some(("component", "categories"))
]
).await
})
.await
.unwrap();
let res = self
.coalescion
.execute(unique_key.clone(), || async move {
self.request::<types::PaginatedMediaResponse>(
"/search",
&[
Some(("key", &self.key)),
Some(("q", query)),
Some(("client_key", "Gifbox")),
Some(("media_filter", "webm,tinywebm")),
Some(("locale", locale)),
Some(("contentfilter", "high")),
Some(("limit", &limit.to_string())),
position.is_empty().then_some(("pos", position)),
is_category.then_some(("component", "categories")),
],
)
.await
})
.await
.unwrap();
if let Ok(resp) = &*res {
self.cache.write().await.insert(unique_key, resp.clone());
@@ -143,8 +150,9 @@ impl Tenor {
Some(("client_key", "Gifbox")),
Some(("locale", locale)),
Some(("contentfilter", "high")),
]
).await
],
)
.await
})
.await
.unwrap();
@@ -173,22 +181,25 @@ impl Tenor {
}
}
let res = self.coalescion.execute(unique_key.clone(), || async move {
self.request::<types::PaginatedMediaResponse>(
"/featured",
&[
Some(("key", &self.key)),
Some(("client_key", "Gifbox")),
Some(("media_filter", "webm,tinywebm")),
Some(("locale", locale)),
Some(("contentfilter", "high")),
Some(("limit", &limit.to_string())),
position.is_empty().then_some(("pos", position)),
]
).await
})
.await
.unwrap();
let res = self
.coalescion
.execute(unique_key.clone(), || async move {
self.request::<types::PaginatedMediaResponse>(
"/featured",
&[
Some(("key", &self.key)),
Some(("client_key", "Gifbox")),
Some(("media_filter", "webm,tinywebm")),
Some(("locale", locale)),
Some(("contentfilter", "high")),
Some(("limit", &limit.to_string())),
position.is_empty().then_some(("pos", position)),
],
)
.await
})
.await
.unwrap();
if let Ok(resp) = &*res {
self.featured.write().await.insert(unique_key, resp.clone());
+23 -17
View File
@@ -21,26 +21,32 @@ async fn main() -> Result<(), std::io::Error> {
// Configure API schema
#[derive(OpenApi)]
#[openapi(
paths(api::root, api::proxy, api::embed),
components(schemas(
api::RootResponse,
revolt_result::Error,
revolt_result::ErrorType,
revolt_models::v0::ImageSize,
revolt_models::v0::Image,
revolt_models::v0::Video,
revolt_models::v0::TwitchType,
revolt_models::v0::LightspeedType,
revolt_models::v0::BandcampType,
revolt_models::v0::Special,
revolt_models::v0::WebsiteMetadata,
revolt_models::v0::Text,
revolt_models::v0::Embed
))
modifiers(&SecurityAddon),
paths(
api::root,
api::proxy,
api::embed
),
components(
schemas(
api::RootResponse,
revolt_result::Error,
revolt_result::ErrorType,
revolt_models::v0::ImageSize,
revolt_models::v0::Image,
revolt_models::v0::Video,
revolt_models::v0::TwitchType,
revolt_models::v0::LightspeedType,
revolt_models::v0::BandcampType,
revolt_models::v0::Special,
revolt_models::v0::WebsiteMetadata,
revolt_models::v0::Text,
revolt_models::v0::Embed
)
)
)]
struct ApiDoc;
#[allow(dead_code)]
struct SecurityAddon;
impl Modify for SecurityAddon {
+4 -4
View File
@@ -6,7 +6,7 @@ import type { ScalarOptions } from '@scalar/docusaurus';
const config: Config = {
title: 'Stoat Developers',
tagline: 'Developer documentation for Stoat',
favicon: 'https://stoat.chat/favicon-stoat.svg',
favicon: 'https://stoat.chat/favicon.svg',
future: {
v4: true,
@@ -80,10 +80,10 @@ const config: Config = {
respectPrefersColorScheme: true,
},
navbar: {
title: 'Stoat Developers',
logo: {
alt: 'Stoat for Developers',
src: '/img/navbar.light.svg',
srcDark: '/img/navbar.dark.svg'
alt: 'Stoat',
src: 'https://stoat.chat/favicon.svg',
},
items: [
{
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

-11
View File
@@ -1,11 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
],
"cloneSubmodules": true,
"cloneSubmodulesFilter": [
"*",
"!packages/client/assets"
]
}