feat: rename

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2025-10-03 15:39:34 +02:00
parent c1730c3a17
commit 8a01d595fb
285 changed files with 2440 additions and 2521 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@
.cxx .cxx
local.properties local.properties
revoltbuild.properties revoltbuild.properties
stoatbuild.properties
sentry.properties sentry.properties
/.kotlin/sessions /.kotlin/sessions
app/src/main/assets/embedded app/src/main/assets/embedded

View File

@ -1 +1 @@
Revolt Stoat

View File

@ -1,10 +0,0 @@
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="500" height="500" rx="250" fill="url(#paint0_linear_204_8)"/>
<path d="M170.057 373C170.057 297.671 170.057 248.292 170.057 184.167L122 127H277.429C295.891 127 312.046 130.323 325.893 136.97C339.739 143.616 350.509 153.186 358.202 165.678C365.894 178.17 369.741 193.145 369.741 210.602C369.741 228.219 365.773 243.073 357.837 255.165C349.983 267.257 338.93 276.386 324.678 282.552C310.507 288.718 293.948 291.801 275 291.801H210.868V239.91H261.396C269.332 239.91 276.093 238.949 281.68 237.027C287.349 235.025 291.681 231.862 294.677 227.538C297.754 223.214 299.292 217.568 299.292 210.602C299.292 203.555 297.754 197.829 294.677 193.425C291.681 188.94 287.349 185.657 281.68 183.575C276.093 181.413 269.332 180.332 261.396 180.332H237.59V373H170.057ZM315.811 260.09L378 373H304.637L243.906 260.09H315.811Z" fill="#FF005C"/>
<defs>
<linearGradient id="paint0_linear_204_8" x1="17.4479" y1="13.8889" x2="589.73" y2="199.249" gradientUnits="userSpaceOnUse">
<stop stop-color="#070C1A"/>
<stop offset="1" stop-color="#262B37"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<h1>Revolt for Android</h1> <h1>Stoat for Android</h1>
<p>Official <a href="https://revolt.chat">Revolt</a> Android app.</p> <p>Official <a href="https://stoat.chat">Stoat</a> Android app.</p>
<br/><br/> <br/><br/>
<div> <div>
<img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" alt="Get it on Google Play" width="200"> <img src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" alt="Get it on Google Play" width="200">
@ -12,7 +12,7 @@
## Description ## Description
The codebase includes the app itself, as well as an internal library for interacting with the Revolt The codebase includes the app itself, as well as an internal library for interacting with the Stoat
API. The app is written in Kotlin, and wholly API. The app is written in Kotlin, and wholly
uses [Jetpack Compose](https://developer.android.com/jetpack/compose). uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
@ -28,20 +28,20 @@ uses [Jetpack Compose](https://developer.android.com/jetpack/compose).
## Resources ## Resources
### Revolt for Android ### Stoat for Android
- [Roadmap](https://op.revolt.wtf/projects/revolt-for-android/work_packages) - [Roadmap](https://op.revolt.wtf/projects/revolt-for-android/work_packages)
- [Revolt for Android Technical Documentation](https://revoltchat.github.io/android/) - [Stoat for Android Technical Documentation](https://revoltchat.github.io/android/)
- [Android-specific Contribution Guide](https://revoltchat.github.io/android/contributing/guidelines/) - [Android-specific Contribution Guide](https://revoltchat.github.io/android/contributing/guidelines/)
&mdash;**read carefully before contributing!** &mdash;**read carefully before contributing!**
### Revolt ### Stoat
- [Revolt Project Board](https://github.com/revoltchat/revolt/discussions) (Submit feature requests - [Stoat Project Board](https://github.com/revoltchat/revolt/discussions) (Submit feature requests
here) here)
- [Revolt Development Server](https://app.revolt.chat/invite/API) - [Stoat Development Server](https://app.revolt.chat/invite/API)
- [Revolt Server](https://app.revolt.chat/invite/Testers) - [Stoat Server](https://app.revolt.chat/invite/Testers)
- [General Revolt Contribution Guide](https://developers.revolt.chat/contrib.html) - [General Stoat Contribution Guide](https://developers.revolt.chat/contrib.html)
## Quick Start ## Quick Start

View File

@ -23,7 +23,7 @@ val accompanistVersion = "0.34.0"
val okhttpVersion = "4.12.0" val okhttpVersion = "4.12.0"
val navVersion = "2.9.0" val navVersion = "2.9.0"
val hiltVersion = "2.57" val hiltVersion = "2.57"
val glideVersion = "4.16.0" val glideVersion = "5.0.5"
val ktorVersion = "3.0.0-beta-2" val ktorVersion = "3.0.0-beta-2"
val media3Version = "1.7.1" val media3Version = "1.7.1"
val material3Version = "1.4.0-alpha15" val material3Version = "1.4.0-alpha15"
@ -67,13 +67,14 @@ fun property(fileName: String, propertyName: String, fallbackEnv: String? = null
} }
} }
// Calls property but with revoltbuild.properties as the first argument // Calls property but with stoatbuild.properties as the first argument
fun buildproperty(propertyName: String, fallbackEnv: String? = null): String? { fun buildproperty(propertyName: String, fallbackEnv: String? = null): String? {
return property("revoltbuild.properties", propertyName, fallbackEnv) return property("stoatbuild.properties", propertyName, fallbackEnv)
} }
android { android {
compileSdk = 36 compileSdk = 36
namespace = "chat.stoat"
defaultConfig { defaultConfig {
applicationId = "chat.revolt" applicationId = "chat.revolt"
@ -159,7 +160,6 @@ android {
androidResources { androidResources {
generateLocaleConfig = true generateLocaleConfig = true
} }
namespace = "chat.revolt"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path(file("src/main/cpp/CMakeLists.txt")) path(file("src/main/cpp/CMakeLists.txt"))
@ -241,6 +241,7 @@ dependencies {
// Glide - Image Loading // Glide - Image Loading
implementation("com.github.bumptech.glide:glide:$glideVersion") implementation("com.github.bumptech.glide:glide:$glideVersion")
implementation("com.github.bumptech.glide:compose:1.0.0-beta01") implementation("com.github.bumptech.glide:compose:1.0.0-beta01")
implementation("com.github.bumptech.glide:okhttp3-integration:5.0.5")
ksp("com.github.bumptech.glide:ksp:$glideVersion") ksp("com.github.bumptech.glide:ksp:$glideVersion")
// AboutLibraries - automated OSS library attribution // AboutLibraries - automated OSS library attribution
@ -313,9 +314,6 @@ dependencies {
// Square Logcat // Square Logcat
implementation("com.squareup.logcat:logcat:0.1") implementation("com.squareup.logcat:logcat:0.1")
// Librevolt
implementation("librevolt:librevolt-jvm:0.1.0")
// Testing // Testing
androidTestImplementation("androidx.test:runner:$androidXTestVersion") androidTestImplementation("androidx.test:runner:$androidXTestVersion")
androidTestImplementation("androidx.test:rules:$androidXTestVersion") androidTestImplementation("androidx.test:rules:$androidXTestVersion")
@ -344,7 +342,7 @@ aboutLibraries {
sqldelight { sqldelight {
databases { databases {
create("Database") { create("Database") {
packageName.set("chat.revolt.persistence") packageName.set("chat.stoat.persistence")
} }
} }
} }

View File

@ -109,4 +109,4 @@
public <fields>; public <fields>;
} }
-keep class chat.revolt.ndk.AstNode { *; } -keep class chat.stoat.ndk.AstNode { *; }

View File

@ -32,8 +32,8 @@
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:name=".RevoltApplication" android:name=".StoatApplication"
android:theme="@style/Theme.Revolt" android:theme="@style/Theme.Stoat"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">
<provider <provider
@ -70,7 +70,7 @@
android:exported="true" android:exported="true"
android:configChanges="orientation|screenSize|colorMode" android:configChanges="orientation|screenSize|colorMode"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.Revolt"> android:theme="@style/Theme.Stoat">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -80,8 +80,9 @@
<activity <activity
android:name=".activities.InviteActivity" android:name=".activities.InviteActivity"
android:theme="@style/Theme.Revolt" android:theme="@style/Theme.Stoat"
android:exported="true"> android:exported="true">
<!-- Revolt invite links -->
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -106,6 +107,37 @@
<data android:host="rvlt.gg" /> <data android:host="rvlt.gg" />
<uri-relative-filter-group
android:allow="false"
tools:targetApi="35">
<data android:pathPrefix="/discover/" />
</uri-relative-filter-group>
</intent-filter>
<!-- Stoat invite links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="stoat.chat" />
<data android:pathPrefix="/invite/" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="stt.gg" />
<uri-relative-filter-group <uri-relative-filter-group
android:allow="false" android:allow="false"
tools:targetApi="35"> tools:targetApi="35">
@ -116,7 +148,7 @@
<activity <activity
android:name=".activities.ShareTargetActivity" android:name=".activities.ShareTargetActivity"
android:theme="@style/Theme.Revolt" android:theme="@style/Theme.Stoat"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -132,17 +164,17 @@
<activity <activity
android:name=".activities.media.ImageViewActivity" android:name=".activities.media.ImageViewActivity"
android:theme="@style/Theme.Revolt" /> android:theme="@style/Theme.Stoat" />
<activity <activity
android:name=".activities.media.VideoViewActivity" android:name=".activities.media.VideoViewActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:theme="@style/Theme.Revolt" /> android:theme="@style/Theme.Stoat" />
<activity <activity
android:name=".activities.voice.IncomingActivity" android:name=".activities.voice.IncomingActivity"
android:configChanges="orientation|screenSize" android:configChanges="orientation|screenSize"
android:theme="@style/Theme.Revolt" /> android:theme="@style/Theme.Stoat" />
<!-- Backport photo picker via Google Play Services --> <!-- Backport photo picker via Google Play Services -->
<service <service

View File

@ -10,7 +10,7 @@ cmake_minimum_required(VERSION 3.22.1)
# Since this is the top level CMakeLists.txt, the project name is also accessible # Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope). # build script scope).
project("revolt") project("stoat")
# Compile cmark in external/cmark # Compile cmark in external/cmark
add_subdirectory(external/cmark) add_subdirectory(external/cmark)

View File

@ -32,7 +32,7 @@ namespace Stendal {
constructArrayListMethod = env->GetMethodID(localArrayListClass, "<init>", "(I)V"); constructArrayListMethod = env->GetMethodID(localArrayListClass, "<init>", "(I)V");
addArrayListMethod = env->GetMethodID(localArrayListClass, "add", "(Ljava/lang/Object;)Z"); addArrayListMethod = env->GetMethodID(localArrayListClass, "add", "(Ljava/lang/Object;)Z");
jclass localAstNodeClass = env->FindClass("chat/revolt/ndk/AstNode"); jclass localAstNodeClass = env->FindClass("chat/stoat/ndk/AstNode");
astNodeClass = (jclass) env->NewGlobalRef(localAstNodeClass); astNodeClass = (jclass) env->NewGlobalRef(localAstNodeClass);
astNodeConstructor = env->GetMethodID(localAstNodeClass, "<init>", astNodeConstructor = env->GetMethodID(localAstNodeClass, "<init>",
STENDAL_ASTNODE_CONSTRUCTOR_SIGNATURE); STENDAL_ASTNODE_CONSTRUCTOR_SIGNATURE);
@ -141,12 +141,12 @@ namespace Stendal {
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_chat_revolt_ndk_Stendal_init(JNIEnv *env, [[maybe_unused]] jobject thiz) { Java_chat_stoat_ndk_Stendal_init(JNIEnv *env, [[maybe_unused]] jobject thiz) {
Stendal::init(env); Stendal::init(env);
} }
extern "C" JNIEXPORT jobject JNICALL extern "C" JNIEXPORT jobject JNICALL
Java_chat_revolt_ndk_Stendal_render(JNIEnv *env, [[maybe_unused]] jobject thiz, jstring input) { Java_chat_stoat_ndk_Stendal_render(JNIEnv *env, [[maybe_unused]] jobject thiz, jstring input) {
const char *inputStr = env->GetStringUTFChars(input, nullptr); const char *inputStr = env->GetStringUTFChars(input, nullptr);
cmark_node *doc = cmark_parse_document(inputStr, strlen(inputStr), cmark_node *doc = cmark_parse_document(inputStr, strlen(inputStr),
CMARK_OPT_DEFAULT | CMARK_OPT_HARDBREAKS | CMARK_OPT_DEFAULT | CMARK_OPT_HARDBREAKS |

View File

@ -1,14 +0,0 @@
package chat.revolt.api.routes.channel
import chat.revolt.api.RevoltHttp
import chat.revolt.api.api
import io.ktor.client.request.delete
import io.ktor.client.request.put
suspend fun react(channelId: String, messageId: String, emoji: String) {
RevoltHttp.put("/channels/$channelId/messages/$messageId/reactions/$emoji".api())
}
suspend fun unreact(channelId: String, messageId: String, emoji: String) {
RevoltHttp.delete("/channels/$channelId/messages/$messageId/reactions/$emoji".api())
}

View File

@ -1,16 +0,0 @@
package chat.revolt.api.routes.custom
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.api
import chat.revolt.api.schemas.Emoji
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
suspend fun fetchEmoji(id: String): Emoji {
val response = RevoltHttp.get("/custom/emoji/$id".api()).bodyAsText()
return RevoltJson.decodeFromString(
Emoji.serializer(),
response
)
}

View File

@ -1,43 +0,0 @@
package chat.revolt.api.routes.invites
import chat.revolt.api.RevoltError
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.api
import chat.revolt.api.schemas.Invite
import chat.revolt.api.schemas.InviteJoined
import chat.revolt.api.schemas.RsResult
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
suspend fun fetchInviteByCode(code: String): RsResult<Invite, RevoltError> {
val response = RevoltHttp.get("/invites/$code".api())
.bodyAsText()
try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
if (error.type != "Server") return RsResult.err(error)
} catch (e: SerializationException) {
// Not an error
}
val invite = RevoltJson.decodeFromString(Invite.serializer(), response)
return RsResult.ok(invite)
}
suspend fun joinInviteByCode(code: String): RsResult<InviteJoined, RevoltError> {
val response = RevoltHttp.post("/invites/$code".api())
.bodyAsText()
try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
if (error.type != "Server") return RsResult.err(error)
} catch (e: SerializationException) {
// Not an error
}
val invite = RevoltJson.decodeFromString(InviteJoined.serializer(), response)
return RsResult.ok(invite)
}

View File

@ -1,12 +0,0 @@
package chat.revolt.api.routes.microservices.health
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.schemas.HealthNotice
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
suspend fun healthCheck(): HealthNotice {
val response = RevoltHttp.get("https://health.revolt.chat/api/health").bodyAsText()
return RevoltJson.decodeFromString(HealthNotice.serializer(), response)
}

View File

@ -1,8 +0,0 @@
package chat.revolt.api.routes.microservices.january
import chat.revolt.api.REVOLT_JANUARY
import java.net.URLEncoder
fun asJanuaryProxyUrl(url: String): String {
return "$REVOLT_JANUARY/proxy?url=${URLEncoder.encode(url, "utf-8")}"
}

View File

@ -1,24 +0,0 @@
package chat.revolt.api.routes.user
import chat.revolt.api.RevoltError
import chat.revolt.api.RevoltHttp
import chat.revolt.api.RevoltJson
import chat.revolt.api.api
import chat.revolt.api.schemas.Channel
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
suspend fun openDM(userId: String): Channel {
val response = RevoltHttp.get("/users/$userId/dm".api())
.bodyAsText()
try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
throw Error(error.type)
} catch (e: SerializationException) {
// Not an error
}
return RevoltJson.decodeFromString(Channel.serializer(), response)
}

View File

@ -1,43 +0,0 @@
package chat.revolt.screens.labs.ui.sandbox
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.navigation.NavController
import chat.revolt.settings.dsl.SettingsPage
import chat.revolt.ui.theme.FragmentMono
@Composable
fun CoreLibSandbox(navController: NavController) {
var greeting by remember { mutableStateOf("<no greeting>") }
SettingsPage(
navController,
title = {
Text(
text = buildAnnotatedString {
pushStyle(SpanStyle(fontFamily = FragmentMono))
append("librevolt")
pop()
append(" Sample")
},
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
) {
Button(onClick = {
greeting = librevolt.greet()
}) {
Text("Greet")
}
Text(greeting)
}
}

View File

@ -1,4 +1,4 @@
package chat.revolt package chat.stoat
import android.app.Application import android.app.Application
import android.os.StrictMode import android.os.StrictMode
@ -8,9 +8,9 @@ import logcat.AndroidLogcatLogger
import logcat.LogPriority import logcat.LogPriority
@HiltAndroidApp @HiltAndroidApp
class RevoltApplication : Application() { class StoatApplication : Application() {
companion object { companion object {
lateinit var instance: RevoltApplication lateinit var instance: StoatApplication
} }
override fun onCreate() { override fun onCreate() {

View File

@ -1,4 +1,4 @@
package chat.revolt.activities package chat.stoat.activities
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -46,19 +46,19 @@ import androidx.core.view.WindowCompat
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.routes.invites.fetchInviteByCode import chat.stoat.api.routes.invites.fetchInviteByCode
import chat.revolt.api.routes.invites.joinInviteByCode import chat.stoat.api.routes.invites.joinInviteByCode
import chat.revolt.api.schemas.Invite import chat.stoat.api.schemas.Invite
import chat.revolt.api.schemas.InviteJoined import chat.stoat.api.schemas.InviteJoined
import chat.revolt.api.schemas.RsResult import chat.stoat.api.schemas.RsResult
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.SyncedSettings import chat.stoat.api.settings.SyncedSettings
import chat.revolt.composables.generic.IconPlaceholder import chat.stoat.composables.generic.IconPlaceholder
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.ui.theme.RevoltTheme import chat.stoat.ui.theme.StoatTheme
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage import com.bumptech.glide.integration.compose.GlideImage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -90,19 +90,19 @@ class InviteViewModel : ViewModel() {
_loadingFinished = loadingFinished _loadingFinished = loadingFinished
} }
private var _inviteResult by mutableStateOf<RsResult<Invite, RevoltError>?>(null) private var _inviteResult by mutableStateOf<RsResult<Invite, StoatAPIError>?>(null)
val inviteResult: RsResult<Invite, RevoltError>? val inviteResult: RsResult<Invite, StoatAPIError>?
get() = _inviteResult get() = _inviteResult
fun setInviteResult(inviteResult: RsResult<Invite, RevoltError>?) { fun setInviteResult(inviteResult: RsResult<Invite, StoatAPIError>?) {
_inviteResult = inviteResult _inviteResult = inviteResult
} }
private var _joinResult by mutableStateOf<RsResult<InviteJoined, RevoltError>?>(null) private var _joinResult by mutableStateOf<RsResult<InviteJoined, StoatAPIError>?>(null)
val joinResult: RsResult<InviteJoined, RevoltError>? val joinResult: RsResult<InviteJoined, StoatAPIError>?
get() = _joinResult get() = _joinResult
fun setJoinResult(joinResult: RsResult<InviteJoined, RevoltError>?) { fun setJoinResult(joinResult: RsResult<InviteJoined, StoatAPIError>?) {
_joinResult = joinResult _joinResult = joinResult
} }
@ -144,7 +144,7 @@ fun InviteScreen(
val inviteValid = if (viewModel.loadingFinished) (viewModel.inviteResult?.ok ?: false) else null val inviteValid = if (viewModel.loadingFinished) (viewModel.inviteResult?.ok ?: false) else null
val invite = viewModel.inviteResult?.value val invite = viewModel.inviteResult?.value
RevoltTheme( StoatTheme(
requestedTheme = LoadedSettings.theme, requestedTheme = LoadedSettings.theme,
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
@ -180,7 +180,7 @@ fun InviteScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
GlideImage( GlideImage(
model = "$REVOLT_FILES/banners/${invite?.serverBanner?.id}/${invite?.serverBanner?.filename}", model = "$STOAT_FILES/banners/${invite?.serverBanner?.id}/${invite?.serverBanner?.filename}",
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
@ -205,7 +205,7 @@ fun InviteScreen(
) { ) {
if (invite?.serverIcon != null) { if (invite?.serverIcon != null) {
RemoteImage( RemoteImage(
url = "$REVOLT_FILES/icons/${invite.serverIcon.id}/${invite.serverIcon.filename}", url = "$STOAT_FILES/icons/${invite.serverIcon.id}/${invite.serverIcon.filename}",
allowAnimation = false, allowAnimation = false,
description = viewModel.inviteResult?.value?.serverName description = viewModel.inviteResult?.value?.serverName
?: stringResource(id = R.string.unknown), ?: stringResource(id = R.string.unknown),
@ -274,7 +274,7 @@ fun InviteScreen(
} }
@Composable @Composable
fun InvalidInviteError(error: RevoltError? = null, onDismissRequest: () -> Unit) { fun InvalidInviteError(error: StoatAPIError? = null, onDismissRequest: () -> Unit) {
AlertDialog( AlertDialog(
onDismissRequest = onDismissRequest, onDismissRequest = onDismissRequest,
icon = { icon = {

View File

@ -1,4 +1,4 @@
package chat.revolt.activities package chat.stoat.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -72,57 +72,57 @@ import androidx.lifecycle.viewModelScope
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import chat.revolt.BuildConfig import chat.stoat.BuildConfig
import chat.revolt.R import chat.stoat.R
import chat.revolt.RevoltApplication import chat.stoat.StoatApplication
import chat.revolt.api.HitRateLimitException import chat.stoat.api.HitRateLimitException
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.routes.microservices.geo.queryGeo import chat.stoat.api.routes.microservices.geo.queryGeo
import chat.revolt.api.routes.microservices.health.healthCheck import chat.stoat.api.routes.microservices.health.healthCheck
import chat.revolt.api.routes.onboard.needsOnboarding import chat.stoat.api.routes.onboard.needsOnboarding
import chat.revolt.api.schemas.HealthNotice import chat.stoat.api.schemas.HealthNotice
import chat.revolt.api.settings.Experiments import chat.stoat.api.settings.Experiments
import chat.revolt.api.settings.GeoStateProvider import chat.stoat.api.settings.GeoStateProvider
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.SyncedSettings import chat.stoat.api.settings.SyncedSettings
import chat.revolt.composables.generic.HealthAlert import chat.stoat.composables.generic.HealthAlert
import chat.revolt.composables.voice.VoicePermissionSwitch import chat.stoat.composables.voice.VoicePermissionSwitch
import chat.revolt.composables.voice.VoiceSheet import chat.stoat.composables.voice.VoiceSheet
import chat.revolt.material.EasingTokens import chat.stoat.material.EasingTokens
import chat.revolt.ndk.NativeLibraries import chat.stoat.ndk.NativeLibraries
import chat.revolt.persistence.KVStorage import chat.stoat.persistence.KVStorage
import chat.revolt.screens.DefaultDestinationScreen import chat.stoat.screens.DefaultDestinationScreen
import chat.revolt.screens.about.AboutScreen import chat.stoat.screens.about.AboutScreen
import chat.revolt.screens.about.AttributionScreen import chat.stoat.screens.about.AttributionScreen
import chat.revolt.screens.chat.ChatRouterScreen import chat.stoat.screens.chat.ChatRouterScreen
import chat.revolt.screens.chat.views.channel.ChannelScreen import chat.stoat.screens.chat.views.channel.ChannelScreen
import chat.revolt.screens.create.CreateGroupScreen import chat.stoat.screens.create.CreateGroupScreen
import chat.revolt.screens.labs.LabsRootScreen import chat.stoat.screens.labs.LabsRootScreen
import chat.revolt.screens.login.LoginGreetingScreen import chat.stoat.screens.login.LoginGreetingScreen
import chat.revolt.screens.login.LoginScreen import chat.stoat.screens.login.LoginScreen
import chat.revolt.screens.login.MfaScreen import chat.stoat.screens.login.MfaScreen
import chat.revolt.screens.login2.InitScreen import chat.stoat.screens.login2.InitScreen
import chat.revolt.screens.main.MainScreen import chat.stoat.screens.main.MainScreen
import chat.revolt.screens.register.OnboardingScreen import chat.stoat.screens.register.OnboardingScreen
import chat.revolt.screens.register.RegisterDetailsScreen import chat.stoat.screens.register.RegisterDetailsScreen
import chat.revolt.screens.register.RegisterGreetingScreen import chat.stoat.screens.register.RegisterGreetingScreen
import chat.revolt.screens.register.RegisterVerifyScreen import chat.stoat.screens.register.RegisterVerifyScreen
import chat.revolt.screens.services.DiscoverScreen import chat.stoat.screens.services.DiscoverScreen
import chat.revolt.screens.settings.AppearanceSettingsScreen import chat.stoat.screens.settings.AppearanceSettingsScreen
import chat.revolt.screens.settings.ChangelogsSettingsScreen import chat.stoat.screens.settings.ChangelogsSettingsScreen
import chat.revolt.screens.settings.ChatSettingsScreen import chat.stoat.screens.settings.ChatSettingsScreen
import chat.revolt.screens.settings.DebugSettingsScreen import chat.stoat.screens.settings.DebugSettingsScreen
import chat.revolt.screens.settings.ExperimentsSettingsScreen import chat.stoat.screens.settings.ExperimentsSettingsScreen
import chat.revolt.screens.settings.LanguagePickerSettingsScreen import chat.stoat.screens.settings.LanguagePickerSettingsScreen
import chat.revolt.screens.settings.ProfileSettingsScreen import chat.stoat.screens.settings.ProfileSettingsScreen
import chat.revolt.screens.settings.SessionSettingsScreen import chat.stoat.screens.settings.SessionSettingsScreen
import chat.revolt.screens.settings.SettingsScreen import chat.stoat.screens.settings.SettingsScreen
import chat.revolt.screens.settings.channel.ChannelSettingsHome import chat.stoat.screens.settings.channel.ChannelSettingsHome
import chat.revolt.screens.settings.channel.ChannelSettingsOverview import chat.stoat.screens.settings.channel.ChannelSettingsOverview
import chat.revolt.screens.settings.channel.ChannelSettingsPermissions import chat.stoat.screens.settings.channel.ChannelSettingsPermissions
import chat.revolt.ui.theme.RevoltTheme import chat.stoat.ui.theme.StoatTheme
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -161,9 +161,9 @@ class MainActivityViewModel @Inject constructor(
} }
} }
private suspend fun canReachRevolt(): Boolean { private suspend fun canReachStoat(): Boolean {
try { try {
val res = RevoltHttp.get("/".api()) val res = StoatHttp.get("/".api())
return res.status.value == 200 return res.status.value == 200
} catch (e: Exception) { } catch (e: Exception) {
return false return false
@ -218,17 +218,17 @@ class MainActivityViewModel @Inject constructor(
Log.d( Log.d(
"MainActivity", "MainActivity",
"We have a session token, checking if it's valid and if we can still reach Revolt" "We have a session token, checking if it's valid and if we can still reach Stoat"
) )
val canReachRevolt = canReachRevolt() val canReachStoat = canReachStoat()
val valid = try { val valid = try {
RevoltAPI.checkSessionToken(token) StoatAPI.checkSessionToken(token)
} catch (e: Throwable) { } catch (e: Throwable) {
false false
} }
if (canReachRevolt && !valid) { if (canReachStoat && !valid) {
Log.d("MainActivity", "Session token is invalid, could not log in") Log.d("MainActivity", "Session token is invalid, could not log in")
couldNotLogIn.emit(true) couldNotLogIn.emit(true)
} else { } else {
@ -255,8 +255,8 @@ class MainActivityViewModel @Inject constructor(
try { try {
Log.d("MainActivity", "Onboarding state is complete, logging in") Log.d("MainActivity", "Onboarding state is complete, logging in")
RevoltAPI.loginAs(token) StoatAPI.loginAs(token)
RevoltAPI.setSessionId(id) StoatAPI.setSessionId(id)
if (Experiments.usePolar.isEnabled) { if (Experiments.usePolar.isEnabled) {
startWithDestination("main") startWithDestination("main")
} else { } else {
@ -331,7 +331,7 @@ class MainActivity : AppCompatActivity() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance) DynamicColors.applyToActivitiesIfAvailable(StoatApplication.instance)
@Suppress("DEPRECATION") // We are fixing a bug in the splash screen @Suppress("DEPRECATION") // We are fixing a bug in the splash screen
window.statusBarColor = Color.Transparent.toArgb() window.statusBarColor = Color.Transparent.toArgb()
} }
@ -340,7 +340,7 @@ class MainActivity : AppCompatActivity() {
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) { override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
DynamicColors.applyToActivityIfAvailable(this) DynamicColors.applyToActivityIfAvailable(this)
DynamicColors.applyToActivitiesIfAvailable(RevoltApplication.instance) DynamicColors.applyToActivitiesIfAvailable(StoatApplication.instance)
@Suppress("DEPRECATION") // We are fixing a bug in the splash screen @Suppress("DEPRECATION") // We are fixing a bug in the splash screen
window.statusBarColor = Color.Transparent.toArgb() window.statusBarColor = Color.Transparent.toArgb()
} }
@ -358,7 +358,7 @@ class MainActivity : AppCompatActivity() {
window.statusBarColor = Color.Transparent.toArgb() window.statusBarColor = Color.Transparent.toArgb()
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
RevoltAPI.hydrateFromPersistentCache() StoatAPI.hydrateFromPersistentCache()
setContent { setContent {
val windowSizeClass = calculateWindowSizeClass(this) val windowSizeClass = calculateWindowSizeClass(this)
@ -426,10 +426,10 @@ class MainActivity : AppCompatActivity() {
} }
} }
val RevoltTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo) val StoatTweenInt: FiniteAnimationSpec<IntOffset> = tween(400, easing = EaseInOutExpo)
val RevoltTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo) val StoatTweenFloat: FiniteAnimationSpec<Float> = tween(400, easing = EaseInOutExpo)
val RevoltTweenDp: FiniteAnimationSpec<Dp> = tween(400, easing = EaseInOutExpo) val StoatTweenDp: FiniteAnimationSpec<Dp> = tween(400, easing = EaseInOutExpo)
val RevoltTweenColour: FiniteAnimationSpec<Color> = tween(400, easing = EaseInOutExpo) val StoatTweenColour: FiniteAnimationSpec<Color> = tween(400, easing = EaseInOutExpo)
val NavTweenInt: FiniteAnimationSpec<IntOffset> = tween(350, easing = EaseInOutExpo) val NavTweenInt: FiniteAnimationSpec<IntOffset> = tween(350, easing = EaseInOutExpo)
val NavTweenFloat: FiniteAnimationSpec<Float> = tween(350, easing = EaseInOutExpo) val NavTweenFloat: FiniteAnimationSpec<Float> = tween(350, easing = EaseInOutExpo)
@ -479,7 +479,7 @@ fun AppEntrypoint(
val navController = rememberNavController() val navController = rememberNavController()
RevoltTheme( StoatTheme(
requestedTheme = LoadedSettings.theme, requestedTheme = LoadedSettings.theme,
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
@ -621,7 +621,7 @@ fun AppEntrypoint(
easing = EasingTokens.EmphasizedDecelerate easing = EasingTokens.EmphasizedDecelerate
), ),
initialOffset = { it / 3 } initialOffset = { it / 3 }
) + fadeIn(animationSpec = RevoltTweenFloat) ) + fadeIn(animationSpec = StoatTweenFloat)
} }
) { ) {
ChatRouterScreen( ChatRouterScreen(
@ -655,7 +655,7 @@ fun AppEntrypoint(
easing = EasingTokens.EmphasizedDecelerate easing = EasingTokens.EmphasizedDecelerate
), ),
initialOffset = { it / 3 } initialOffset = { it / 3 }
) + fadeIn(animationSpec = RevoltTweenFloat) + scaleIn( ) + fadeIn(animationSpec = StoatTweenFloat) + scaleIn(
animationSpec = tween( animationSpec = tween(
400, 400,
easing = EasingTokens.EmphasizedDecelerate easing = EasingTokens.EmphasizedDecelerate
@ -677,7 +677,7 @@ fun AppEntrypoint(
easing = EasingTokens.EmphasizedDecelerate easing = EasingTokens.EmphasizedDecelerate
), ),
initialOffset = { it } initialOffset = { it }
) + fadeIn(animationSpec = RevoltTweenFloat) ) + fadeIn(animationSpec = StoatTweenFloat)
}, },
exitTransition = { exitTransition = {
slideOutOfContainer( slideOutOfContainer(
@ -687,7 +687,7 @@ fun AppEntrypoint(
easing = EasingTokens.EmphasizedDecelerate easing = EasingTokens.EmphasizedDecelerate
), ),
targetOffset = { it } targetOffset = { it }
) + fadeOut(animationSpec = RevoltTweenFloat) ) + fadeOut(animationSpec = StoatTweenFloat)
} }
) { backStackEntry -> ) { backStackEntry ->
val channelId = backStackEntry.arguments?.getString("channelId") ?: "" val channelId = backStackEntry.arguments?.getString("channelId") ?: ""

View File

@ -1,4 +1,4 @@
package chat.revolt.activities package chat.stoat.activities
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@ -50,25 +50,25 @@ import androidx.documentfile.provider.DocumentFile
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.ChannelUtils import chat.stoat.api.internals.ChannelUtils
import chat.revolt.api.routes.channel.sendMessage import chat.stoat.api.routes.channel.sendMessage
import chat.revolt.api.routes.microservices.autumn.FileArgs import chat.stoat.api.routes.microservices.autumn.FileArgs
import chat.revolt.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE import chat.stoat.api.routes.microservices.autumn.MAX_ATTACHMENTS_PER_MESSAGE
import chat.revolt.api.routes.microservices.autumn.uploadToAutumn import chat.stoat.api.routes.microservices.autumn.uploadToAutumn
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.SyncedSettings import chat.stoat.api.settings.SyncedSettings
import chat.revolt.composables.chat.MessageField import chat.stoat.composables.chat.MessageField
import chat.revolt.composables.emoji.EmojiPicker import chat.stoat.composables.emoji.EmojiPicker
import chat.revolt.composables.screens.chat.AttachmentManager import chat.stoat.composables.screens.chat.AttachmentManager
import chat.revolt.composables.screens.chat.drawer.ChannelItem import chat.stoat.composables.screens.chat.drawer.ChannelItem
import chat.revolt.composables.screens.chat.drawer.ChannelItemIconType import chat.stoat.composables.screens.chat.drawer.ChannelItemIconType
import chat.revolt.composables.screens.chat.drawer.DMOrGroupItem import chat.stoat.composables.screens.chat.drawer.DMOrGroupItem
import chat.revolt.persistence.KVStorage import chat.stoat.persistence.KVStorage
import chat.revolt.screens.chat.views.channel.ChannelScreenActivePane import chat.stoat.screens.chat.views.channel.ChannelScreenActivePane
import chat.revolt.ui.theme.RevoltTheme import chat.stoat.ui.theme.StoatTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import io.ktor.http.ContentType import io.ktor.http.ContentType
@ -180,10 +180,10 @@ class ShareTargetScreenViewModel @Inject constructor(
} }
suspend fun initialiseAPI() { suspend fun initialiseAPI() {
if (!RevoltAPI.isLoggedIn()) { if (!StoatAPI.isLoggedIn()) {
val token = kvStorage.get("sessionToken") ?: return val token = kvStorage.get("sessionToken") ?: return
RevoltAPI.loginAs(token) StoatAPI.loginAs(token)
RevoltAPI.initialize() StoatAPI.initialize()
} }
apiIsReady = true apiIsReady = true
} }
@ -277,7 +277,7 @@ fun ShareTargetScreen(
var channelSearchContent by remember { mutableStateOf("") } var channelSearchContent by remember { mutableStateOf("") }
var selectedChannel by rememberSaveable { mutableStateOf<String?>(null) } var selectedChannel by rememberSaveable { mutableStateOf<String?>(null) }
RevoltTheme( StoatTheme(
requestedTheme = LoadedSettings.theme, requestedTheme = LoadedSettings.theme,
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {
@ -326,7 +326,7 @@ fun ShareTargetScreen(
Box( Box(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
val filteredChannels = RevoltAPI.channelCache.values.asSequence().filter { val filteredChannels = StoatAPI.channelCache.values.asSequence().filter {
it.name?.contains( it.name?.contains(
channelSearchContent, channelSearchContent,
ignoreCase = true ignoreCase = true
@ -346,7 +346,7 @@ fun ShareTargetScreen(
ChannelType.Group, ChannelType.DirectMessage -> DMOrGroupItem( ChannelType.Group, ChannelType.DirectMessage -> DMOrGroupItem(
channel = channel, channel = channel,
partner = ChannelUtils.resolveDMPartner(channel)?.let { u -> partner = ChannelUtils.resolveDMPartner(channel)?.let { u ->
RevoltAPI.userCache[u] StoatAPI.userCache[u]
}, },
isCurrent = selectedChannel == channel.id, isCurrent = selectedChannel == channel.id,
hasUnread = false, hasUnread = false,
@ -421,9 +421,9 @@ fun ShareTargetScreen(
} }
} }
}, },
channelType = RevoltAPI.channelCache[selectedChannel]?.channelType channelType = StoatAPI.channelCache[selectedChannel]?.channelType
?: ChannelType.TextChannel, ?: ChannelType.TextChannel,
channelName = RevoltAPI.channelCache[selectedChannel]?.name ?: "", channelName = StoatAPI.channelCache[selectedChannel]?.name ?: "",
) )
AnimatedVisibility(viewModel.activeBottomPane is ChannelScreenActivePane.EmojiPicker) { AnimatedVisibility(viewModel.activeBottomPane is ChannelScreenActivePane.EmojiPicker) {

View File

@ -1,4 +1,4 @@
package chat.revolt.activities.media package chat.stoat.activities.media
import android.content.ContentValues import android.content.ContentValues
import android.content.Intent import android.content.Intent
@ -40,14 +40,14 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.schemas.AutumnResource import chat.stoat.api.schemas.AutumnResource
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.SyncedSettings import chat.stoat.api.settings.SyncedSettings
import chat.revolt.providers.getAttachmentContentUri import chat.stoat.providers.getAttachmentContentUri
import chat.revolt.ui.theme.RevoltTheme import chat.stoat.ui.theme.StoatTheme
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.readBytes import io.ktor.client.statement.readBytes
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -85,7 +85,7 @@ class ImageViewActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) { fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
val resourceUrl = "$REVOLT_FILES/attachments/${resource.id}/${resource.filename}" val resourceUrl = "$STOAT_FILES/attachments/${resource.id}/${resource.filename}"
val context = LocalContext.current val context = LocalContext.current
@ -151,7 +151,7 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
) )
}?.let { uri -> }?.let { uri ->
context.contentResolver.openOutputStream(uri).use { stream -> context.contentResolver.openOutputStream(uri).use { stream ->
val image = RevoltHttp.get(resourceUrl).readBytes() val image = StoatHttp.get(resourceUrl).readBytes()
stream?.write(image) stream?.write(image)
context.applicationContext.let { context.applicationContext.let {
@ -182,7 +182,7 @@ fun ImageViewScreen(resource: AutumnResource, onClose: () -> Unit = {}) {
} }
} }
RevoltTheme( StoatTheme(
requestedTheme = LoadedSettings.theme, requestedTheme = LoadedSettings.theme,
colourOverrides = SyncedSettings.android.colourOverrides colourOverrides = SyncedSettings.android.colourOverrides
) { ) {

View File

@ -1,4 +1,4 @@
package chat.revolt.activities.media package chat.stoat.activities.media
import android.content.ContentValues import android.content.ContentValues
import android.content.Intent import android.content.Intent
@ -18,12 +18,12 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.schemas.AutumnResource import chat.stoat.api.schemas.AutumnResource
import chat.revolt.databinding.ActivityVideoplayerBinding import chat.stoat.databinding.ActivityVideoplayerBinding
import chat.revolt.providers.getAttachmentContentUri import chat.stoat.providers.getAttachmentContentUri
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import io.ktor.client.request.get import io.ktor.client.request.get
@ -54,7 +54,7 @@ class VideoViewActivity : FragmentActivity() {
} }
val resourceUrl = val resourceUrl =
"$REVOLT_FILES/attachments/${autumnResource.id}/${autumnResource.filename}" "$STOAT_FILES/attachments/${autumnResource.id}/${autumnResource.filename}"
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
@ -195,7 +195,7 @@ class VideoViewActivity : FragmentActivity() {
) )
}?.let { uri -> }?.let { uri ->
this@VideoViewActivity.contentResolver.openOutputStream(uri).use { stream -> this@VideoViewActivity.contentResolver.openOutputStream(uri).use { stream ->
val video = RevoltHttp.get(resourceUrl).readBytes() val video = StoatHttp.get(resourceUrl).readBytes()
stream?.write(video) stream?.write(video)
this@VideoViewActivity.applicationContext.let { this@VideoViewActivity.applicationContext.let {

View File

@ -1,4 +1,4 @@
package chat.revolt.activities.voice package chat.stoat.activities.voice
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -57,12 +57,12 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.stoat.R
import chat.revolt.composables.generic.Presence import chat.stoat.composables.generic.Presence
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.composables.generic.presenceColour import chat.stoat.composables.generic.presenceColour
import chat.revolt.ui.theme.RevoltTheme import chat.stoat.ui.theme.StoatTheme
import chat.revolt.ui.theme.Theme import chat.stoat.ui.theme.Theme
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -80,8 +80,8 @@ class IncomingActivity : ComponentActivity() {
@Composable @Composable
fun IncomingCall() { fun IncomingCall() {
RevoltTheme( StoatTheme(
requestedTheme = if (isSystemInDarkTheme()) Theme.Revolt else Theme.Light requestedTheme = if (isSystemInDarkTheme()) Theme.Default else Theme.Light
) { ) {
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) { CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onBackground) {
IncomingCallInner() IncomingCallInner()

View File

@ -1,25 +1,25 @@
package chat.revolt.api package chat.stoat.api
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import chat.revolt.BuildConfig import chat.stoat.BuildConfig
import chat.revolt.RevoltApplication import chat.stoat.StoatApplication
import chat.revolt.api.RevoltAPI.initialize import chat.stoat.api.StoatAPI.initialize
import chat.revolt.api.internals.Members import chat.stoat.api.internals.Members
import chat.revolt.api.realtime.DisconnectionState import chat.stoat.api.realtime.DisconnectionState
import chat.revolt.api.realtime.RealtimeSocket import chat.stoat.api.realtime.RealtimeSocket
import chat.revolt.api.routes.user.fetchSelf import chat.stoat.api.routes.user.fetchSelf
import chat.revolt.api.schemas.AutumnResource import chat.stoat.api.schemas.AutumnResource
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.schemas.Emoji import chat.stoat.api.schemas.Emoji
import chat.revolt.api.schemas.Message import chat.stoat.api.schemas.Message
import chat.revolt.api.schemas.Server import chat.stoat.api.schemas.Server
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import chat.revolt.api.unreads.Unreads import chat.stoat.api.unreads.Unreads
import chat.revolt.persistence.Database import chat.stoat.persistence.Database
import chat.revolt.persistence.SqlStorage import chat.stoat.persistence.SqlStorage
import com.chuckerteam.chucker.api.ChuckerCollector import com.chuckerteam.chucker.api.ChuckerCollector
import com.chuckerteam.chucker.api.ChuckerInterceptor import com.chuckerteam.chucker.api.ChuckerInterceptor
import com.chuckerteam.chucker.api.RetentionManager import com.chuckerteam.chucker.api.RetentionManager
@ -51,49 +51,49 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.net.SocketException import java.net.SocketException
import chat.revolt.api.schemas.Channel as ChannelSchema import chat.stoat.api.schemas.Channel as ChannelSchema
private const val USE_ALPHA_API = false private const val USE_ALPHA_API = false
val REVOLT_BASE = val STOAT_BASE =
if (USE_ALPHA_API) "https://alpha.revolt.chat/api" else "https://api.revolt.chat/0.8" if (USE_ALPHA_API) "https://alpha.revolt.chat/api" else "https://api.stoat.chat/0.8"
const val REVOLT_SUPPORT = "https://support.revolt.chat" const val STOAT_SUPPORT = "https://support.stoat.chat"
const val REVOLT_MARKETING = "https://revolt.chat" const val STOAT_MARKETING = "https://stoat.chat"
val REVOLT_FILES = val STOAT_FILES =
if (USE_ALPHA_API) "https://alpha.revolt.chat/autumn" else "https://cdn.revoltusercontent.com" if (USE_ALPHA_API) "https://alpha.revolt.chat/autumn" else "https://cdn.stoatusercontent.com"
val REVOLT_JANUARY = val STOAT_PROXY =
if (USE_ALPHA_API) "https://alpha.revolt.chat/january" else "https://jan.revolt.chat" if (USE_ALPHA_API) "https://alpha.revolt.chat/january" else "https://proxy.stoatusercontent.com"
const val REVOLT_APP = "https://app.revolt.chat" const val STOAT_WEB_APP = "https://stoat.chat/app"
const val REVOLT_INVITES = "https://rvlt.gg" const val STOAT_INVITES = "https://stt.gg"
val REVOLT_WEBSOCKET = val STOAT_WEBSOCKET =
if (USE_ALPHA_API) "wss://alpha.revolt.chat/ws" else "wss://ws.revolt.chat" if (USE_ALPHA_API) "wss://alpha.revolt.chat/ws" else "wss://events.stoat.chat"
const val REVOLT_KJBOOK = "https://revoltchat.github.io/android" const val STOAT_KJBOOK = "https://revoltchat.github.io/android"
fun String.api(): String { fun String.api(): String {
return "$REVOLT_BASE$this" return "$STOAT_BASE$this"
} }
fun buildUserAgent(accessMethod: String = "Ktor"): String { fun buildUserAgent(accessMethod: String = "Ktor"): String {
return "$accessMethod RevoltAndroid/${BuildConfig.VERSION_NAME} " + return "$accessMethod StoatForAndroid/${BuildConfig.VERSION_NAME} " +
"${BuildConfig.APPLICATION_ID} Android/${android.os.Build.VERSION.SDK_INT} " + "${BuildConfig.APPLICATION_ID} Android/${android.os.Build.VERSION.SDK_INT} " +
"(${android.os.Build.MANUFACTURER} ${android.os.Build.DEVICE}) Kotlin/${KotlinVersion.CURRENT}" "(${android.os.Build.MANUFACTURER} ${android.os.Build.DEVICE}) Kotlin/${KotlinVersion.CURRENT}"
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
val RevoltJson = Json { val StoatJson = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
explicitNulls = false explicitNulls = false
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
val RevoltCbor = Cbor { val StoatCbor = Cbor {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }
val RevoltHttp = HttpClient(OkHttp) { val StoatHttp = HttpClient(OkHttp) {
install(DefaultRequest) install(DefaultRequest)
install(ContentNegotiation) { install(ContentNegotiation) {
json(RevoltJson) json(StoatJson)
} }
install(WebSockets) install(WebSockets)
@ -112,15 +112,15 @@ val RevoltHttp = HttpClient(OkHttp) {
install(Logging) { level = LogLevel.INFO } install(Logging) { level = LogLevel.INFO }
val chuckerCollector = ChuckerCollector( val chuckerCollector = ChuckerCollector(
context = RevoltApplication.instance, context = StoatApplication.instance,
showNotification = true, showNotification = true,
retentionPeriod = RetentionManager.Period.ONE_DAY retentionPeriod = RetentionManager.Period.ONE_DAY
) )
val chuckerInterceptor = ChuckerInterceptor.Builder(RevoltApplication.instance) val chuckerInterceptor = ChuckerInterceptor.Builder(StoatApplication.instance)
.collector(chuckerCollector) .collector(chuckerCollector)
.maxContentLength(250_000L) .maxContentLength(250_000L)
.redactHeaders(RevoltAPI.TOKEN_HEADER_NAME) .redactHeaders(StoatAPI.TOKEN_HEADER_NAME)
.alwaysReadResponseBody(true) .alwaysReadResponseBody(true)
.createShortcut(false) .createShortcut(false)
.build() .build()
@ -129,8 +129,8 @@ val RevoltHttp = HttpClient(OkHttp) {
addInterceptor { chain -> addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
.apply { .apply {
if (chain.request().headers[RevoltAPI.TOKEN_HEADER_NAME] == null) { if (chain.request().headers[StoatAPI.TOKEN_HEADER_NAME] == null) {
header(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken) header(StoatAPI.TOKEN_HEADER_NAME, StoatAPI.sessionToken)
} }
} }
.build() .build()
@ -140,14 +140,14 @@ val RevoltHttp = HttpClient(OkHttp) {
} }
defaultRequest { defaultRequest {
url(REVOLT_BASE) url(STOAT_BASE)
header("User-Agent", buildUserAgent()) header("User-Agent", buildUserAgent())
} }
} }
val mainHandler = Handler(Looper.getMainLooper()) val mainHandler = Handler(Looper.getMainLooper())
object RevoltAPI { object StoatAPI {
const val TOKEN_HEADER_NAME = "x-session-token" const val TOKEN_HEADER_NAME = "x-session-token"
val userCache = mutableStateMapOf<String, User>() val userCache = mutableStateMapOf<String, User>()
@ -369,7 +369,7 @@ object RevoltAPI {
} }
@Serializable @Serializable
data class RevoltError(val type: String) data class StoatAPIError(val type: String)
@Serializable @Serializable
data class RateLimitResponse(@SerialName("retry_after") val retryAfter: Int) { data class RateLimitResponse(@SerialName("retry_after") val retryAfter: Int) {

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import android.util.Log import android.util.Log
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.Brush.Companion.linearGradient
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import chat.revolt.api.internals.colour.CSSColours import chat.stoat.api.internals.colour.CSSColours
fun Brush.Companion.solidColor(colour: Color) = SolidColor(colour) fun Brush.Companion.solidColor(colour: Color) = SolidColor(colour)

View File

@ -1,13 +1,13 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.Server import chat.stoat.api.schemas.Server
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
sealed class CategorisedChannelList { sealed class CategorisedChannelList {
data class Channel(val channel: chat.revolt.api.schemas.Channel) : CategorisedChannelList() data class Channel(val channel: chat.stoat.api.schemas.Channel) : CategorisedChannelList()
data class Category(val category: chat.revolt.api.schemas.Category) : CategorisedChannelList() data class Category(val category: chat.stoat.api.schemas.Category) : CategorisedChannelList()
} }
object ChannelUtils { object ChannelUtils {
@ -19,7 +19,7 @@ object ChannelUtils {
*/ */
fun resolveName(channel: Channel): String? { fun resolveName(channel: Channel): String? {
return channel.name return channel.name
?: RevoltAPI.userCache[channel.recipients?.first { u -> u != RevoltAPI.selfId }]?.let { ?: StoatAPI.userCache[channel.recipients?.first { u -> u != StoatAPI.selfId }]?.let {
User.resolveDefaultName( User.resolveDefaultName(
it it
) )
@ -27,7 +27,7 @@ object ChannelUtils {
} }
fun resolveDMPartner(channel: Channel): String? { fun resolveDMPartner(channel: Channel): String? {
return channel.recipients?.firstOrNull { u -> u != RevoltAPI.selfId } return channel.recipients?.firstOrNull { u -> u != StoatAPI.selfId }
} }
fun categoriseServerFlat(server: Server): List<CategorisedChannelList> { fun categoriseServerFlat(server: Server): List<CategorisedChannelList> {
@ -42,7 +42,7 @@ object ChannelUtils {
} ?: true } ?: true
} }
?.mapNotNull { ?.mapNotNull {
RevoltAPI.channelCache[it]?.let { it1 -> StoatAPI.channelCache[it]?.let { it1 ->
CategorisedChannelList.Channel(it1) CategorisedChannelList.Channel(it1)
} }
} ?: emptyList() } ?: emptyList()
@ -53,7 +53,7 @@ object ChannelUtils {
categories.forEach { categories.forEach {
output.add(it) output.add(it)
val channels = it.category.channels?.mapNotNull { c -> val channels = it.category.channels?.mapNotNull { c ->
RevoltAPI.channelCache[c]?.let { it1 -> StoatAPI.channelCache[c]?.let { it1 ->
CategorisedChannelList.Channel(it1) CategorisedChannelList.Channel(it1)
} }
} ?: emptyList() } ?: emptyList()

View File

@ -1,13 +1,13 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.SpecialUsers.PLATFORM_MODERATION_USER import chat.stoat.api.internals.SpecialUsers.PLATFORM_MODERATION_USER
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
object DirectMessages { object DirectMessages {
fun unreadDMs(): List<Channel> { fun unreadDMs(): List<Channel> {
return RevoltAPI.channelCache.values return StoatAPI.channelCache.values
.filter { .filter {
it.channelType in listOf( it.channelType in listOf(
ChannelType.DirectMessage, ChannelType.Group ChannelType.DirectMessage, ChannelType.Group
@ -15,7 +15,7 @@ object DirectMessages {
} }
.filter { .filter {
it.id?.let { id -> it.id?.let { id ->
RevoltAPI.unreads.hasUnread( StoatAPI.unreads.hasUnread(
id, id,
it.lastMessageID!!, it.lastMessageID!!,
serverId = null serverId = null

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper

View File

@ -1,35 +1,35 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
object FriendRequests { object FriendRequests {
fun getIncoming(): List<User> { fun getIncoming(): List<User> {
return RevoltAPI.userCache.values.filter { user -> return StoatAPI.userCache.values.filter { user ->
user.relationship == "Incoming" user.relationship == "Incoming"
} }
} }
fun getOutgoing(): List<User> { fun getOutgoing(): List<User> {
return RevoltAPI.userCache.values.filter { user -> return StoatAPI.userCache.values.filter { user ->
user.relationship == "Outgoing" user.relationship == "Outgoing"
} }
} }
fun getBlocked(): List<User> { fun getBlocked(): List<User> {
return RevoltAPI.userCache.values.filter { user -> return StoatAPI.userCache.values.filter { user ->
user.relationship == "Blocked" user.relationship == "Blocked"
} }
} }
fun getOnlineFriends(): List<User> { fun getOnlineFriends(): List<User> {
return RevoltAPI.userCache.values.filter { user -> return StoatAPI.userCache.values.filter { user ->
user.relationship == "Friend" && user.online == true user.relationship == "Friend" && user.online == true
} }
} }
fun getFriends(excludeOnline: Boolean = false): List<User> { fun getFriends(excludeOnline: Boolean = false): List<User> {
return RevoltAPI.userCache.values.filter { user -> return StoatAPI.userCache.values.filter { user ->
user.relationship == "Friend" && if (excludeOnline) user.online == false else true user.relationship == "Friend" && if (excludeOnline) user.online == false else true
} }
} }

View File

@ -1,6 +1,6 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
class Members { class Members {
// memberCache (mapping of serverId to userId to member) // memberCache (mapping of serverId to userId to member)

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
/** /**
* Flags for messages that can be set to modify their behavior. * Flags for messages that can be set to modify their behavior.

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
enum class PermissionBit(val value: Long) { enum class PermissionBit(val value: Long) {
// * Generic permissions // * Generic permissions

View File

@ -1,13 +1,13 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
object ResourceLocations { object ResourceLocations {
fun userAvatarUrl(user: User?): String { fun userAvatarUrl(user: User?): String {
if (user?.avatar != null) { if (user?.avatar != null) {
return "$REVOLT_FILES/avatars/${user.avatar.id}" return "$STOAT_FILES/avatars/${user.avatar.id}"
} }
return "/users/${(user?.id ?: "").ifBlank { "0".repeat(26) }}/default_avatar".api() return "/users/${(user?.id ?: "").ifBlank { "0".repeat(26) }}/default_avatar".api()
} }

View File

@ -1,13 +1,13 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
import chat.revolt.api.schemas.PermissionDescription import chat.stoat.api.schemas.PermissionDescription
import chat.revolt.api.schemas.Role import chat.stoat.api.schemas.Role
import chat.revolt.api.schemas.Server import chat.stoat.api.schemas.Server
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
object Roles { object Roles {
@ -27,8 +27,8 @@ object Roles {
withColour: Boolean = false, withColour: Boolean = false,
hoisted: Boolean = false hoisted: Boolean = false
): Role? { ): Role? {
val server = RevoltAPI.serverCache[serverId] ?: return null val server = StoatAPI.serverCache[serverId] ?: return null
val member = RevoltAPI.members.getMember(serverId, userId) ?: return null val member = StoatAPI.members.getMember(serverId, userId) ?: return null
val roles = member.roles?.map { roleId -> val roles = member.roles?.map { roleId ->
server.roles?.get(roleId) server.roles?.get(roleId)
@ -43,13 +43,13 @@ object Roles {
} }
fun inOrder(serverId: String, predicate: (Role) -> Boolean): List<Role> { fun inOrder(serverId: String, predicate: (Role) -> Boolean): List<Role> {
val server = RevoltAPI.serverCache[serverId] ?: return emptyList() val server = StoatAPI.serverCache[serverId] ?: return emptyList()
return server.roles?.values?.filter(predicate)?.sortedBy { it.rank } ?: emptyList() return server.roles?.values?.filter(predicate)?.sortedBy { it.rank } ?: emptyList()
} }
fun permissionFor(server: Server, member: Member): Long { fun permissionFor(server: Server, member: Member): Long {
val user = RevoltAPI.userCache[member.id?.user] ?: return 0L val user = StoatAPI.userCache[member.id?.user] ?: return 0L
if (user.privileged == true) return PermissionBit.GrantAllSafe.value if (user.privileged == true) return PermissionBit.GrantAllSafe.value
if (server.owner == member.id?.user) return PermissionBit.GrantAllSafe.value if (server.owner == member.id?.user) return PermissionBit.GrantAllSafe.value
@ -80,13 +80,13 @@ object Roles {
ChannelType.Group -> if (channel.owner == user?.id) PermissionBit.GrantAllSafe.value else BitDefaults.DirectMessages ChannelType.Group -> if (channel.owner == user?.id) PermissionBit.GrantAllSafe.value else BitDefaults.DirectMessages
ChannelType.TextChannel, ChannelType.VoiceChannel -> { ChannelType.TextChannel, ChannelType.VoiceChannel -> {
val server = RevoltAPI.serverCache[channel.server] val server = StoatAPI.serverCache[channel.server]
// FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch // FIXME this is a stupid patch to prevent it from showing "no permission" on a channel on launch
?: return PermissionBit.GrantAllSafe.value ?: return PermissionBit.GrantAllSafe.value
if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value if (server.owner == user?.id) return PermissionBit.GrantAllSafe.value
val chMember = member ?: RevoltAPI.members.getMember( val chMember = member ?: StoatAPI.members.getMember(
server.id ?: return 0L, server.id ?: return 0L,
user?.id ?: return 0L user?.id ?: return 0L
) ?: return 0L ) ?: return 0L

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import android.content.Context import android.content.Context
import android.graphics.RuntimeShader import android.graphics.RuntimeShader

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import android.graphics.LinearGradient import android.graphics.LinearGradient
import android.graphics.Shader import android.graphics.Shader
@ -6,7 +6,7 @@ import android.util.Log
import android.widget.TextView import android.widget.TextView
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import chat.revolt.api.internals.colour.CSSColours import chat.stoat.api.internals.colour.CSSColours
import com.google.android.material.color.MaterialColors import com.google.android.material.color.MaterialColors
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.random.Random import kotlin.random.Random

View File

@ -1,8 +1,9 @@
package chat.revolt.api.internals package chat.stoat.api.internals
import androidx.core.net.toUri import androidx.core.net.toUri
import chat.revolt.api.RevoltCbor import chat.stoat.api.STOAT_MARKETING
import chat.revolt.api.schemas.User import chat.stoat.api.StoatCbor
import chat.stoat.api.schemas.User
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
@ -21,8 +22,8 @@ data class UserQRContents(
object UserQR { object UserQR {
@OptIn(ExperimentalSerializationApi::class, ExperimentalEncodingApi::class) @OptIn(ExperimentalSerializationApi::class, ExperimentalEncodingApi::class)
fun contents(user: User): String { fun contents(user: User): String {
return "https://revolt.chat/qr?" + Base64.encode( return "$STOAT_MARKETING/qr?" + Base64.encode(
RevoltCbor.encodeToByteArray( StoatCbor.encodeToByteArray(
UserQRContents.serializer(), UserQRContents.serializer(),
UserQRContents( UserQRContents(
format = "rqr\$user\$0", format = "rqr\$user\$0",
@ -45,7 +46,7 @@ object UserQR {
val uri = uriString.toUri() val uri = uriString.toUri()
val base64 = uri.query ?: return null val base64 = uri.query ?: return null
val decodedBytes = Base64.decode(base64) val decodedBytes = Base64.decode(base64)
RevoltCbor.decodeFromByteArray(UserQRContents.serializer(), decodedBytes) StoatCbor.decodeFromByteArray(UserQRContents.serializer(), decodedBytes)
} catch (e: Exception) { } catch (e: Exception) {
null null
} }

View File

@ -1,4 +1,4 @@
package chat.revolt.api.internals.colour package chat.stoat.api.internals.colour
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color

View File

@ -1,49 +1,49 @@
package chat.revolt.api.realtime package chat.stoat.api.realtime
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import chat.revolt.RevoltApplication import chat.stoat.StoatApplication
import chat.revolt.api.REVOLT_WEBSOCKET import chat.stoat.api.STOAT_WEBSOCKET
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.realtime.frames.receivable.AnyFrame import chat.stoat.api.realtime.frames.receivable.AnyFrame
import chat.revolt.api.realtime.frames.receivable.BulkFrame import chat.stoat.api.realtime.frames.receivable.BulkFrame
import chat.revolt.api.realtime.frames.receivable.ChannelAckFrame import chat.stoat.api.realtime.frames.receivable.ChannelAckFrame
import chat.revolt.api.realtime.frames.receivable.ChannelDeleteFrame import chat.stoat.api.realtime.frames.receivable.ChannelDeleteFrame
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame import chat.stoat.api.realtime.frames.receivable.ChannelStartTypingFrame
import chat.revolt.api.realtime.frames.receivable.ChannelStopTypingFrame import chat.stoat.api.realtime.frames.receivable.ChannelStopTypingFrame
import chat.revolt.api.realtime.frames.receivable.ChannelUpdateFrame import chat.stoat.api.realtime.frames.receivable.ChannelUpdateFrame
import chat.revolt.api.realtime.frames.receivable.MessageAppendFrame import chat.stoat.api.realtime.frames.receivable.MessageAppendFrame
import chat.revolt.api.realtime.frames.receivable.MessageDeleteFrame import chat.stoat.api.realtime.frames.receivable.MessageDeleteFrame
import chat.revolt.api.realtime.frames.receivable.MessageFrame import chat.stoat.api.realtime.frames.receivable.MessageFrame
import chat.revolt.api.realtime.frames.receivable.MessageReactFrame import chat.stoat.api.realtime.frames.receivable.MessageReactFrame
import chat.revolt.api.realtime.frames.receivable.MessageUpdateFrame import chat.stoat.api.realtime.frames.receivable.MessageUpdateFrame
import chat.revolt.api.realtime.frames.receivable.PongFrame import chat.stoat.api.realtime.frames.receivable.PongFrame
import chat.revolt.api.realtime.frames.receivable.ReadyFrame import chat.stoat.api.realtime.frames.receivable.ReadyFrame
import chat.revolt.api.realtime.frames.receivable.ServerCreateFrame import chat.stoat.api.realtime.frames.receivable.ServerCreateFrame
import chat.revolt.api.realtime.frames.receivable.ServerDeleteFrame import chat.stoat.api.realtime.frames.receivable.ServerDeleteFrame
import chat.revolt.api.realtime.frames.receivable.ServerMemberJoinFrame import chat.stoat.api.realtime.frames.receivable.ServerMemberJoinFrame
import chat.revolt.api.realtime.frames.receivable.ServerMemberLeaveFrame import chat.stoat.api.realtime.frames.receivable.ServerMemberLeaveFrame
import chat.revolt.api.realtime.frames.receivable.ServerMemberUpdateFrame import chat.stoat.api.realtime.frames.receivable.ServerMemberUpdateFrame
import chat.revolt.api.realtime.frames.receivable.ServerRoleDeleteFrame import chat.stoat.api.realtime.frames.receivable.ServerRoleDeleteFrame
import chat.revolt.api.realtime.frames.receivable.ServerRoleUpdateFrame import chat.stoat.api.realtime.frames.receivable.ServerRoleUpdateFrame
import chat.revolt.api.realtime.frames.receivable.ServerUpdateFrame import chat.stoat.api.realtime.frames.receivable.ServerUpdateFrame
import chat.revolt.api.realtime.frames.receivable.UserRelationshipFrame import chat.stoat.api.realtime.frames.receivable.UserRelationshipFrame
import chat.revolt.api.realtime.frames.receivable.UserUpdateFrame import chat.stoat.api.realtime.frames.receivable.UserUpdateFrame
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame import chat.stoat.api.realtime.frames.sendable.AuthorizationFrame
import chat.revolt.api.realtime.frames.sendable.BeginTypingFrame import chat.stoat.api.realtime.frames.sendable.BeginTypingFrame
import chat.revolt.api.realtime.frames.sendable.EndTypingFrame import chat.stoat.api.realtime.frames.sendable.EndTypingFrame
import chat.revolt.api.realtime.frames.sendable.PingFrame import chat.stoat.api.realtime.frames.sendable.PingFrame
import chat.revolt.api.routes.server.fetchMember import chat.stoat.api.routes.server.fetchMember
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.schemas.Role import chat.stoat.api.schemas.Role
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.SyncedSettings import chat.stoat.api.settings.SyncedSettings
import chat.revolt.c2dm.ChannelRegistrator import chat.stoat.c2dm.ChannelRegistrator
import chat.revolt.persistence.Database import chat.stoat.persistence.Database
import chat.revolt.persistence.SqlStorage import chat.stoat.persistence.SqlStorage
import io.ktor.client.plugins.websocket.ws import io.ktor.client.plugins.websocket.ws
import io.ktor.websocket.CloseReason import io.ktor.websocket.CloseReason
import io.ktor.websocket.Frame import io.ktor.websocket.Frame
@ -70,7 +70,7 @@ object RealtimeSocket {
var socket: WebSocketSession? = null var socket: WebSocketSession? = null
private val channelRegistrator: ChannelRegistrator private val channelRegistrator: ChannelRegistrator
get() = ChannelRegistrator(RevoltApplication.instance) get() = ChannelRegistrator(StoatApplication.instance)
private var _disconnectionState = mutableStateOf(DisconnectionState.Reconnecting) private var _disconnectionState = mutableStateOf(DisconnectionState.Reconnecting)
val disconnectionState: DisconnectionState val disconnectionState: DisconnectionState
@ -88,7 +88,7 @@ object RealtimeSocket {
socket?.close(CloseReason(CloseReason.Codes.NORMAL, "Reconnecting to websocket.")) socket?.close(CloseReason(CloseReason.Codes.NORMAL, "Reconnecting to websocket."))
RevoltHttp.ws(REVOLT_WEBSOCKET) { StoatHttp.ws(STOAT_WEBSOCKET) {
socket = this socket = this
Log.d("RealtimeSocket", "Connected to websocket.") Log.d("RealtimeSocket", "Connected to websocket.")
@ -98,7 +98,7 @@ object RealtimeSocket {
// Send authorization frame // Send authorization frame
val authFrame = AuthorizationFrame("Authenticate", token) val authFrame = AuthorizationFrame("Authenticate", token)
val authFrameString = val authFrameString =
RevoltJson.encodeToString(AuthorizationFrame.serializer(), authFrame) StoatJson.encodeToString(AuthorizationFrame.serializer(), authFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -109,13 +109,13 @@ object RealtimeSocket {
) )
}" }"
) )
send(RevoltJson.encodeToString(AuthorizationFrame.serializer(), authFrame)) send(StoatJson.encodeToString(AuthorizationFrame.serializer(), authFrame))
incoming.consumeEach { frame -> incoming.consumeEach { frame ->
if (frame is Frame.Text) { if (frame is Frame.Text) {
val frameString = frame.readText() val frameString = frame.readText()
val frameType = val frameType =
RevoltJson.decodeFromString(AnyFrame.serializer(), frameString).type StoatJson.decodeFromString(AnyFrame.serializer(), frameString).type
handleFrame(frameType, frameString) handleFrame(frameType, frameString)
} }
@ -127,29 +127,29 @@ object RealtimeSocket {
if (disconnectionState != DisconnectionState.Connected) return if (disconnectionState != DisconnectionState.Connected) return
val pingPacket = PingFrame("Ping", System.currentTimeMillis()) val pingPacket = PingFrame("Ping", System.currentTimeMillis())
socket?.send(RevoltJson.encodeToString(PingFrame.serializer(), pingPacket)) socket?.send(StoatJson.encodeToString(PingFrame.serializer(), pingPacket))
Log.d("RealtimeSocket", "Sent ping frame with ${pingPacket.data}") Log.d("RealtimeSocket", "Sent ping frame with ${pingPacket.data}")
} }
private suspend fun handleFrame(type: String, rawFrame: String) { private suspend fun handleFrame(type: String, rawFrame: String) {
when (type) { when (type) {
"Pong" -> { "Pong" -> {
val pongFrame = RevoltJson.decodeFromString(PongFrame.serializer(), rawFrame) val pongFrame = StoatJson.decodeFromString(PongFrame.serializer(), rawFrame)
Log.d("RealtimeSocket", "Received pong frame for ${pongFrame.data}") Log.d("RealtimeSocket", "Received pong frame for ${pongFrame.data}")
} }
"Bulk" -> { "Bulk" -> {
val bulkFrame = RevoltJson.decodeFromString(BulkFrame.serializer(), rawFrame) val bulkFrame = StoatJson.decodeFromString(BulkFrame.serializer(), rawFrame)
Log.d("RealtimeSocket", "Received bulk frame with ${bulkFrame.v.size} sub-frames.") Log.d("RealtimeSocket", "Received bulk frame with ${bulkFrame.v.size} sub-frames.")
bulkFrame.v.forEach { subFrame -> bulkFrame.v.forEach { subFrame ->
val subFrameType = val subFrameType =
RevoltJson.decodeFromString(AnyFrame.serializer(), subFrame.toString()).type StoatJson.decodeFromString(AnyFrame.serializer(), subFrame.toString()).type
handleFrame(subFrameType, subFrame.toString()) handleFrame(subFrameType, subFrame.toString())
} }
} }
"Ready" -> { "Ready" -> {
val readyFrame = RevoltJson.decodeFromString(ReadyFrame.serializer(), rawFrame) val readyFrame = StoatJson.decodeFromString(ReadyFrame.serializer(), rawFrame)
logcat { logcat {
"Received ready frame with ${readyFrame.users.size} users, " + "Received ready frame with ${readyFrame.users.size} users, " +
@ -161,11 +161,11 @@ object RealtimeSocket {
Log.d("RealtimeSocket", "Adding users to cache.") Log.d("RealtimeSocket", "Adding users to cache.")
val userMap = readyFrame.users.associateBy { it.id!! } val userMap = readyFrame.users.associateBy { it.id!! }
RevoltAPI.userCache.putAll(userMap) StoatAPI.userCache.putAll(userMap)
Log.d("RealtimeSocket", "Adding servers to cache.") Log.d("RealtimeSocket", "Adding servers to cache.")
val serverMap = readyFrame.servers.associateBy { it.id!! } val serverMap = readyFrame.servers.associateBy { it.id!! }
RevoltAPI.serverCache.putAll(serverMap) StoatAPI.serverCache.putAll(serverMap)
// Cache servers in persistent local database // Cache servers in persistent local database
readyFrame.servers.map { readyFrame.servers.map {
@ -196,12 +196,12 @@ object RealtimeSocket {
"Deleted server $it from local database due to not being in ready frame." "Deleted server $it from local database due to not being in ready frame."
) )
// Conversely, remove the server from the API state // Conversely, remove the server from the API state
RevoltAPI.serverCache.remove(it) StoatAPI.serverCache.remove(it)
} }
Log.d("RealtimeSocket", "Adding channels to cache.") Log.d("RealtimeSocket", "Adding channels to cache.")
val channelMap = readyFrame.channels.associateBy { it.id!! } val channelMap = readyFrame.channels.associateBy { it.id!! }
RevoltAPI.channelCache.putAll(channelMap) StoatAPI.channelCache.putAll(channelMap)
// Cache channels in persistent local database // Cache channels in persistent local database
readyFrame.channels.map { readyFrame.channels.map {
@ -216,7 +216,7 @@ object RealtimeSocket {
it.name, it.name,
it.owner, it.owner,
it.description, it.description,
if (it.channelType == ChannelType.DirectMessage) it.recipients?.firstOrNull { u -> u != RevoltAPI.selfId } else null, if (it.channelType == ChannelType.DirectMessage) it.recipients?.firstOrNull { u -> u != StoatAPI.selfId } else null,
it.icon?.id, it.icon?.id,
it.lastMessageID, it.lastMessageID,
if (it.active == true) 1L else 0L, if (it.active == true) 1L else 0L,
@ -237,21 +237,21 @@ object RealtimeSocket {
"Deleted channel $it from local database due to not being in ready frame." "Deleted channel $it from local database due to not being in ready frame."
) )
// Conversely, remove the channel from the API state // Conversely, remove the channel from the API state
RevoltAPI.channelCache.remove(it) StoatAPI.channelCache.remove(it)
} }
Log.d("RealtimeSocket", "Adding emojis to cache.") Log.d("RealtimeSocket", "Adding emojis to cache.")
val emojiMap = readyFrame.emojis.associateBy { it.id!! } val emojiMap = readyFrame.emojis.associateBy { it.id!! }
RevoltAPI.emojiCache.putAll(emojiMap) StoatAPI.emojiCache.putAll(emojiMap)
Log.d("RealtimeSocket", "Registering push notification channels.") Log.d("RealtimeSocket", "Registering push notification channels.")
channelRegistrator.register() channelRegistrator.register()
RevoltAPI.closeHydration() StoatAPI.closeHydration()
} }
"Message" -> { "Message" -> {
val messageFrame = RevoltJson.decodeFromString(MessageFrame.serializer(), rawFrame) val messageFrame = StoatJson.decodeFromString(MessageFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message frame for ${messageFrame.id} in channel ${messageFrame.channel}." "Received message frame for ${messageFrame.id} in channel ${messageFrame.channel}."
@ -262,30 +262,30 @@ object RealtimeSocket {
return return
} }
RevoltAPI.messageCache[messageFrame.id] = messageFrame StoatAPI.messageCache[messageFrame.id] = messageFrame
messageFrame.channel?.let { messageFrame.channel?.let {
if (RevoltAPI.channelCache[it] == null) { if (StoatAPI.channelCache[it] == null) {
Log.d("RealtimeSocket", "Channel $it not found in cache. Ignoring.") Log.d("RealtimeSocket", "Channel $it not found in cache. Ignoring.")
return return
} }
RevoltAPI.channelCache[it] = StoatAPI.channelCache[it] =
RevoltAPI.channelCache[it]!!.copy(lastMessageID = messageFrame.id) StoatAPI.channelCache[it]!!.copy(lastMessageID = messageFrame.id)
RevoltAPI.wsFrameChannel.send(messageFrame) StoatAPI.wsFrameChannel.send(messageFrame)
} }
} }
"MessageAppend" -> { "MessageAppend" -> {
val messageAppendFrame = val messageAppendFrame =
RevoltJson.decodeFromString(MessageAppendFrame.serializer(), rawFrame) StoatJson.decodeFromString(MessageAppendFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message append frame for ${messageAppendFrame.id} in channel ${messageAppendFrame.channel}." "Received message append frame for ${messageAppendFrame.id} in channel ${messageAppendFrame.channel}."
) )
var message = RevoltAPI.messageCache[messageAppendFrame.id] var message = StoatAPI.messageCache[messageAppendFrame.id]
if (message == null) { if (message == null) {
Log.d( Log.d(
@ -299,20 +299,20 @@ object RealtimeSocket {
message = message!!.copy(embeds = message!!.embeds?.plus(it) ?: it) message = message!!.copy(embeds = message!!.embeds?.plus(it) ?: it)
} }
RevoltAPI.messageCache[messageAppendFrame.id] = message!! StoatAPI.messageCache[messageAppendFrame.id] = message!!
RevoltAPI.wsFrameChannel.send(messageAppendFrame) StoatAPI.wsFrameChannel.send(messageAppendFrame)
} }
"MessageUpdate" -> { "MessageUpdate" -> {
val messageUpdateFrame = val messageUpdateFrame =
RevoltJson.decodeFromString(MessageUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(MessageUpdateFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message update frame for ${messageUpdateFrame.id} in channel ${messageUpdateFrame.channel}." "Received message update frame for ${messageUpdateFrame.id} in channel ${messageUpdateFrame.channel}."
) )
val oldMessage = RevoltAPI.messageCache[messageUpdateFrame.id] val oldMessage = StoatAPI.messageCache[messageUpdateFrame.id]
if (oldMessage == null) { if (oldMessage == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -324,7 +324,7 @@ object RealtimeSocket {
val rawMessage: MessageFrame val rawMessage: MessageFrame
try { try {
rawMessage = rawMessage =
RevoltJson.decodeFromJsonElement( StoatJson.decodeFromJsonElement(
MessageFrame.serializer(), MessageFrame.serializer(),
messageUpdateFrame.data messageUpdateFrame.data
) )
@ -338,28 +338,28 @@ object RealtimeSocket {
"Merging message ${messageUpdateFrame.id} with updated partial." "Merging message ${messageUpdateFrame.id} with updated partial."
) )
RevoltAPI.messageCache[messageUpdateFrame.id] = StoatAPI.messageCache[messageUpdateFrame.id] =
oldMessage.mergeWithPartial(rawMessage) oldMessage.mergeWithPartial(rawMessage)
messageUpdateFrame.channel.let { messageUpdateFrame.channel.let {
if (RevoltAPI.channelCache[it] == null) { if (StoatAPI.channelCache[it] == null) {
Log.d("RealtimeSocket", "Channel $it not found in cache. Ignoring.") Log.d("RealtimeSocket", "Channel $it not found in cache. Ignoring.")
return return
} }
} }
RevoltAPI.wsFrameChannel.send(messageUpdateFrame) StoatAPI.wsFrameChannel.send(messageUpdateFrame)
} }
"MessageDelete" -> { "MessageDelete" -> {
val messageDeleteFrame = val messageDeleteFrame =
RevoltJson.decodeFromString(MessageDeleteFrame.serializer(), rawFrame) StoatJson.decodeFromString(MessageDeleteFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message react frame for ${messageDeleteFrame.id}." "Received message react frame for ${messageDeleteFrame.id}."
) )
val message = RevoltAPI.messageCache[messageDeleteFrame.id] val message = StoatAPI.messageCache[messageDeleteFrame.id]
if (message == null) { if (message == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -368,19 +368,19 @@ object RealtimeSocket {
return return
} }
RevoltAPI.messageCache.remove(messageDeleteFrame.id) StoatAPI.messageCache.remove(messageDeleteFrame.id)
RevoltAPI.wsFrameChannel.send(messageDeleteFrame) StoatAPI.wsFrameChannel.send(messageDeleteFrame)
} }
"MessageReact" -> { "MessageReact" -> {
val messageReactFrame = val messageReactFrame =
RevoltJson.decodeFromString(MessageReactFrame.serializer(), rawFrame) StoatJson.decodeFromString(MessageReactFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message react frame for ${messageReactFrame.id}." "Received message react frame for ${messageReactFrame.id}."
) )
val oldMessage = RevoltAPI.messageCache[messageReactFrame.id] val oldMessage = StoatAPI.messageCache[messageReactFrame.id]
if (oldMessage == null) { if (oldMessage == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -395,21 +395,21 @@ object RealtimeSocket {
forEmoji.add(messageReactFrame.user_id) forEmoji.add(messageReactFrame.user_id)
reactions[messageReactFrame.emoji_id] = forEmoji reactions[messageReactFrame.emoji_id] = forEmoji
RevoltAPI.messageCache[messageReactFrame.id] = StoatAPI.messageCache[messageReactFrame.id] =
oldMessage.copy(reactions = reactions) oldMessage.copy(reactions = reactions)
RevoltAPI.wsFrameChannel.send(messageReactFrame) StoatAPI.wsFrameChannel.send(messageReactFrame)
} }
"MessageUnreact" -> { "MessageUnreact" -> {
val messageUnreactFrame = val messageUnreactFrame =
RevoltJson.decodeFromString(MessageReactFrame.serializer(), rawFrame) StoatJson.decodeFromString(MessageReactFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received message unreact frame for ${messageUnreactFrame.id}." "Received message unreact frame for ${messageUnreactFrame.id}."
) )
val oldMessage = RevoltAPI.messageCache[messageUnreactFrame.id] val oldMessage = StoatAPI.messageCache[messageUnreactFrame.id]
if (oldMessage == null) { if (oldMessage == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -429,38 +429,38 @@ object RealtimeSocket {
reactions[messageUnreactFrame.emoji_id] = forEmoji reactions[messageUnreactFrame.emoji_id] = forEmoji
} }
RevoltAPI.messageCache[messageUnreactFrame.id] = StoatAPI.messageCache[messageUnreactFrame.id] =
oldMessage.copy(reactions = reactions) oldMessage.copy(reactions = reactions)
RevoltAPI.wsFrameChannel.send(messageUnreactFrame) StoatAPI.wsFrameChannel.send(messageUnreactFrame)
} }
"UserUpdate" -> { "UserUpdate" -> {
val userUpdateFrame = val userUpdateFrame =
RevoltJson.decodeFromString(UserUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(UserUpdateFrame.serializer(), rawFrame)
val existing = RevoltAPI.userCache[userUpdateFrame.id] val existing = StoatAPI.userCache[userUpdateFrame.id]
?: return // if we don't have the user no point in updating it ?: return // if we don't have the user no point in updating it
if (userUpdateFrame.clear != null) { if (userUpdateFrame.clear != null) {
if (userUpdateFrame.clear.contains("Avatar")) { if (userUpdateFrame.clear.contains("Avatar")) {
RevoltAPI.userCache[userUpdateFrame.id] = StoatAPI.userCache[userUpdateFrame.id] =
existing.copy(avatar = null) existing.copy(avatar = null)
} }
} }
RevoltAPI.userCache[userUpdateFrame.id] = StoatAPI.userCache[userUpdateFrame.id] =
existing.mergeWithPartial(userUpdateFrame.data) existing.mergeWithPartial(userUpdateFrame.data)
} }
"UserRelationship" -> { "UserRelationship" -> {
val userRelationshipFrame = val userRelationshipFrame =
RevoltJson.decodeFromString(UserRelationshipFrame.serializer(), rawFrame) StoatJson.decodeFromString(UserRelationshipFrame.serializer(), rawFrame)
val existing = RevoltAPI.userCache[userRelationshipFrame.user.id] val existing = StoatAPI.userCache[userRelationshipFrame.user.id]
if (existing == null && userRelationshipFrame.user.id != null) { if (existing == null && userRelationshipFrame.user.id != null) {
RevoltAPI.userCache[userRelationshipFrame.user.id] = StoatAPI.userCache[userRelationshipFrame.user.id] =
userRelationshipFrame.user.copy( userRelationshipFrame.user.copy(
relationship = userRelationshipFrame.status ?: "None" relationship = userRelationshipFrame.status ?: "None"
) )
@ -468,7 +468,7 @@ object RealtimeSocket {
val merged = existing.mergeWithPartial(userRelationshipFrame.user).copy( val merged = existing.mergeWithPartial(userRelationshipFrame.user).copy(
relationship = userRelationshipFrame.status ?: "None" relationship = userRelationshipFrame.status ?: "None"
) )
RevoltAPI.userCache[userRelationshipFrame.user.id] = merged StoatAPI.userCache[userRelationshipFrame.user.id] = merged
} else { } else {
Log.w("RealtimeSocket", "Invalid UserRelationship frame: $rawFrame") Log.w("RealtimeSocket", "Invalid UserRelationship frame: $rawFrame")
} }
@ -476,13 +476,13 @@ object RealtimeSocket {
"ChannelUpdate" -> { "ChannelUpdate" -> {
val channelUpdateFrame = val channelUpdateFrame =
RevoltJson.decodeFromString(ChannelUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(ChannelUpdateFrame.serializer(), rawFrame)
val existing = RevoltAPI.channelCache[channelUpdateFrame.id] val existing = StoatAPI.channelCache[channelUpdateFrame.id]
?: return // if we don't have the channel no point in updating it ?: return // if we don't have the channel no point in updating it
val combined = existing.mergeWithPartial(channelUpdateFrame.data) val combined = existing.mergeWithPartial(channelUpdateFrame.data)
RevoltAPI.channelCache[channelUpdateFrame.id] = combined StoatAPI.channelCache[channelUpdateFrame.id] = combined
database.channelQueries.upsert( database.channelQueries.upsert(
channelUpdateFrame.id, channelUpdateFrame.id,
@ -491,7 +491,7 @@ object RealtimeSocket {
combined.name, combined.name,
combined.owner, combined.owner,
combined.description, combined.description,
if (combined.channelType == ChannelType.DirectMessage) combined.recipients?.firstOrNull { u -> u != RevoltAPI.selfId } else null, if (combined.channelType == ChannelType.DirectMessage) combined.recipients?.firstOrNull { u -> u != StoatAPI.selfId } else null,
combined.icon?.id, combined.icon?.id,
combined.lastMessageID, combined.lastMessageID,
if (combined.active == true) 1L else 0L, if (combined.active == true) 1L else 0L,
@ -502,14 +502,14 @@ object RealtimeSocket {
"ChannelCreate" -> { "ChannelCreate" -> {
val channelCreateFrame = val channelCreateFrame =
RevoltJson.decodeFromString(Channel.serializer(), rawFrame) StoatJson.decodeFromString(Channel.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received channel create frame for ${channelCreateFrame.id}, with name ${channelCreateFrame.name}. Adding to cache." "Received channel create frame for ${channelCreateFrame.id}, with name ${channelCreateFrame.name}. Adding to cache."
) )
RevoltAPI.channelCache[channelCreateFrame.id!!] = channelCreateFrame StoatAPI.channelCache[channelCreateFrame.id!!] = channelCreateFrame
database.channelQueries.upsert( database.channelQueries.upsert(
channelCreateFrame.id, channelCreateFrame.id,
channelCreateFrame.channelType?.value ?: ChannelType.TextChannel.value, channelCreateFrame.channelType?.value ?: ChannelType.TextChannel.value,
@ -517,7 +517,7 @@ object RealtimeSocket {
channelCreateFrame.name, channelCreateFrame.name,
channelCreateFrame.owner, channelCreateFrame.owner,
channelCreateFrame.description, channelCreateFrame.description,
if (channelCreateFrame.channelType == ChannelType.DirectMessage) channelCreateFrame.recipients?.firstOrNull { u -> u != RevoltAPI.selfId } else null, if (channelCreateFrame.channelType == ChannelType.DirectMessage) channelCreateFrame.recipients?.firstOrNull { u -> u != StoatAPI.selfId } else null,
channelCreateFrame.icon?.id, channelCreateFrame.icon?.id,
channelCreateFrame.lastMessageID, channelCreateFrame.lastMessageID,
if (channelCreateFrame.active == true) 1L else 0L, if (channelCreateFrame.active == true) 1L else 0L,
@ -528,13 +528,13 @@ object RealtimeSocket {
"ChannelDelete" -> { "ChannelDelete" -> {
val channelDeleteFrame = val channelDeleteFrame =
RevoltJson.decodeFromString(ChannelDeleteFrame.serializer(), rawFrame) StoatJson.decodeFromString(ChannelDeleteFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received channel delete frame for ${channelDeleteFrame.id}. Removing from cache." "Received channel delete frame for ${channelDeleteFrame.id}. Removing from cache."
) )
val currentChannel = RevoltAPI.channelCache[channelDeleteFrame.id] val currentChannel = StoatAPI.channelCache[channelDeleteFrame.id]
if (currentChannel == null) { if (currentChannel == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -543,11 +543,11 @@ object RealtimeSocket {
return return
} }
RevoltAPI.channelCache.remove(channelDeleteFrame.id) StoatAPI.channelCache.remove(channelDeleteFrame.id)
database.channelQueries.delete(channelDeleteFrame.id) database.channelQueries.delete(channelDeleteFrame.id)
if (currentChannel.server != null) { if (currentChannel.server != null) {
val existingServer = RevoltAPI.serverCache[currentChannel.server] val existingServer = StoatAPI.serverCache[currentChannel.server]
if (existingServer == null) { if (existingServer == null) {
Log.d( Log.d(
@ -557,39 +557,39 @@ object RealtimeSocket {
return return
} }
RevoltAPI.serverCache[currentChannel.server] = existingServer.copy( StoatAPI.serverCache[currentChannel.server] = existingServer.copy(
channels = existingServer.channels?.filter { it != channelDeleteFrame.id } channels = existingServer.channels?.filter { it != channelDeleteFrame.id }
?: emptyList() ?: emptyList()
) )
} }
RevoltAPI.wsFrameChannel.send(channelDeleteFrame) StoatAPI.wsFrameChannel.send(channelDeleteFrame)
} }
"ChannelAck" -> { "ChannelAck" -> {
val channelAckFrame = val channelAckFrame =
RevoltJson.decodeFromString(ChannelAckFrame.serializer(), rawFrame) StoatJson.decodeFromString(ChannelAckFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received channel ack frame for ${channelAckFrame.id} with new newest ${channelAckFrame.messageId}." "Received channel ack frame for ${channelAckFrame.id} with new newest ${channelAckFrame.messageId}."
) )
RevoltAPI.unreads.processExternalAck(channelAckFrame.id, channelAckFrame.messageId) StoatAPI.unreads.processExternalAck(channelAckFrame.id, channelAckFrame.messageId)
} }
"ServerCreate" -> { "ServerCreate" -> {
val serverCreateFrame = val serverCreateFrame =
RevoltJson.decodeFromString(ServerCreateFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerCreateFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server create frame for ${serverCreateFrame.id}, with name ${serverCreateFrame.server.name}. Adding to cache." "Received server create frame for ${serverCreateFrame.id}, with name ${serverCreateFrame.server.name}. Adding to cache."
) )
RevoltAPI.serverCache[serverCreateFrame.id] = serverCreateFrame.server StoatAPI.serverCache[serverCreateFrame.id] = serverCreateFrame.server
serverCreateFrame.channels.forEach { channel -> serverCreateFrame.channels.forEach { channel ->
if (channel.id == null) return@forEach if (channel.id == null) return@forEach
RevoltAPI.channelCache[channel.id] = channel StoatAPI.channelCache[channel.id] = channel
} }
if (serverCreateFrame.server.owner != null && serverCreateFrame.server.name != null) { if (serverCreateFrame.server.owner != null && serverCreateFrame.server.name != null) {
@ -607,35 +607,35 @@ object RealtimeSocket {
"ChannelStartTyping" -> { "ChannelStartTyping" -> {
val channelStartTypingFrame = val channelStartTypingFrame =
RevoltJson.decodeFromString(ChannelStartTypingFrame.serializer(), rawFrame) StoatJson.decodeFromString(ChannelStartTypingFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received channel start typing frame for ${channelStartTypingFrame.id}." "Received channel start typing frame for ${channelStartTypingFrame.id}."
) )
RevoltAPI.wsFrameChannel.send(channelStartTypingFrame) StoatAPI.wsFrameChannel.send(channelStartTypingFrame)
} }
"ChannelStopTyping" -> { "ChannelStopTyping" -> {
val channelStopTypingFrame = val channelStopTypingFrame =
RevoltJson.decodeFromString(ChannelStopTypingFrame.serializer(), rawFrame) StoatJson.decodeFromString(ChannelStopTypingFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received channel stop typing frame for ${channelStopTypingFrame.id}." "Received channel stop typing frame for ${channelStopTypingFrame.id}."
) )
RevoltAPI.wsFrameChannel.send(channelStopTypingFrame) StoatAPI.wsFrameChannel.send(channelStopTypingFrame)
} }
"ServerUpdate" -> { "ServerUpdate" -> {
val serverUpdateFrame = val serverUpdateFrame =
RevoltJson.decodeFromString(ServerUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerUpdateFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server update frame for ${serverUpdateFrame.id}." "Received server update frame for ${serverUpdateFrame.id}."
) )
val existing = RevoltAPI.serverCache[serverUpdateFrame.id] val existing = StoatAPI.serverCache[serverUpdateFrame.id]
?: return // if we don't have the server no point in updating it ?: return // if we don't have the server no point in updating it
var updated = var updated =
@ -650,7 +650,7 @@ object RealtimeSocket {
} }
} }
RevoltAPI.serverCache[serverUpdateFrame.id] = updated StoatAPI.serverCache[serverUpdateFrame.id] = updated
if (updated.id != null && updated.owner != null && updated.name != null) { if (updated.id != null && updated.owner != null && updated.name != null) {
try { try {
@ -671,25 +671,25 @@ object RealtimeSocket {
"ServerDelete" -> { "ServerDelete" -> {
val serverDeleteFrame = val serverDeleteFrame =
RevoltJson.decodeFromString(ServerDeleteFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerDeleteFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server delete frame for ${serverDeleteFrame.id}." "Received server delete frame for ${serverDeleteFrame.id}."
) )
RevoltAPI.serverCache.remove(serverDeleteFrame.id) StoatAPI.serverCache.remove(serverDeleteFrame.id)
database.serverQueries.delete(serverDeleteFrame.id) database.serverQueries.delete(serverDeleteFrame.id)
} }
"ServerMemberUpdate" -> { "ServerMemberUpdate" -> {
val serverMemberUpdateFrame = val serverMemberUpdateFrame =
RevoltJson.decodeFromString(ServerMemberUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerMemberUpdateFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server member update frame for ${serverMemberUpdateFrame.id.user} in ${serverMemberUpdateFrame.id.server}." "Received server member update frame for ${serverMemberUpdateFrame.id.user} in ${serverMemberUpdateFrame.id.server}."
) )
val existing = RevoltAPI.members.getMember( val existing = StoatAPI.members.getMember(
serverMemberUpdateFrame.id.server, serverMemberUpdateFrame.id.server,
serverMemberUpdateFrame.id.user serverMemberUpdateFrame.id.user
) )
@ -707,12 +707,12 @@ object RealtimeSocket {
Log.d("RealtimeSocket", "Updated member: $updated") Log.d("RealtimeSocket", "Updated member: $updated")
RevoltAPI.members.setMember(serverMemberUpdateFrame.id.server, updated) StoatAPI.members.setMember(serverMemberUpdateFrame.id.server, updated)
} }
"ServerMemberJoin" -> { "ServerMemberJoin" -> {
val serverMemberJoinFrame = val serverMemberJoinFrame =
RevoltJson.decodeFromString(ServerMemberJoinFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerMemberJoinFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server member join frame for ${serverMemberJoinFrame.user} in ${serverMemberJoinFrame.id}." "Received server member join frame for ${serverMemberJoinFrame.user} in ${serverMemberJoinFrame.id}."
@ -720,18 +720,18 @@ object RealtimeSocket {
val member = fetchMember(serverMemberJoinFrame.id, serverMemberJoinFrame.user) val member = fetchMember(serverMemberJoinFrame.id, serverMemberJoinFrame.user)
RevoltAPI.members.setMember(serverMemberJoinFrame.id, member) StoatAPI.members.setMember(serverMemberJoinFrame.id, member)
} }
"ServerMemberLeave" -> { "ServerMemberLeave" -> {
val serverMemberLeaveFrame = val serverMemberLeaveFrame =
RevoltJson.decodeFromString(ServerMemberLeaveFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerMemberLeaveFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server member leave frame for ${serverMemberLeaveFrame.user} in ${serverMemberLeaveFrame.id}." "Received server member leave frame for ${serverMemberLeaveFrame.user} in ${serverMemberLeaveFrame.id}."
) )
RevoltAPI.members.removeMember( StoatAPI.members.removeMember(
serverMemberLeaveFrame.id, serverMemberLeaveFrame.id,
serverMemberLeaveFrame.user serverMemberLeaveFrame.user
) )
@ -739,13 +739,13 @@ object RealtimeSocket {
"ServerRoleUpdate" -> { "ServerRoleUpdate" -> {
val serverRoleUpdateFrame = val serverRoleUpdateFrame =
RevoltJson.decodeFromString(ServerRoleUpdateFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerRoleUpdateFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server role update frame for ${serverRoleUpdateFrame.id}." "Received server role update frame for ${serverRoleUpdateFrame.id}."
) )
val server = RevoltAPI.serverCache[serverRoleUpdateFrame.id] val server = StoatAPI.serverCache[serverRoleUpdateFrame.id]
if (server == null) { if (server == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -767,7 +767,7 @@ object RealtimeSocket {
Pair(serverRoleUpdateFrame.roleId, newRole) Pair(serverRoleUpdateFrame.roleId, newRole)
) ?: mapOf(serverRoleUpdateFrame.roleId to newRole) ) ?: mapOf(serverRoleUpdateFrame.roleId to newRole)
) )
RevoltAPI.serverCache[serverRoleUpdateFrame.id] = newServer StoatAPI.serverCache[serverRoleUpdateFrame.id] = newServer
} else { } else {
// True role update. // True role update.
Log.d( Log.d(
@ -780,19 +780,19 @@ object RealtimeSocket {
Pair(serverRoleUpdateFrame.roleId, updatedRole) Pair(serverRoleUpdateFrame.roleId, updatedRole)
) )
) )
RevoltAPI.serverCache[serverRoleUpdateFrame.id] = newServer StoatAPI.serverCache[serverRoleUpdateFrame.id] = newServer
} }
} }
"ServerRoleDelete" -> { "ServerRoleDelete" -> {
val serverRoleDeleteFrame = val serverRoleDeleteFrame =
RevoltJson.decodeFromString(ServerRoleDeleteFrame.serializer(), rawFrame) StoatJson.decodeFromString(ServerRoleDeleteFrame.serializer(), rawFrame)
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
"Received server role delete frame for ${serverRoleDeleteFrame.id} and role ${serverRoleDeleteFrame.roleId}." "Received server role delete frame for ${serverRoleDeleteFrame.id} and role ${serverRoleDeleteFrame.roleId}."
) )
val server = RevoltAPI.serverCache[serverRoleDeleteFrame.id] val server = StoatAPI.serverCache[serverRoleDeleteFrame.id]
if (server == null) { if (server == null) {
Log.d( Log.d(
"RealtimeSocket", "RealtimeSocket",
@ -804,7 +804,7 @@ object RealtimeSocket {
val newRoles = server.roles?.toMutableMap() ?: mutableMapOf() val newRoles = server.roles?.toMutableMap() ?: mutableMapOf()
newRoles.remove(serverRoleDeleteFrame.roleId) newRoles.remove(serverRoleDeleteFrame.roleId)
RevoltAPI.serverCache[serverRoleDeleteFrame.id] = StoatAPI.serverCache[serverRoleDeleteFrame.id] =
server.copy(roles = newRoles) server.copy(roles = newRoles)
} }
@ -820,7 +820,7 @@ object RealtimeSocket {
} }
private suspend fun pushReconnectEvent() { private suspend fun pushReconnectEvent() {
RevoltAPI.wsFrameChannel.send(RealtimeSocketFrames.Reconnected) StoatAPI.wsFrameChannel.send(RealtimeSocketFrames.Reconnected)
} }
suspend fun beginTyping(channelId: String) { suspend fun beginTyping(channelId: String) {
@ -828,7 +828,7 @@ object RealtimeSocket {
val beginTypingFrame = BeginTypingFrame("BeginTyping", channelId) val beginTypingFrame = BeginTypingFrame("BeginTyping", channelId)
socket?.send( socket?.send(
RevoltJson.encodeToString( StoatJson.encodeToString(
BeginTypingFrame.serializer(), BeginTypingFrame.serializer(),
beginTypingFrame beginTypingFrame
) )
@ -840,7 +840,7 @@ object RealtimeSocket {
val endTypingFrame = EndTypingFrame("EndTyping", channelId) val endTypingFrame = EndTypingFrame("EndTyping", channelId)
socket?.send( socket?.send(
RevoltJson.encodeToString( StoatJson.encodeToString(
EndTypingFrame.serializer(), EndTypingFrame.serializer(),
endTypingFrame endTypingFrame
) )

View File

@ -1,15 +1,15 @@
package chat.revolt.api.realtime.frames.receivable package chat.stoat.api.realtime.frames.receivable
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.ChannelVoiceState import chat.stoat.api.schemas.ChannelVoiceState
import chat.revolt.api.schemas.Embed import chat.stoat.api.schemas.Embed
import chat.revolt.api.schemas.Emoji import chat.stoat.api.schemas.Emoji
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
import chat.revolt.api.schemas.Message import chat.stoat.api.schemas.Message
import chat.revolt.api.schemas.Role import chat.stoat.api.schemas.Role
import chat.revolt.api.schemas.Server import chat.stoat.api.schemas.Server
import chat.revolt.api.schemas.ServerUserChoice import chat.stoat.api.schemas.ServerUserChoice
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject

View File

@ -1,4 +1,4 @@
package chat.revolt.api.realtime.frames.sendable package chat.stoat.api.realtime.frames.sendable
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,11 +1,11 @@
package chat.revolt.api.routes.account package chat.stoat.api.routes.account
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
@ -104,22 +104,22 @@ data class EmailPasswordAssessment(
val proceedMfa: Boolean = false, val proceedMfa: Boolean = false,
val mfaSpec: MfaLoginSpec? = null, val mfaSpec: MfaLoginSpec? = null,
val firstUserHints: UserHints? = null, val firstUserHints: UserHints? = null,
val error: RevoltError? = null val error: StoatAPIError? = null
) )
suspend fun negotiateAuthentication(email: String, password: String): EmailPasswordAssessment { suspend fun negotiateAuthentication(email: String, password: String): EmailPasswordAssessment {
val sessionName = friendlySessionName() val sessionName = friendlySessionName()
val response: HttpResponse = RevoltHttp.post("/auth/session/login".api()) { val response: HttpResponse = StoatHttp.post("/auth/session/login".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(LoginNegotiation(email, password, sessionName, null)) setBody(LoginNegotiation(email, password, sessionName, null))
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
Log.d("Revolt", "negotiateAuthentication: $responseContent") Log.d("Stoat", "negotiateAuthentication: $responseContent")
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), responseContent)
return EmailPasswordAssessment(error = error) return EmailPasswordAssessment(error = error)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -127,22 +127,22 @@ suspend fun negotiateAuthentication(email: String, password: String): EmailPassw
if (response.status == HttpStatusCode.InternalServerError) { if (response.status == HttpStatusCode.InternalServerError) {
return EmailPasswordAssessment( return EmailPasswordAssessment(
error = RevoltError( error = StoatAPIError(
"InternalServerError" "InternalServerError"
) )
) )
} }
val responseJson = RevoltJson.decodeFromString(MfaCheck.serializer(), responseContent) val responseJson = StoatJson.decodeFromString(MfaCheck.serializer(), responseContent)
return when (responseJson.result) { return when (responseJson.result) {
"Success" -> EmailPasswordAssessment( "Success" -> EmailPasswordAssessment(
firstUserHints = RevoltJson.decodeFromString(UserHints.serializer(), responseContent) firstUserHints = StoatJson.decodeFromString(UserHints.serializer(), responseContent)
) )
"MFA" -> EmailPasswordAssessment( "MFA" -> EmailPasswordAssessment(
proceedMfa = true, proceedMfa = true,
mfaSpec = RevoltJson.decodeFromString(MfaLoginSpec.serializer(), responseContent) mfaSpec = StoatJson.decodeFromString(MfaLoginSpec.serializer(), responseContent)
) )
else -> throw Exception("Unknown result: ${responseJson.result}") else -> throw Exception("Unknown result: ${responseJson.result}")
@ -153,23 +153,23 @@ suspend fun authenticateWithMfaTotpCode(
mfaTicket: String, mfaTicket: String,
mfaResponse: MfaResponseTotpCode mfaResponse: MfaResponseTotpCode
): EmailPasswordAssessment { ): EmailPasswordAssessment {
val response: HttpResponse = RevoltHttp.post("/auth/session/login".api()) { val response: HttpResponse = StoatHttp.post("/auth/session/login".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(LoginMfaAmendmentTotpCode(mfaTicket, mfaResponse, friendlySessionName())) setBody(LoginMfaAmendmentTotpCode(mfaTicket, mfaResponse, friendlySessionName()))
} }
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response.bodyAsText()) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response.bodyAsText())
return EmailPasswordAssessment(error = error) return EmailPasswordAssessment(error = error)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
Log.d("Revolt", "authenticateWithMfaTotpCode: $responseContent") Log.d("Stoat", "authenticateWithMfaTotpCode: $responseContent")
return EmailPasswordAssessment( return EmailPasswordAssessment(
firstUserHints = RevoltJson.decodeFromString(UserHints.serializer(), responseContent) firstUserHints = StoatJson.decodeFromString(UserHints.serializer(), responseContent)
) )
} }
@ -177,26 +177,26 @@ suspend fun authenticateWithMfaRecoveryCode(
mfaTicket: String, mfaTicket: String,
mfaResponse: MfaResponseRecoveryCode mfaResponse: MfaResponseRecoveryCode
): EmailPasswordAssessment { ): EmailPasswordAssessment {
val response: HttpResponse = RevoltHttp.post("/auth/session/login".api()) { val response: HttpResponse = StoatHttp.post("/auth/session/login".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(LoginMfaAmendmentRecoveryCode(mfaTicket, mfaResponse, friendlySessionName())) setBody(LoginMfaAmendmentRecoveryCode(mfaTicket, mfaResponse, friendlySessionName()))
} }
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response.bodyAsText()) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response.bodyAsText())
return EmailPasswordAssessment(error = error) return EmailPasswordAssessment(error = error)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
Log.d("Revolt", "authenticateWithMfaRecoveryCode: $responseContent") Log.d("Stoat", "authenticateWithMfaRecoveryCode: $responseContent")
return EmailPasswordAssessment( return EmailPasswordAssessment(
firstUserHints = RevoltJson.decodeFromString(UserHints.serializer(), responseContent) firstUserHints = StoatJson.decodeFromString(UserHints.serializer(), responseContent)
) )
} }
fun friendlySessionName(): String { fun friendlySessionName(): String {
return "Revolt Android on ${Build.MANUFACTURER} ${Build.MODEL}" return "Stoat for Android on ${Build.MANUFACTURER} ${Build.MODEL}"
} }

View File

@ -1,10 +1,10 @@
package chat.revolt.api.routes.account package chat.stoat.api.routes.account
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.schemas.RsResult import chat.stoat.api.schemas.RsResult
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -21,8 +21,8 @@ data class RegistrationBody(
val captcha: String val captcha: String
) )
suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> { suspend fun register(body: RegistrationBody): RsResult<Unit, StoatAPIError> {
val response = RevoltHttp.post("/auth/account/create".api()) { val response = StoatHttp.post("/auth/account/create".api()) {
setBody(body) setBody(body)
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
} }
@ -30,7 +30,7 @@ suspend fun register(body: RegistrationBody): RsResult<Unit, RevoltError> {
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), responseContent)
return RsResult.err(error) return RsResult.err(error)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error

View File

@ -1,9 +1,9 @@
package chat.revolt.api.routes.auth package chat.stoat.api.routes.auth
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.Session import chat.stoat.api.schemas.Session
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.parameter import io.ktor.client.request.parameter
@ -11,21 +11,21 @@ import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
suspend fun fetchAllSessions(): List<Session> { suspend fun fetchAllSessions(): List<Session> {
val response = RevoltHttp.get("/auth/session/all".api()) val response = StoatHttp.get("/auth/session/all".api())
.bodyAsText() .bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
ListSerializer(Session.serializer()), ListSerializer(Session.serializer()),
response response
) )
} }
suspend fun logoutSessionById(id: String) { suspend fun logoutSessionById(id: String) {
RevoltHttp.delete("/auth/session/$id".api()) StoatHttp.delete("/auth/session/$id".api())
} }
suspend fun logoutAllSessions(includingSelf: Boolean = false) { suspend fun logoutAllSessions(includingSelf: Boolean = false) {
RevoltHttp.delete("/auth/session/all".api()) { StoatHttp.delete("/auth/session/all".api()) {
parameter("revoke_self", includingSelf) parameter("revoke_self", includingSelf)
} }
} }

View File

@ -1,15 +1,15 @@
package chat.revolt.api.routes.channel package chat.stoat.api.routes.channel
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.internals.ULID import chat.stoat.api.internals.ULID
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.api.schemas.Message import chat.stoat.api.schemas.Message
import chat.revolt.api.schemas.MessagesInChannel import chat.stoat.api.schemas.MessagesInChannel
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
@ -37,7 +37,7 @@ suspend fun fetchMessagesFromChannel(
nearby: String? = null, nearby: String? = null,
sort: String? = null sort: String? = null
): MessagesInChannel { ): MessagesInChannel {
val response = RevoltHttp.get("/channels/$channelId/messages".api()) { val response = StoatHttp.get("/channels/$channelId/messages".api()) {
parameter("limit", limit) parameter("limit", limit)
parameter("include_users", includeUsers) parameter("include_users", includeUsers)
@ -49,12 +49,12 @@ suspend fun fetchMessagesFromChannel(
.bodyAsText() .bodyAsText()
if (includeUsers) { if (includeUsers) {
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
MessagesInChannel.serializer(), MessagesInChannel.serializer(),
response response
) )
} else { } else {
val messages = RevoltJson.decodeFromString( val messages = StoatJson.decodeFromString(
ListSerializer(Message.serializer()), ListSerializer(Message.serializer()),
response response
) )
@ -104,7 +104,7 @@ suspend fun sendMessage(
attachments: List<String>? = null, attachments: List<String>? = null,
idempotencyKey: String = ULID.makeNext() idempotencyKey: String = ULID.makeNext()
): String { ): String {
val response = RevoltHttp.post("/channels/$channelId/messages".api()) { val response = StoatHttp.post("/channels/$channelId/messages".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(
SendMessageBody( SendMessageBody(
@ -122,7 +122,7 @@ suspend fun sendMessage(
} }
suspend fun editMessage(channelId: String, messageId: String, newContent: String? = null) { suspend fun editMessage(channelId: String, messageId: String, newContent: String? = null) {
val response = RevoltHttp.patch("/channels/$channelId/messages/$messageId".api()) { val response = StoatHttp.patch("/channels/$channelId/messages/$messageId".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(
EditMessageBody( EditMessageBody(
@ -133,7 +133,7 @@ suspend fun editMessage(channelId: String, messageId: String, newContent: String
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -141,55 +141,55 @@ suspend fun editMessage(channelId: String, messageId: String, newContent: String
} }
suspend fun deleteMessage(channelId: String, messageId: String) { suspend fun deleteMessage(channelId: String, messageId: String) {
RevoltHttp.delete("/channels/$channelId/messages/$messageId".api()) StoatHttp.delete("/channels/$channelId/messages/$messageId".api())
} }
suspend fun ackChannel(channelId: String, messageId: String = ULID.makeNext()) { suspend fun ackChannel(channelId: String, messageId: String = ULID.makeNext()) {
RevoltHttp.put("/channels/$channelId/ack/$messageId".api()) StoatHttp.put("/channels/$channelId/ack/$messageId".api())
} }
suspend fun fetchSingleChannel(channelId: String): Channel { suspend fun fetchSingleChannel(channelId: String): Channel {
val response = RevoltHttp.get("/channels/$channelId".api()) val response = StoatHttp.get("/channels/$channelId".api())
.bodyAsText() .bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
Channel.serializer(), Channel.serializer(),
response response
) )
} }
suspend fun fetchGroupParticipants(channelId: String): List<User> { suspend fun fetchGroupParticipants(channelId: String): List<User> {
val response = RevoltHttp.get("/channels/$channelId/members".api()) val response = StoatHttp.get("/channels/$channelId/members".api())
.bodyAsText() .bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
ListSerializer(User.serializer()), ListSerializer(User.serializer()),
response response
) )
} }
suspend fun createInvite(channelId: String): CreateInviteResponse { suspend fun createInvite(channelId: String): CreateInviteResponse {
val response = RevoltHttp.post("/channels/$channelId/invites".api()) val response = StoatHttp.post("/channels/$channelId/invites".api())
.bodyAsText() .bodyAsText()
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
if (error.type != "Server") throw Error(error.type) if (error.type != "Server") throw Error(error.type)
return RevoltJson.decodeFromString(CreateInviteResponse.serializer(), response) return StoatJson.decodeFromString(CreateInviteResponse.serializer(), response)
} }
suspend fun fetchSingleMessage(channelId: String, messageId: String): Message { suspend fun fetchSingleMessage(channelId: String, messageId: String): Message {
val response = RevoltHttp.get("/channels/$channelId/messages/$messageId".api()) val response = StoatHttp.get("/channels/$channelId/messages/$messageId".api())
.bodyAsText() .bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
Message.serializer(), Message.serializer(),
response response
) )
} }
suspend fun leaveDeleteOrCloseChannel(channelId: String, leaveSilently: Boolean = false) { suspend fun leaveDeleteOrCloseChannel(channelId: String, leaveSilently: Boolean = false) {
RevoltHttp.delete("/channels/$channelId".api()) { StoatHttp.delete("/channels/$channelId".api()) {
parameter("leave_silently", leaveSilently) parameter("leave_silently", leaveSilently)
} }
} }
@ -207,33 +207,33 @@ suspend fun patchChannel(
val body = mutableMapOf<String, JsonElement>() val body = mutableMapOf<String, JsonElement>()
if (name != null) { if (name != null) {
body["name"] = RevoltJson.encodeToJsonElement(String.serializer(), name) body["name"] = StoatJson.encodeToJsonElement(String.serializer(), name)
} }
if (description != null) { if (description != null) {
body["description"] = RevoltJson.encodeToJsonElement(String.serializer(), description) body["description"] = StoatJson.encodeToJsonElement(String.serializer(), description)
} }
if (icon != null) { if (icon != null) {
body["icon"] = RevoltJson.encodeToJsonElement(String.serializer(), icon) body["icon"] = StoatJson.encodeToJsonElement(String.serializer(), icon)
} }
if (banner != null) { if (banner != null) {
body["banner"] = RevoltJson.encodeToJsonElement(String.serializer(), banner) body["banner"] = StoatJson.encodeToJsonElement(String.serializer(), banner)
} }
if (remove != null) { if (remove != null) {
body["remove"] = RevoltJson.encodeToJsonElement(ListSerializer(String.serializer()), remove) body["remove"] = StoatJson.encodeToJsonElement(ListSerializer(String.serializer()), remove)
} }
if (nsfw != null) { if (nsfw != null) {
body["nsfw"] = RevoltJson.encodeToJsonElement(Boolean.serializer(), nsfw) body["nsfw"] = StoatJson.encodeToJsonElement(Boolean.serializer(), nsfw)
} }
val response = RevoltHttp.patch("/channels/$channelId".api()) { val response = StoatHttp.patch("/channels/$channelId".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
JsonElement.serializer() JsonElement.serializer()
@ -245,14 +245,14 @@ suspend fun patchChannel(
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
if (!pure) { if (!pure) {
val channel = RevoltJson.decodeFromString(Channel.serializer(), response) val channel = StoatJson.decodeFromString(Channel.serializer(), response)
RevoltAPI.channelCache[channelId] = channel StoatAPI.channelCache[channelId] = channel
} }
} }

View File

@ -1,11 +1,11 @@
package chat.revolt.api.routes.channel package chat.stoat.api.routes.channel
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.Channel import chat.stoat.api.schemas.Channel
import chat.revolt.screens.create.MAX_ADDABLE_PEOPLE_IN_GROUP import chat.stoat.screens.create.MAX_ADDABLE_PEOPLE_IN_GROUP
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.put import io.ktor.client.request.put
@ -28,23 +28,23 @@ suspend fun createGroupDM(name: String, members: List<String>): Channel {
throw Exception("Too many members, maximum is $MAX_ADDABLE_PEOPLE_IN_GROUP") throw Exception("Too many members, maximum is $MAX_ADDABLE_PEOPLE_IN_GROUP")
} }
val response = RevoltHttp.post("/channels/create".api()) { val response = StoatHttp.post("/channels/create".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(CreateGroupDMBody(name, members)) setBody(CreateGroupDMBody(name, members))
}.bodyAsText() }.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
return RevoltJson.decodeFromString(Channel.serializer(), response) return StoatJson.decodeFromString(Channel.serializer(), response)
} }
suspend fun removeMember(channelId: String, userId: String) { suspend fun removeMember(channelId: String, userId: String) {
val response = RevoltHttp.delete("/channels/$channelId/recipients/$userId".api()) val response = StoatHttp.delete("/channels/$channelId/recipients/$userId".api())
if (!response.status.isSuccess()) { if (!response.status.isSuccess()) {
throw Error(response.status.toString()) throw Error(response.status.toString())
@ -52,7 +52,7 @@ suspend fun removeMember(channelId: String, userId: String) {
} }
suspend fun addMember(channelId: String, userId: String) { suspend fun addMember(channelId: String, userId: String) {
val response = RevoltHttp.put("/channels/$channelId/recipients/$userId".api()) val response = StoatHttp.put("/channels/$channelId/recipients/$userId".api())
if (!response.status.isSuccess()) { if (!response.status.isSuccess()) {
throw Error(response.status.toString()) throw Error(response.status.toString())

View File

@ -0,0 +1,14 @@
package chat.stoat.api.routes.channel
import chat.stoat.api.StoatHttp
import chat.stoat.api.api
import io.ktor.client.request.delete
import io.ktor.client.request.put
suspend fun react(channelId: String, messageId: String, emoji: String) {
StoatHttp.put("/channels/$channelId/messages/$messageId/reactions/$emoji".api())
}
suspend fun unreact(channelId: String, messageId: String, emoji: String) {
StoatHttp.delete("/channels/$channelId/messages/$messageId/reactions/$emoji".api())
}

View File

@ -0,0 +1,16 @@
package chat.stoat.api.routes.custom
import chat.stoat.api.StoatHttp
import chat.stoat.api.StoatJson
import chat.stoat.api.api
import chat.stoat.api.schemas.Emoji
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
suspend fun fetchEmoji(id: String): Emoji {
val response = StoatHttp.get("/custom/emoji/$id".api()).bodyAsText()
return StoatJson.decodeFromString(
Emoji.serializer(),
response
)
}

View File

@ -0,0 +1,43 @@
package chat.stoat.api.routes.invites
import chat.stoat.api.StoatAPIError
import chat.stoat.api.StoatHttp
import chat.stoat.api.StoatJson
import chat.stoat.api.api
import chat.stoat.api.schemas.Invite
import chat.stoat.api.schemas.InviteJoined
import chat.stoat.api.schemas.RsResult
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
suspend fun fetchInviteByCode(code: String): RsResult<Invite, StoatAPIError> {
val response = StoatHttp.get("/invites/$code".api())
.bodyAsText()
try {
val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
if (error.type != "Server") return RsResult.err(error)
} catch (e: SerializationException) {
// Not an error
}
val invite = StoatJson.decodeFromString(Invite.serializer(), response)
return RsResult.ok(invite)
}
suspend fun joinInviteByCode(code: String): RsResult<InviteJoined, StoatAPIError> {
val response = StoatHttp.post("/invites/$code".api())
.bodyAsText()
try {
val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
if (error.type != "Server") return RsResult.err(error)
} catch (e: SerializationException) {
// Not an error
}
val invite = StoatJson.decodeFromString(InviteJoined.serializer(), response)
return RsResult.ok(invite)
}

View File

@ -1,12 +1,12 @@
package chat.revolt.api.routes.microservices.autumn package chat.stoat.api.routes.microservices.autumn
import chat.revolt.api.HitRateLimitException import chat.stoat.api.HitRateLimitException
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.schemas.AutumnError import chat.stoat.api.schemas.AutumnError
import chat.revolt.api.schemas.AutumnId import chat.stoat.api.schemas.AutumnId
import io.ktor.client.plugins.onUpload import io.ktor.client.plugins.onUpload
import io.ktor.client.request.forms.MultiPartFormDataContent import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.formData
@ -37,9 +37,9 @@ suspend fun uploadToAutumn(
contentType: ContentType, contentType: ContentType,
onProgress: (Long, Long) -> Unit = { _, _ -> } onProgress: (Long, Long) -> Unit = { _, _ -> }
): String { ): String {
val uploadUrl = "$REVOLT_FILES/$tag" val uploadUrl = "$STOAT_FILES/$tag"
val response = RevoltHttp.post(uploadUrl) { val response = StoatHttp.post(uploadUrl) {
setBody( setBody(
MultiPartFormDataContent( MultiPartFormDataContent(
formData { formData {
@ -54,18 +54,18 @@ suspend fun uploadToAutumn(
} }
) )
) )
header(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken) header(StoatAPI.TOKEN_HEADER_NAME, StoatAPI.sessionToken)
onUpload { bytesSentTotal, contentLength -> onUpload { bytesSentTotal, contentLength ->
contentLength?.let { onProgress(bytesSentTotal, it) } contentLength?.let { onProgress(bytesSentTotal, it) }
} }
} }
try { try {
val autumnId = RevoltJson.decodeFromString(AutumnId.serializer(), response.bodyAsText()) val autumnId = StoatJson.decodeFromString(AutumnId.serializer(), response.bodyAsText())
return autumnId.id return autumnId.id
} catch (e: Exception) { } catch (e: Exception) {
try { try {
val error = RevoltJson.decodeFromString(AutumnError.serializer(), response.bodyAsText()) val error = StoatJson.decodeFromString(AutumnError.serializer(), response.bodyAsText())
throw Exception(error.type) throw Exception(error.type)
} catch (e: Exception) { } catch (e: Exception) {
if (response.status == HttpStatusCode.TooManyRequests) { if (response.status == HttpStatusCode.TooManyRequests) {

View File

@ -1,9 +1,9 @@
package chat.revolt.api.routes.microservices.geo package chat.stoat.api.routes.microservices.geo
import chat.revolt.api.HitRateLimitException import chat.stoat.api.HitRateLimitException
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.buildUserAgent import chat.stoat.api.buildUserAgent
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -18,12 +18,12 @@ data class GeoResponse(
suspend fun queryGeo(): GeoResponse { suspend fun queryGeo(): GeoResponse {
try { try {
val response = RevoltHttp.get("https://geo.revolt.chat/?client=android") { val response = StoatHttp.get("https://geo.revolt.chat/?client=android") {
header("User-Agent", buildUserAgent("Ktor queryGeo")) header("User-Agent", buildUserAgent("Ktor queryGeo"))
} }
if (response.status == HttpStatusCode.OK) { if (response.status == HttpStatusCode.OK) {
return RevoltJson.decodeFromString(response.bodyAsText()) return StoatJson.decodeFromString(response.bodyAsText())
} else throw Exception("Failed to query geo: ${response.status.value} ${response.status.description}") } else throw Exception("Failed to query geo: ${response.status.value} ${response.status.description}")
} catch (e: Exception) { } catch (e: Exception) {
throw Exception("Failed to query geo: ${e.message}", e).also { throw Exception("Failed to query geo: ${e.message}", e).also {

View File

@ -0,0 +1,12 @@
package chat.stoat.api.routes.microservices.health
import chat.stoat.api.StoatHttp
import chat.stoat.api.StoatJson
import chat.stoat.api.schemas.HealthNotice
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
suspend fun healthCheck(): HealthNotice {
val response = StoatHttp.get("https://health.revolt.chat/api/health").bodyAsText()
return StoatJson.decodeFromString(HealthNotice.serializer(), response)
}

View File

@ -0,0 +1,8 @@
package chat.stoat.api.routes.microservices.january
import chat.stoat.api.STOAT_PROXY
import java.net.URLEncoder
fun asJanuaryProxyUrl(url: String): String {
return "$STOAT_PROXY/proxy?url=${URLEncoder.encode(url, "utf-8")}"
}

View File

@ -1,7 +1,7 @@
package chat.revolt.api.routes.misc package chat.stoat.api.routes.misc
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.request.get import io.ktor.client.request.get
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -61,5 +61,5 @@ data class LiveKitNode(
) )
suspend fun getRootRoute(): Root { suspend fun getRootRoute(): Root {
return RevoltHttp.get("/".api()).body() return StoatHttp.get("/".api()).body()
} }

View File

@ -1,12 +1,12 @@
package chat.revolt.api.routes.onboard package chat.stoat.api.routes.onboard
import chat.revolt.api.RateLimitResponse import chat.stoat.api.RateLimitResponse
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.RsResult import chat.stoat.api.schemas.RsResult
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.header import io.ktor.client.request.header
import io.ktor.client.request.post import io.ktor.client.request.post
@ -23,22 +23,22 @@ data class OnboardingResponse(
val onboarding: Boolean val onboarding: Boolean
) )
suspend fun needsOnboarding(sessionToken: String = RevoltAPI.sessionToken): Boolean { suspend fun needsOnboarding(sessionToken: String = StoatAPI.sessionToken): Boolean {
val response = RevoltHttp.get("/onboard/hello".api()) { val response = StoatHttp.get("/onboard/hello".api()) {
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken) header(StoatAPI.TOKEN_HEADER_NAME, sessionToken)
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
try { try {
val rateLimitResponse = val rateLimitResponse =
RevoltJson.decodeFromString(RateLimitResponse.serializer(), responseContent) StoatJson.decodeFromString(RateLimitResponse.serializer(), responseContent)
throw rateLimitResponse.toException() throw rateLimitResponse.toException()
} catch (e: SerializationException) { } catch (e: SerializationException) {
// good path // good path
} }
return RevoltJson.decodeFromString(OnboardingResponse.serializer(), responseContent).onboarding return StoatJson.decodeFromString(OnboardingResponse.serializer(), responseContent).onboarding
} }
@Serializable @Serializable
@ -48,26 +48,26 @@ data class OnboardingCompletionBody(
suspend fun completeOnboarding( suspend fun completeOnboarding(
body: OnboardingCompletionBody, body: OnboardingCompletionBody,
sessionToken: String = RevoltAPI.sessionToken sessionToken: String = StoatAPI.sessionToken
): RsResult<Unit, RevoltError> { ): RsResult<Unit, StoatAPIError> {
val response = RevoltHttp.post("/onboard/complete".api()) { val response = StoatHttp.post("/onboard/complete".api()) {
setBody(body) setBody(body)
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
header(RevoltAPI.TOKEN_HEADER_NAME, sessionToken) header(StoatAPI.TOKEN_HEADER_NAME, sessionToken)
} }
if (response.status == HttpStatusCode.Conflict) { if (response.status == HttpStatusCode.Conflict) {
return RsResult.err(RevoltError("UsernameTaken")) return RsResult.err(StoatAPIError("UsernameTaken"))
} }
if (response.status == HttpStatusCode.BadRequest) { if (response.status == HttpStatusCode.BadRequest) {
return RsResult.err(RevoltError("InvalidUsername")) return RsResult.err(StoatAPIError("InvalidUsername"))
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), responseContent)
return RsResult.err(error) return RsResult.err(error)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error

View File

@ -1,8 +1,8 @@
package chat.revolt.api.routes.push package chat.stoat.api.routes.push
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.routes.account.WebPushData import chat.stoat.api.routes.account.WebPushData
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.http.ContentType import io.ktor.http.ContentType
@ -19,7 +19,7 @@ suspend fun subscribePush(
auth = auth auth = auth
) )
RevoltHttp.post("/push/subscribe".api()) { StoatHttp.post("/push/subscribe".api()) {
setBody(data) setBody(data)
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
} }

View File

@ -1,17 +1,17 @@
package chat.revolt.api.routes.safety package chat.stoat.api.routes.safety
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.schemas.ContentReportReason import chat.stoat.api.schemas.ContentReportReason
import chat.revolt.api.schemas.FullMessageReport import chat.stoat.api.schemas.FullMessageReport
import chat.revolt.api.schemas.FullServerReport import chat.stoat.api.schemas.FullServerReport
import chat.revolt.api.schemas.FullUserReport import chat.stoat.api.schemas.FullUserReport
import chat.revolt.api.schemas.MessageReport import chat.stoat.api.schemas.MessageReport
import chat.revolt.api.schemas.ServerReport import chat.stoat.api.schemas.ServerReport
import chat.revolt.api.schemas.UserReport import chat.stoat.api.schemas.UserReport
import chat.revolt.api.schemas.UserReportReason import chat.stoat.api.schemas.UserReportReason
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -31,9 +31,9 @@ suspend fun putMessageReport(
additional_context = additionalContext additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report".api()) { val response = StoatHttp.post("/safety/report".api()) {
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
FullMessageReport.serializer(), FullMessageReport.serializer(),
fullMessageReport fullMessageReport
) )
@ -42,7 +42,7 @@ suspend fun putMessageReport(
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -63,9 +63,9 @@ suspend fun putServerReport(
additional_context = additionalContext additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report".api()) { val response = StoatHttp.post("/safety/report".api()) {
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
FullServerReport.serializer(), FullServerReport.serializer(),
fullServerReport fullServerReport
) )
@ -74,7 +74,7 @@ suspend fun putServerReport(
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -95,9 +95,9 @@ suspend fun putUserReport(
additional_context = additionalContext additional_context = additionalContext
) )
val response = RevoltHttp.post("/safety/report".api()) { val response = StoatHttp.post("/safety/report".api()) {
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
FullUserReport.serializer(), FullUserReport.serializer(),
fullUserReport fullUserReport
) )
@ -106,7 +106,7 @@ suspend fun putUserReport(
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error

View File

@ -1,13 +1,13 @@
package chat.revolt.api.routes.server package chat.stoat.api.routes.server
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
import chat.revolt.api.schemas.ServerWithChannelObjects import chat.stoat.api.schemas.ServerWithChannelObjects
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.parameter import io.ktor.client.request.parameter
@ -25,7 +25,7 @@ data class FetchMembersResponse(
) )
suspend fun ackServer(serverId: String) { suspend fun ackServer(serverId: String) {
RevoltHttp.put("/servers/$serverId/ack".api()) StoatHttp.put("/servers/$serverId/ack".api())
} }
suspend fun fetchMembers( suspend fun fetchMembers(
@ -33,55 +33,55 @@ suspend fun fetchMembers(
includeOffline: Boolean = false, includeOffline: Boolean = false,
pure: Boolean = false pure: Boolean = false
): FetchMembersResponse { ): FetchMembersResponse {
val response = RevoltHttp.get("/servers/$serverId/members".api()) { val response = StoatHttp.get("/servers/$serverId/members".api()) {
parameter("exclude_offline", !includeOffline) parameter("exclude_offline", !includeOffline)
} }
val responseContent = response.bodyAsText() val responseContent = response.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), responseContent) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), responseContent)
throw Error(error.type) throw Error(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val membersResponse = val membersResponse =
RevoltJson.decodeFromString(FetchMembersResponse.serializer(), responseContent) StoatJson.decodeFromString(FetchMembersResponse.serializer(), responseContent)
if (pure) { if (pure) {
return membersResponse return membersResponse
} }
membersResponse.members.forEach { member -> membersResponse.members.forEach { member ->
if (!RevoltAPI.members.hasMember(serverId, member.id!!.user)) { if (!StoatAPI.members.hasMember(serverId, member.id!!.user)) {
RevoltAPI.members.setMember(serverId, member) StoatAPI.members.setMember(serverId, member)
} }
} }
membersResponse.users.forEach { user -> membersResponse.users.forEach { user ->
user.id?.let { RevoltAPI.userCache.putIfAbsent(it, user) } user.id?.let { StoatAPI.userCache.putIfAbsent(it, user) }
} }
return membersResponse return membersResponse
} }
suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false): Member { suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false): Member {
val response = RevoltHttp.get("/servers/$serverId/members/$userId".api()) val response = StoatHttp.get("/servers/$serverId/members/$userId".api())
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response.bodyAsText()) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response.bodyAsText())
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val member = RevoltJson.decodeFromString(Member.serializer(), response.bodyAsText()) val member = StoatJson.decodeFromString(Member.serializer(), response.bodyAsText())
if (!pure) { if (!pure) {
member.id?.let { member.id?.let {
if (!RevoltAPI.members.hasMember(serverId, it.user)) { if (!StoatAPI.members.hasMember(serverId, it.user)) {
RevoltAPI.members.setMember(serverId, member) StoatAPI.members.setMember(serverId, member)
} }
} }
} }
@ -90,7 +90,7 @@ suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false)
} }
suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false) { suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false) {
RevoltHttp.delete("/servers/$serverId".api()) { StoatHttp.delete("/servers/$serverId".api()) {
parameter("leave_silently", leaveSilently) parameter("leave_silently", leaveSilently)
} }
} }
@ -109,16 +109,16 @@ suspend fun createServer(
): ServerWithChannelObjects { ): ServerWithChannelObjects {
val body = ServerCreationBody(name, description, nsfw) val body = ServerCreationBody(name, description, nsfw)
val response = RevoltHttp.post("/servers/create".api()) { val response = StoatHttp.post("/servers/create".api()) {
setBody(RevoltJson.encodeToString(ServerCreationBody.serializer(), body)) setBody(StoatJson.encodeToString(ServerCreationBody.serializer(), body))
} }
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response.bodyAsText()) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response.bodyAsText())
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
return RevoltJson.decodeFromString(ServerWithChannelObjects.serializer(), response.bodyAsText()) return StoatJson.decodeFromString(ServerWithChannelObjects.serializer(), response.bodyAsText())
} }

View File

@ -1,9 +1,9 @@
package chat.revolt.api.routes.sync package chat.stoat.api.routes.sync
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.parameter import io.ktor.client.request.parameter
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
@ -17,13 +17,13 @@ import kotlinx.serialization.json.JsonArray
@Serializable @Serializable
data class SyncedSetting(val timestamp: Long, val value: String) data class SyncedSetting(val timestamp: Long, val value: String)
suspend fun getKeys(vararg keys: String, revoltToken: String): Map<String, SyncedSetting> { suspend fun getKeys(vararg keys: String, token: String): Map<String, SyncedSetting> {
val response = RevoltHttp.post("/sync/settings/fetch".api()) { val response = StoatHttp.post("/sync/settings/fetch".api()) {
headers.append(RevoltAPI.TOKEN_HEADER_NAME, revoltToken) headers.append(StoatAPI.TOKEN_HEADER_NAME, token)
// format: {"keys": ["key1", "key2"]} // format: {"keys": ["key1", "key2"]}
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
ListSerializer(String.serializer()) ListSerializer(String.serializer())
@ -33,7 +33,7 @@ suspend fun getKeys(vararg keys: String, revoltToken: String): Map<String, Synce
) )
}.bodyAsText() }.bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
JsonArray.serializer() JsonArray.serializer()
@ -52,16 +52,16 @@ suspend fun getKeys(vararg keys: String, revoltToken: String): Map<String, Synce
} }
suspend fun getKeys(vararg keys: String): Map<String, SyncedSetting> { suspend fun getKeys(vararg keys: String): Map<String, SyncedSetting> {
return getKeys(*keys, revoltToken = RevoltAPI.sessionToken) return getKeys(*keys, token = StoatAPI.sessionToken)
} }
suspend fun setKey(key: String, value: String) { suspend fun setKey(key: String, value: String) {
RevoltHttp.post("/sync/settings/set".api()) { StoatHttp.post("/sync/settings/set".api()) {
parameter("timestamp", System.currentTimeMillis()) parameter("timestamp", System.currentTimeMillis())
// format: {"key": "value"} // format: {"key": "value"}
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
String.serializer() String.serializer()

View File

@ -1,18 +1,18 @@
package chat.revolt.api.routes.sync package chat.stoat.api.routes.sync
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.ChannelUnreadResponse import chat.stoat.api.schemas.ChannelUnreadResponse
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.ListSerializer
suspend fun syncUnreads(): List<ChannelUnreadResponse> { suspend fun syncUnreads(): List<ChannelUnreadResponse> {
val response = RevoltHttp.get("/sync/unreads".api()) val response = StoatHttp.get("/sync/unreads".api())
.bodyAsText() .bodyAsText()
return RevoltJson.decodeFromString( return StoatJson.decodeFromString(
ListSerializer(ChannelUnreadResponse.serializer()), ListSerializer(ChannelUnreadResponse.serializer()),
response response
) )

View File

@ -0,0 +1,24 @@
package chat.stoat.api.routes.user
import chat.stoat.api.StoatAPIError
import chat.stoat.api.StoatHttp
import chat.stoat.api.StoatJson
import chat.stoat.api.api
import chat.stoat.api.schemas.Channel
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.SerializationException
suspend fun openDM(userId: String): Channel {
val response = StoatHttp.get("/users/$userId/dm".api())
.bodyAsText()
try {
val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Error(error.type)
} catch (e: SerializationException) {
// Not an error
}
return StoatJson.decodeFromString(Channel.serializer(), response)
}

View File

@ -1,9 +1,9 @@
package chat.revolt.api.routes.user package chat.stoat.api.routes.user
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.delete import io.ktor.client.request.delete
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.put import io.ktor.client.request.put
@ -14,11 +14,11 @@ import io.ktor.http.contentType
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
suspend fun blockUser(userId: String) { suspend fun blockUser(userId: String) {
val response = RevoltHttp.put("/users/$userId/block".api()) val response = StoatHttp.put("/users/$userId/block".api())
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -26,11 +26,11 @@ suspend fun blockUser(userId: String) {
} }
suspend fun unblockUser(userId: String) { suspend fun unblockUser(userId: String) {
val response = RevoltHttp.delete("/users/$userId/block".api()) val response = StoatHttp.delete("/users/$userId/block".api())
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -38,14 +38,14 @@ suspend fun unblockUser(userId: String) {
} }
suspend fun friendUser(username: String) { suspend fun friendUser(username: String) {
val response = RevoltHttp.post("/users/friend".api()) { val response = StoatHttp.post("/users/friend".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(mapOf("username" to username)) setBody(mapOf("username" to username))
} }
val body = response.bodyAsText() val body = response.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), body) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), body)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -53,11 +53,11 @@ suspend fun friendUser(username: String) {
} }
suspend fun acceptFriendRequest(userId: String) { suspend fun acceptFriendRequest(userId: String) {
val response = RevoltHttp.put("/users/$userId/friend".api()) val response = StoatHttp.put("/users/$userId/friend".api())
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
@ -65,11 +65,11 @@ suspend fun acceptFriendRequest(userId: String) {
} }
suspend fun unfriendUser(userId: String) { suspend fun unfriendUser(userId: String) {
val response = RevoltHttp.delete("/users/$userId/friend".api()) val response = StoatHttp.delete("/users/$userId/friend".api())
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error

View File

@ -1,13 +1,13 @@
package chat.revolt.api.routes.user package chat.stoat.api.routes.user
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import chat.revolt.api.schemas.Profile import chat.stoat.api.schemas.Profile
import chat.revolt.api.schemas.Status import chat.stoat.api.schemas.Status
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.patch import io.ktor.client.request.patch
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
@ -21,24 +21,24 @@ import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
suspend fun fetchSelf(): User { suspend fun fetchSelf(): User {
val response = RevoltHttp.get("/users/@me".api()) val response = StoatHttp.get("/users/@me".api())
.bodyAsText() .bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val user = RevoltJson.decodeFromString(User.serializer(), response) val user = StoatJson.decodeFromString(User.serializer(), response)
if (user.id == null) { if (user.id == null) {
throw Exception("Self user ID is null") throw Exception("Self user ID is null")
} }
RevoltAPI.userCache[user.id] = user StoatAPI.userCache[user.id] = user
RevoltAPI.selfId = user.id StoatAPI.selfId = user.id
return user return user
} }
@ -54,11 +54,11 @@ suspend fun patchSelf(
val body = mutableMapOf<String, JsonElement>() val body = mutableMapOf<String, JsonElement>()
if (status != null) { if (status != null) {
body["status"] = RevoltJson.encodeToJsonElement(Status.serializer(), status) body["status"] = StoatJson.encodeToJsonElement(Status.serializer(), status)
} }
if (avatar != null) { if (avatar != null) {
body["avatar"] = RevoltJson.encodeToJsonElement(String.serializer(), avatar) body["avatar"] = StoatJson.encodeToJsonElement(String.serializer(), avatar)
} }
if (background != null || bio != null) { if (background != null || bio != null) {
@ -71,7 +71,7 @@ suspend fun patchSelf(
profileMap["content"] = bio profileMap["content"] = bio
} }
body["profile"] = RevoltJson.encodeToJsonElement( body["profile"] = StoatJson.encodeToJsonElement(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
String.serializer() String.serializer()
@ -81,13 +81,13 @@ suspend fun patchSelf(
} }
if (remove != null) { if (remove != null) {
body["remove"] = RevoltJson.encodeToJsonElement(ListSerializer(String.serializer()), remove) body["remove"] = StoatJson.encodeToJsonElement(ListSerializer(String.serializer()), remove)
} }
val response = RevoltHttp.patch("/users/@me".api()) { val response = StoatHttp.patch("/users/@me".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody( setBody(
RevoltJson.encodeToString( StoatJson.encodeToString(
MapSerializer( MapSerializer(
String.serializer(), String.serializer(),
JsonElement.serializer() JsonElement.serializer()
@ -98,21 +98,21 @@ suspend fun patchSelf(
} }
.bodyAsText() .bodyAsText()
if (RevoltAPI.selfId == null) { if (StoatAPI.selfId == null) {
throw Error("Self ID is null") throw Error("Self ID is null")
} }
val currentUser = RevoltAPI.userCache[RevoltAPI.selfId] ?: fetchSelf() val currentUser = StoatAPI.userCache[StoatAPI.selfId] ?: fetchSelf()
val newUserKeys = RevoltJson.decodeFromString(User.serializer(), response) val newUserKeys = StoatJson.decodeFromString(User.serializer(), response)
val mergedUser = currentUser.mergeWithPartial(newUserKeys) val mergedUser = currentUser.mergeWithPartial(newUserKeys)
if (!pure) { if (!pure) {
RevoltAPI.userCache[RevoltAPI.selfId!!] = mergedUser StoatAPI.userCache[StoatAPI.selfId!!] = mergedUser
} }
} }
suspend fun fetchUser(id: String): User { suspend fun fetchUser(id: String): User {
val res = RevoltHttp.get("/users/$id".api()) val res = StoatHttp.get("/users/$id".api())
if (res.status.value == 404) { if (res.status.value == 404) {
return User.getPlaceholder(id) return User.getPlaceholder(id)
@ -121,42 +121,42 @@ suspend fun fetchUser(id: String): User {
val response = res.bodyAsText() val response = res.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
val user = RevoltJson.decodeFromString(User.serializer(), response) val user = StoatJson.decodeFromString(User.serializer(), response)
user.id?.let { user.id?.let {
RevoltAPI.userCache[it] = user StoatAPI.userCache[it] = user
} }
return user return user
} }
suspend fun getOrFetchUser(id: String): User { suspend fun getOrFetchUser(id: String): User {
return RevoltAPI.userCache[id] ?: fetchUser(id) return StoatAPI.userCache[id] ?: fetchUser(id)
} }
suspend fun addUserIfUnknown(id: String) { suspend fun addUserIfUnknown(id: String) {
if (RevoltAPI.userCache[id] == null) { if (StoatAPI.userCache[id] == null) {
RevoltAPI.userCache[id] = fetchUser(id) StoatAPI.userCache[id] = fetchUser(id)
} }
} }
suspend fun fetchUserProfile(id: String): Profile { suspend fun fetchUserProfile(id: String): Profile {
val res = RevoltHttp.get("/users/$id/profile".api()) val res = StoatHttp.get("/users/$id/profile".api())
val response = res.bodyAsText() val response = res.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
return RevoltJson.decodeFromString(Profile.serializer(), response) return StoatJson.decodeFromString(Profile.serializer(), response)
} }

View File

@ -1,9 +1,9 @@
package chat.revolt.api.routes.voice package chat.stoat.api.routes.voice
import chat.revolt.api.RevoltError import chat.stoat.api.StoatAPIError
import chat.revolt.api.RevoltHttp import chat.stoat.api.StoatHttp
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.api import chat.stoat.api.api
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
@ -19,17 +19,17 @@ data class JoinCallResponse(
) )
suspend fun joinCall(channelId: String, nodeName: String): JoinCallResponse { suspend fun joinCall(channelId: String, nodeName: String): JoinCallResponse {
val response = RevoltHttp.post("/channels/$channelId/join_call".api()) { val response = StoatHttp.post("/channels/$channelId/join_call".api()) {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(mapOf("node" to nodeName)) setBody(mapOf("node" to nodeName))
}.bodyAsText() }.bodyAsText()
try { try {
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response) val error = StoatJson.decodeFromString(StoatAPIError.serializer(), response)
throw Exception(error.type) throw Exception(error.type)
} catch (e: SerializationException) { } catch (e: SerializationException) {
// Not an error // Not an error
} }
return RevoltJson.decodeFromString(JoinCallResponse.serializer(), response) return StoatJson.decodeFromString(JoinCallResponse.serializer(), response)
} }

View File

@ -1,6 +1,6 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -10,6 +10,6 @@ data class Session(
val name: String val name: String
) { ) {
fun isCurrent(): Boolean { fun isCurrent(): Boolean {
return id == RevoltAPI.sessionId return id == StoatAPI.sessionId
} }
} }

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
@ -111,7 +111,7 @@ enum class ChannelType(val value: String) {
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() { get() {
return PrimitiveSerialDescriptor( return PrimitiveSerialDescriptor(
"chat.revolt.api.schemas.ChannelType", "chat.stoat.api.schemas.ChannelType",
PrimitiveKind.STRING PrimitiveKind.STRING
) )
} }

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,9 +1,9 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import chat.revolt.api.REVOLT_APP import chat.stoat.api.STOAT_WEB_APP
import chat.revolt.api.REVOLT_INVITES import chat.stoat.api.STOAT_INVITES
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -52,10 +52,10 @@ data class InviteJoined(
fun Uri.isInviteUri(): Boolean { fun Uri.isInviteUri(): Boolean {
val firstPathSegmentIsInvite = this.pathSegments.firstOrNull() == "invite" val firstPathSegmentIsInvite = this.pathSegments.firstOrNull() == "invite"
val isAppRevoltChat = this.host == REVOLT_APP.toUri().host val isStoatChat = this.host == STOAT_WEB_APP.toUri().host
val matchRvltGG = this.host == REVOLT_INVITES.toUri().host val matchRvltGG = this.host == STOAT_INVITES.toUri().host
val matchApp = isAppRevoltChat && firstPathSegmentIsInvite val matchApp = isStoatChat && firstPathSegmentIsInvite
val hasEnoughSegments = val hasEnoughSegments =
if (matchApp) this.pathSegments.size == 2 else this.pathSegments.size == 1 if (matchApp) this.pathSegments.size == 2 else this.pathSegments.size == 1

View File

@ -1,6 +1,6 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -24,14 +24,14 @@ data class Message(
val interactions: InteractionsDescription? = null, val interactions: InteractionsDescription? = null,
val pinned: Boolean? = null, val pinned: Boolean? = null,
/** /**
* See [chat.revolt.api.internals.MessageFlag] * See [chat.stoat.api.internals.MessageFlag]
*/ */
val flags: Int? = null, val flags: Int? = null,
val type: String? = null, // this is _only_ used for websocket events! val type: String? = null, // this is _only_ used for websocket events!
val tail: Boolean? = null // this is used to determine if the message is the last in a message group val tail: Boolean? = null // this is used to determine if the message is the last in a message group
) { ) {
fun getAuthor(): User? { fun getAuthor(): User? {
return author?.let { RevoltAPI.userCache[it] } return author?.let { StoatAPI.userCache[it] }
} }
fun mergeWithPartial(partial: Message): Message { fun mergeWithPartial(partial: Message): Message {

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -29,7 +29,7 @@ enum class ContentReportReason(val value: String) {
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() { get() {
return PrimitiveSerialDescriptor( return PrimitiveSerialDescriptor(
"chat.revolt.api.schemas.ContentReportReason", "chat.stoat.api.schemas.ContentReportReason",
PrimitiveKind.STRING PrimitiveKind.STRING
) )
} }
@ -73,7 +73,7 @@ enum class UserReportReason(val value: String) {
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() { get() {
return PrimitiveSerialDescriptor( return PrimitiveSerialDescriptor(
"chat.revolt.api.schemas.UserReportReason", "chat.stoat.api.schemas.UserReportReason",
PrimitiveKind.STRING PrimitiveKind.STRING
) )
} }

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,6 +1,6 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import chat.revolt.ui.theme.OverridableColourScheme import chat.stoat.ui.theme.OverridableColourScheme
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
@ -40,7 +40,7 @@ data class AndroidSpecificSettingsSpecialEmbedSettings(
data class AndroidSpecificSettings( data class AndroidSpecificSettings(
/** /**
* The theme to use for the app. * The theme to use for the app.
* Can be one of `{ None, Revolt, Light, M3Dynamic, Amoled }` * Can be one of `{ None, Default, Light, M3Dynamic, Amoled }`
*/ */
var theme: String? = null, var theme: String? = null,
/** /**

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
// Result class similar to Rust std::result::Result // Result class similar to Rust std::result::Result
data class RsResult<V, E>(val value: V?, val error: E?) { data class RsResult<V, E>(val value: V?, val error: E?) {

View File

@ -1,4 +1,4 @@
package chat.revolt.api.schemas package chat.stoat.api.schemas
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,11 +1,11 @@
package chat.revolt.api.settings package chat.stoat.api.settings
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import chat.revolt.BuildConfig import chat.stoat.BuildConfig
import chat.revolt.RevoltApplication import chat.stoat.StoatApplication
import chat.revolt.persistence.KVStorage import chat.stoat.persistence.KVStorage
class ExperimentInstance(default: Boolean) { class ExperimentInstance(default: Boolean) {
private var _isEnabled by mutableStateOf(default) private var _isEnabled by mutableStateOf(default)
@ -32,7 +32,7 @@ object Experiments {
val useFinalMarkdownRenderer = ExperimentInstance(false) val useFinalMarkdownRenderer = ExperimentInstance(false)
suspend fun hydrateWithKv() { suspend fun hydrateWithKv() {
val kvStorage = KVStorage(RevoltApplication.instance) val kvStorage = KVStorage(StoatApplication.instance)
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
LoadedSettings.experimentsEnabled = true LoadedSettings.experimentsEnabled = true

View File

@ -1,10 +1,10 @@
package chat.revolt.api.settings package chat.stoat.api.settings
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.SpecialUsers import chat.stoat.api.internals.SpecialUsers
annotation class FeatureFlag(val name: String) annotation class FeatureFlag(val name: String)
annotation class Treatment(val description: String) annotation class Treatment(val description: String)
@ -73,7 +73,7 @@ object FeatureFlags {
@FeatureFlag("LabsAccessControl") @FeatureFlag("LabsAccessControl")
var labsAccessControl by mutableStateOf<LabsAccessControlVariates>( var labsAccessControl by mutableStateOf<LabsAccessControlVariates>(
LabsAccessControlVariates.Restricted { LabsAccessControlVariates.Restricted {
RevoltAPI.selfId == SpecialUsers.JENNIFER StoatAPI.selfId == SpecialUsers.JENNIFER
} }
) )
@ -85,7 +85,7 @@ object FeatureFlags {
@FeatureFlag("UserCards") @FeatureFlag("UserCards")
var userCards by mutableStateOf<UserCardsVariates>( var userCards by mutableStateOf<UserCardsVariates>(
UserCardsVariates.Restricted { UserCardsVariates.Restricted {
RevoltAPI.selfId?.endsWith("Z") == true StoatAPI.selfId?.endsWith("Z") == true
} }
) )

View File

@ -1,9 +1,9 @@
package chat.revolt.api.settings package chat.stoat.api.settings
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import chat.revolt.api.routes.microservices.geo.GeoResponse import chat.stoat.api.routes.microservices.geo.GeoResponse
object GeoStateProvider { object GeoStateProvider {
var geoState by mutableStateOf<GeoResponse?>(null) var geoState by mutableStateOf<GeoResponse?>(null)

View File

@ -1,12 +1,12 @@
package chat.revolt.api.settings package chat.stoat.api.settings
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import chat.revolt.api.schemas.AndroidSpecificSettingsSpecialEmbedSettings import chat.stoat.api.schemas.AndroidSpecificSettingsSpecialEmbedSettings
import chat.revolt.ui.theme.Theme import chat.stoat.ui.theme.Theme
import chat.revolt.ui.theme.getDefaultTheme import chat.stoat.ui.theme.getDefaultTheme
enum class MessageReplyStyle { enum class MessageReplyStyle {
None, None,

View File

@ -1,4 +1,4 @@
package chat.revolt.api.settings package chat.stoat.api.settings
object NotificationSettingsProvider { object NotificationSettingsProvider {
fun isChannelMuted(channelId: String, serverId: String?): Boolean { fun isChannelMuted(channelId: String, serverId: String?): Boolean {

View File

@ -1,14 +1,14 @@
package chat.revolt.api.settings package chat.stoat.api.settings
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.routes.sync.getKeys import chat.stoat.api.routes.sync.getKeys
import chat.revolt.api.routes.sync.setKey import chat.stoat.api.routes.sync.setKey
import chat.revolt.api.schemas.AndroidSpecificSettings import chat.stoat.api.schemas.AndroidSpecificSettings
import chat.revolt.api.schemas.NotificationSettings import chat.stoat.api.schemas.NotificationSettings
import chat.revolt.api.schemas.OrderingSettings import chat.stoat.api.schemas.OrderingSettings
import chat.revolt.api.schemas._NotificationSettingsToParse import chat.stoat.api.schemas._NotificationSettingsToParse
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import logcat.LogPriority import logcat.LogPriority
@ -40,14 +40,14 @@ object SyncedSettings {
val notifications: NotificationSettings val notifications: NotificationSettings
get() = _notifications.value get() = _notifications.value
suspend fun fetch(revoltToken: String = RevoltAPI.sessionToken) { suspend fun fetch(apiToken: String = StoatAPI.sessionToken) {
try { try {
val settings = val settings =
getKeys("ordering", "android", "notifications", revoltToken = revoltToken) getKeys("ordering", "android", "notifications", token = apiToken)
settings["ordering"]?.let { settings["ordering"]?.let {
try { try {
_ordering.value = RevoltJson.decodeFromString( _ordering.value = StoatJson.decodeFromString(
OrderingSettings.serializer(), OrderingSettings.serializer(),
it.value it.value
) )
@ -59,7 +59,7 @@ object SyncedSettings {
settings["android"]?.let { settings["android"]?.let {
try { try {
_android.value = RevoltJson.decodeFromString( _android.value = StoatJson.decodeFromString(
AndroidSpecificSettings.serializer(), AndroidSpecificSettings.serializer(),
it.value it.value
) )
@ -82,7 +82,7 @@ object SyncedSettings {
private fun parseNotificationSettings(value: String): NotificationSettings { private fun parseNotificationSettings(value: String): NotificationSettings {
return try { return try {
var intermediate = var intermediate =
RevoltJson.decodeFromString(_NotificationSettingsToParse.serializer(), value) StoatJson.decodeFromString(_NotificationSettingsToParse.serializer(), value)
// Throw out any value of intermediate.server and .channel that isn't a string // Throw out any value of intermediate.server and .channel that isn't a string
intermediate = intermediate.copy( intermediate = intermediate.copy(
@ -108,23 +108,23 @@ object SyncedSettings {
suspend fun updateOrdering(value: OrderingSettings) { suspend fun updateOrdering(value: OrderingSettings) {
_ordering.value = value _ordering.value = value
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), value)) setKey("ordering", StoatJson.encodeToString(OrderingSettings.serializer(), value))
} }
suspend fun updateAndroid(value: AndroidSpecificSettings) { suspend fun updateAndroid(value: AndroidSpecificSettings) {
_android.value = value _android.value = value
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), value)) setKey("android", StoatJson.encodeToString(AndroidSpecificSettings.serializer(), value))
} }
suspend fun updateNotifications(value: NotificationSettings) { suspend fun updateNotifications(value: NotificationSettings) {
_notifications.value = value _notifications.value = value
setKey("notifications", RevoltJson.encodeToString(NotificationSettings.serializer(), value)) setKey("notifications", StoatJson.encodeToString(NotificationSettings.serializer(), value))
} }
suspend fun resetOrdering() { suspend fun resetOrdering() {
val default = OrderingSettings() val default = OrderingSettings()
_ordering.value = default _ordering.value = default
setKey("ordering", RevoltJson.encodeToString(OrderingSettings.serializer(), default)) setKey("ordering", StoatJson.encodeToString(OrderingSettings.serializer(), default))
} }
suspend fun resetAndroid() { suspend fun resetAndroid() {
@ -134,7 +134,7 @@ object SyncedSettings {
messageReplyStyle = "None" messageReplyStyle = "None"
) )
_android.value = default _android.value = default
setKey("android", RevoltJson.encodeToString(AndroidSpecificSettings.serializer(), default)) setKey("android", StoatJson.encodeToString(AndroidSpecificSettings.serializer(), default))
} }
suspend fun resetNotifications() { suspend fun resetNotifications() {
@ -142,7 +142,7 @@ object SyncedSettings {
_notifications.value = default _notifications.value = default
setKey( setKey(
"notifications", "notifications",
RevoltJson.encodeToString(NotificationSettings.serializer(), default) StoatJson.encodeToString(NotificationSettings.serializer(), default)
) )
} }
} }

View File

@ -1,16 +1,16 @@
package chat.revolt.api.unreads package chat.stoat.api.unreads
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.ULID import chat.stoat.api.internals.ULID
import chat.revolt.api.routes.channel.ackChannel import chat.stoat.api.routes.channel.ackChannel
import chat.revolt.api.routes.server.ackServer import chat.stoat.api.routes.server.ackServer
import chat.revolt.api.routes.sync.syncUnreads import chat.stoat.api.routes.sync.syncUnreads
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.schemas.ChannelUnread import chat.stoat.api.schemas.ChannelUnread
import chat.revolt.api.settings.NotificationSettingsProvider import chat.stoat.api.settings.NotificationSettingsProvider
class Unreads { class Unreads {
private val hasLoaded = mutableStateOf(false) private val hasLoaded = mutableStateOf(false)
@ -50,8 +50,8 @@ class Unreads {
fun serverHasUnread(serverId: String): Boolean { fun serverHasUnread(serverId: String): Boolean {
if (!hasLoaded.value) return false if (!hasLoaded.value) return false
return RevoltAPI.serverCache[serverId]?.channels?.any { return StoatAPI.serverCache[serverId]?.channels?.any {
val channel = RevoltAPI.channelCache[it] ?: return@any false // Channel not found val channel = StoatAPI.channelCache[it] ?: return@any false // Channel not found
if (channel.channelType == ChannelType.VoiceChannel) return@any false // Channel is voice if (channel.channelType == ChannelType.VoiceChannel) return@any false // Channel is voice
if (NotificationSettingsProvider.isChannelMuted( if (NotificationSettingsProvider.isChannelMuted(
it, it,
@ -81,7 +81,7 @@ class Unreads {
suspend fun markServerAsRead(serverId: String, sync: Boolean = true) { suspend fun markServerAsRead(serverId: String, sync: Boolean = true) {
if (!hasLoaded.value) return if (!hasLoaded.value) return
val server = RevoltAPI.serverCache[serverId] ?: return val server = StoatAPI.serverCache[serverId] ?: return
server.channels?.forEach { channel -> server.channels?.forEach { channel ->
channels[channel] = channels[channel]?.copy(last_id = ULID.makeNext()) ?: ChannelUnread( channels[channel] = channels[channel]?.copy(last_id = ULID.makeNext()) ?: ChannelUnread(
channel, channel,

View File

@ -1,11 +1,11 @@
package chat.revolt.c2dm package chat.stoat.c2dm
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationChannelGroup import android.app.NotificationChannelGroup
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import androidx.core.content.ContextCompat.getSystemService import androidx.core.content.ContextCompat.getSystemService
import chat.revolt.R import chat.stoat.R
// TODO // TODO
// * Add the remaining groups. // * Add the remaining groups.

View File

@ -1,4 +1,4 @@
package chat.revolt.c2dm package chat.stoat.c2dm
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Intent import android.content.Intent
@ -10,17 +10,17 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.app.Person import androidx.core.app.Person
import androidx.core.app.RemoteInput import androidx.core.app.RemoteInput
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import chat.revolt.R import chat.stoat.R
import chat.revolt.activities.MainActivity import chat.stoat.activities.MainActivity
import chat.revolt.api.REVOLT_BASE import chat.stoat.api.STOAT_BASE
import chat.revolt.api.RevoltJson import chat.stoat.api.StoatJson
import chat.revolt.api.internals.ULID import chat.stoat.api.internals.ULID
import chat.revolt.api.routes.push.subscribePush import chat.stoat.api.routes.push.subscribePush
import chat.revolt.api.schemas.Message import chat.stoat.api.schemas.Message
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import chat.revolt.c2dm.ChannelRegistrator.Companion.CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS import chat.stoat.c2dm.ChannelRegistrator.Companion.CHANNEL_ID_GROUP_SOCIAL_FRIENDREQUESTS
import chat.revolt.persistence.Database import chat.stoat.persistence.Database
import chat.revolt.persistence.SqlStorage import chat.stoat.persistence.SqlStorage
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage import com.google.firebase.messaging.RemoteMessage
@ -51,13 +51,13 @@ class HandlerService : FirebaseMessagingService() {
Log.d("HandlerService", payloadString) Log.d("HandlerService", payloadString)
val payload = RevoltJson.parseToJsonElement(payloadString).jsonObject val payload = StoatJson.parseToJsonElement(payloadString).jsonObject
val keys = payload.keys.toList().toString() val keys = payload.keys.toList().toString()
Log.d("HandlerService", "following keys: $keys") Log.d("HandlerService", "following keys: $keys")
var authorIcon = payload["icon"]?.jsonPrimitive?.contentOrNull var authorIcon = payload["icon"]?.jsonPrimitive?.contentOrNull
val message = payload["message"]?.jsonObject?.let { val message = payload["message"]?.jsonObject?.let {
RevoltJson.decodeFromJsonElement( StoatJson.decodeFromJsonElement(
Message.serializer(), Message.serializer(),
it it
) )
@ -67,7 +67,7 @@ class HandlerService : FirebaseMessagingService() {
} }
val user = payload["message"]?.jsonObject?.get("user")?.jsonObject?.let { val user = payload["message"]?.jsonObject?.get("user")?.jsonObject?.let {
RevoltJson.decodeFromJsonElement( StoatJson.decodeFromJsonElement(
User.serializer(), User.serializer(),
it it
) )
@ -78,7 +78,7 @@ class HandlerService : FirebaseMessagingService() {
if (authorIcon == null) { if (authorIcon == null) {
authorIcon = authorIcon =
"$REVOLT_BASE/users/${message.author?.ifBlank { "0".repeat(26) }}/default_avatar" "$STOAT_BASE/users/${message.author?.ifBlank { "0".repeat(26) }}/default_avatar"
} }
val db = Database(SqlStorage.driver) val db = Database(SqlStorage.driver)

View File

@ -1,6 +1,6 @@
package chat.revolt.callbacks package chat.stoat.callbacks
import chat.revolt.screens.chat.ChatRouterDestination import chat.stoat.screens.chat.ChatRouterDestination
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
sealed class Action { sealed class Action {

View File

@ -1,4 +1,4 @@
package chat.revolt.callbacks package chat.stoat.callbacks
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import android.icu.text.DateFormat import android.icu.text.DateFormat
import android.text.format.DateUtils import android.text.format.DateUtils

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -28,10 +28,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.realtime.DisconnectionState import chat.stoat.api.realtime.DisconnectionState
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.ui.theme.Theme import chat.stoat.ui.theme.Theme
private val NON_MATERIAL_COLOURS = mapOf( private val NON_MATERIAL_COLOURS = mapOf(
DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)), DisconnectionState.Disconnected to (Color(0xff4E0C0C) to Color(0xffff1744)),

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -23,14 +23,14 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.api.internals.BrushCompat import chat.stoat.api.internals.BrushCompat
import chat.revolt.api.internals.solidColor import chat.stoat.api.internals.solidColor
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.stoat.api.routes.microservices.january.asJanuaryProxyUrl
import chat.revolt.api.schemas.Embed import chat.stoat.api.schemas.Embed
import chat.revolt.composables.chat.specialembeds.SpecialEmbedSwitch import chat.stoat.composables.chat.specialembeds.SpecialEmbedSwitch
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.composables.markdown.RichMarkdown import chat.stoat.composables.markdown.RichMarkdown
import chat.revolt.api.schemas.Embed as EmbedSchema import chat.stoat.api.schemas.Embed as EmbedSchema
@Composable @Composable
fun RegularEmbed( fun RegularEmbed(

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import android.util.Log import android.util.Log
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
@ -26,16 +26,16 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.solidColor import chat.stoat.api.internals.solidColor
import chat.revolt.api.routes.channel.fetchSingleMessage import chat.stoat.api.routes.channel.fetchSingleMessage
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import chat.revolt.api.settings.Experiments import chat.stoat.api.settings.Experiments
import chat.revolt.composables.generic.UserAvatar import chat.stoat.composables.generic.UserAvatar
import chat.revolt.markdown.jbm.JBM import chat.stoat.markdown.jbm.JBM
import chat.revolt.markdown.jbm.JBMRenderer import chat.stoat.markdown.jbm.JBMRenderer
import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState import chat.stoat.markdown.jbm.LocalJBMarkdownTreeState
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
@OptIn(JBM::class) @OptIn(JBM::class)
@ -47,8 +47,8 @@ fun InReplyTo(
withMention: Boolean = false, withMention: Boolean = false,
onMessageClick: (String) -> Unit = { _ -> } onMessageClick: (String) -> Unit = { _ -> }
) { ) {
val message = RevoltAPI.messageCache[messageId] val message = StoatAPI.messageCache[messageId]
val author = RevoltAPI.userCache[message?.author ?: ""] val author = StoatAPI.userCache[message?.author ?: ""]
val username = message?.let { authorName(it) } val username = message?.let { authorName(it) }
?: author?.let { User.resolveDefaultName(it) } ?: author?.let { User.resolveDefaultName(it) }
@ -58,12 +58,12 @@ fun InReplyTo(
val usernameColor = val usernameColor =
message?.let { authorColour(it) } ?: Brush.solidColor(contentColor) message?.let { authorColour(it) } ?: Brush.solidColor(contentColor)
val serverId = remember(channelId) { RevoltAPI.channelCache[channelId]?.server } val serverId = remember(channelId) { StoatAPI.channelCache[channelId]?.server }
LaunchedEffect(messageId) { LaunchedEffect(messageId) {
if (messageId !in RevoltAPI.messageCache) { if (messageId !in StoatAPI.messageCache) {
try { try {
RevoltAPI.messageCache[messageId] = fetchSingleMessage(channelId, messageId) StoatAPI.messageCache[messageId] = fetchSingleMessage(channelId, messageId)
} catch (e: CancellationException) { } catch (e: CancellationException) {
// It's fine // It's fine
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -9,7 +9,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.R import chat.stoat.R
enum class InlineBadge { enum class InlineBadge {
Bot, Bot,

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.material3.ListItem import androidx.compose.material3.ListItem
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
@ -8,15 +8,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.internals.BrushCompat import chat.stoat.api.internals.BrushCompat
import chat.revolt.api.internals.Roles import chat.stoat.api.internals.Roles
import chat.revolt.api.internals.solidColor import chat.stoat.api.internals.solidColor
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import chat.revolt.composables.generic.UserAvatar import chat.stoat.composables.generic.UserAvatar
import chat.revolt.composables.generic.presenceFromStatus import chat.stoat.composables.generic.presenceFromStatus
import chat.revolt.internals.extensions.TransparentListItemColours import chat.stoat.internals.extensions.TransparentListItemColours
@Composable @Composable
fun MemberListItem( fun MemberListItem(
@ -74,7 +74,7 @@ fun MemberListItem(
?: user?.id ?: user?.id
?: userId, ?: userId,
avatar = user?.avatar, avatar = user?.avatar,
rawUrl = member?.avatar?.let { "$REVOLT_FILES/avatars/${it.id}" }, rawUrl = member?.avatar?.let { "$STOAT_FILES/avatars/${it.id}" },
userId = userId, userId = userId,
presence = presenceFromStatus( presence = presenceFromStatus(
user?.status?.presence, user?.status?.presence,

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
@ -59,41 +59,41 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.R import chat.stoat.R
import chat.revolt.activities.media.ImageViewActivity import chat.stoat.activities.media.ImageViewActivity
import chat.revolt.activities.media.VideoViewActivity import chat.stoat.activities.media.VideoViewActivity
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.BrushCompat import chat.stoat.api.internals.BrushCompat
import chat.revolt.api.internals.MessageFlag import chat.stoat.api.internals.MessageFlag
import chat.revolt.api.internals.Roles import chat.stoat.api.internals.Roles
import chat.revolt.api.internals.SpecialUsers import chat.stoat.api.internals.SpecialUsers
import chat.revolt.api.internals.ULID import chat.stoat.api.internals.ULID
import chat.revolt.api.internals.has import chat.stoat.api.internals.has
import chat.revolt.api.internals.solidColor import chat.stoat.api.internals.solidColor
import chat.revolt.api.routes.channel.react import chat.stoat.api.routes.channel.react
import chat.revolt.api.routes.channel.unreact import chat.stoat.api.routes.channel.unreact
import chat.revolt.api.routes.microservices.january.asJanuaryProxyUrl import chat.stoat.api.routes.microservices.january.asJanuaryProxyUrl
import chat.revolt.api.schemas.AutumnResource import chat.stoat.api.schemas.AutumnResource
import chat.revolt.api.schemas.User import chat.stoat.api.schemas.User
import chat.revolt.api.settings.Experiments import chat.stoat.api.settings.Experiments
import chat.revolt.api.settings.LoadedSettings import chat.stoat.api.settings.LoadedSettings
import chat.revolt.api.settings.MessageReplyStyle import chat.stoat.api.settings.MessageReplyStyle
import chat.revolt.callbacks.Action import chat.stoat.callbacks.Action
import chat.revolt.callbacks.ActionChannel import chat.stoat.callbacks.ActionChannel
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.composables.generic.UserAvatar import chat.stoat.composables.generic.UserAvatar
import chat.revolt.composables.generic.UserAvatarWidthPlaceholder import chat.stoat.composables.generic.UserAvatarWidthPlaceholder
import chat.revolt.composables.markdown.LocalMarkdownTreeConfig import chat.stoat.composables.markdown.LocalMarkdownTreeConfig
import chat.revolt.composables.markdown.RichMarkdown import chat.stoat.composables.markdown.RichMarkdown
import chat.revolt.internals.text.Gigamoji import chat.stoat.internals.text.Gigamoji
import chat.revolt.internals.text.MessageProcessor import chat.stoat.internals.text.MessageProcessor
import chat.revolt.markdown.jbm.JBM import chat.stoat.markdown.jbm.JBM
import chat.revolt.markdown.jbm.JBMRenderer import chat.stoat.markdown.jbm.JBMRenderer
import chat.revolt.markdown.jbm.LocalJBMarkdownTreeState import chat.stoat.markdown.jbm.LocalJBMarkdownTreeState
import chat.revolt.persistence.KVStorage import chat.stoat.persistence.KVStorage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import chat.revolt.api.schemas.Message as MessageSchema import chat.stoat.api.schemas.Message as MessageSchema
@Composable @Composable
fun authorColour(message: MessageSchema): Brush { fun authorColour(message: MessageSchema): Brush {
@ -102,7 +102,7 @@ fun authorColour(message: MessageSchema): Brush {
} else { } else {
val defaultColour = Brush.solidColor(LocalContentColor.current) val defaultColour = Brush.solidColor(LocalContentColor.current)
val serverId = RevoltAPI.channelCache[message.channel]?.server ?: return defaultColour val serverId = StoatAPI.channelCache[message.channel]?.server ?: return defaultColour
val highestRole = message.author?.let { val highestRole = message.author?.let {
Roles.resolveHighestRole(serverId, it, withColour = true) Roles.resolveHighestRole(serverId, it, withColour = true)
@ -120,14 +120,14 @@ fun authorName(message: MessageSchema): String {
} }
val serverId = val serverId =
RevoltAPI.channelCache[message.channel]?.server StoatAPI.channelCache[message.channel]?.server
?: return RevoltAPI.userCache[message.author]?.let { User.resolveDefaultName(it) } ?: return StoatAPI.userCache[message.author]?.let { User.resolveDefaultName(it) }
?: stringResource(R.string.unknown) ?: stringResource(R.string.unknown)
val member = message.author?.let { RevoltAPI.members.getMember(serverId, it) } val member = message.author?.let { StoatAPI.members.getMember(serverId, it) }
?: return stringResource(R.string.unknown) ?: return stringResource(R.string.unknown)
return member.nickname return member.nickname
?: RevoltAPI.userCache[message.author]?.let { User.resolveDefaultName(it) } ?: StoatAPI.userCache[message.author]?.let { User.resolveDefaultName(it) }
?: stringResource(R.string.unknown) ?: stringResource(R.string.unknown)
} }
@ -138,11 +138,11 @@ fun authorAvatarUrl(message: MessageSchema): String? {
} }
val serverId = val serverId =
RevoltAPI.channelCache[message.channel]?.server ?: return null StoatAPI.channelCache[message.channel]?.server ?: return null
val member = message.author?.let { RevoltAPI.members.getMember(serverId, it) } val member = message.author?.let { StoatAPI.members.getMember(serverId, it) }
?: return null ?: return null
return member.avatar?.let { "$REVOLT_FILES/avatars/${it.id}" } return member.avatar?.let { "$STOAT_FILES/avatars/${it.id}" }
} }
fun viewUrlInBrowser(ctx: android.content.Context, url: String) { fun viewUrlInBrowser(ctx: android.content.Context, url: String) {
@ -153,7 +153,7 @@ fun viewUrlInBrowser(ctx: android.content.Context, url: String) {
} }
fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnResource) { fun viewAttachmentInBrowser(ctx: android.content.Context, attachment: AutumnResource) {
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}" val url = "$STOAT_FILES/attachments/${attachment.id}/${attachment.filename}"
viewUrlInBrowser(ctx, url) viewUrlInBrowser(ctx, url)
} }
@ -198,7 +198,7 @@ fun Message(
webhookName: String? = null, webhookName: String? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator() val author = StoatAPI.userCache[message.author] ?: return CircularProgressIndicator()
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -230,8 +230,8 @@ fun Message(
var mentionsSelfRole by remember(message) { mutableStateOf(false) } var mentionsSelfRole by remember(message) { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
val serverId = val serverId =
RevoltAPI.channelCache[message.channel]?.server ?: return@LaunchedEffect StoatAPI.channelCache[message.channel]?.server ?: return@LaunchedEffect
var selfMember = RevoltAPI.selfId?.let { RevoltAPI.members.getMember(serverId, it) } var selfMember = StoatAPI.selfId?.let { StoatAPI.members.getMember(serverId, it) }
?: return@LaunchedEffect ?: return@LaunchedEffect
var messageRoleMentions = MessageProcessor.findMentionedRoleIDs(message.content) var messageRoleMentions = MessageProcessor.findMentionedRoleIDs(message.content)
@ -281,7 +281,7 @@ fun Message(
} else { } else {
Column( Column(
modifier = Modifier.then( modifier = Modifier.then(
if ((message.mentions?.contains(RevoltAPI.selfId) == true) if ((message.mentions?.contains(StoatAPI.selfId) == true)
|| mentionsSelfRole || mentionsSelfRole
|| message.flags has MessageFlag.MentionsOnline || message.flags has MessageFlag.MentionsOnline
|| message.flags has MessageFlag.MentionsEveryone || message.flags has MessageFlag.MentionsEveryone
@ -295,7 +295,7 @@ fun Message(
) )
) { ) {
message.replies?.forEach { reply -> message.replies?.forEach { reply ->
val replyMessage = RevoltAPI.messageCache[reply] val replyMessage = StoatAPI.messageCache[reply]
message.channel?.let { chId -> message.channel?.let { chId ->
InReplyTo( InReplyTo(
@ -431,7 +431,7 @@ fun Message(
if (Experiments.useKotlinBasedMarkdownRenderer.isEnabled) { if (Experiments.useKotlinBasedMarkdownRenderer.isEnabled) {
CompositionLocalProvider( CompositionLocalProvider(
LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy( LocalJBMarkdownTreeState provides LocalJBMarkdownTreeState.current.copy(
currentServer = RevoltAPI.channelCache[message.channel]?.server, currentServer = StoatAPI.channelCache[message.channel]?.server,
fontSizeMultiplier = Gigamoji.useGigamojiForMessage( fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
message.content message.content
) )
@ -446,7 +446,7 @@ fun Message(
} else { } else {
CompositionLocalProvider( CompositionLocalProvider(
LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy( LocalMarkdownTreeConfig provides LocalMarkdownTreeConfig.current.copy(
currentServer = RevoltAPI.channelCache[message.channel]?.server, currentServer = StoatAPI.channelCache[message.channel]?.server,
fontSizeMultiplier = Gigamoji.useGigamojiForMessage( fontSizeMultiplier = Gigamoji.useGigamojiForMessage(
message.content message.content
) )

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.text.format.Formatter import android.text.format.Formatter
@ -32,11 +32,11 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.R import chat.stoat.R
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.schemas.AutumnResource import chat.stoat.api.schemas.AutumnResource
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.composables.media.AudioPlayer import chat.stoat.composables.media.AudioPlayer
import dev.chrisbanes.haze.hazeEffect import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
@ -83,7 +83,7 @@ fun FileAttachment(attachment: AutumnResource) {
@SuppressLint("UnusedBoxWithConstraintsScope") @SuppressLint("UnusedBoxWithConstraintsScope")
@Composable @Composable
fun ImageAttachment(attachment: AutumnResource) { fun ImageAttachment(attachment: AutumnResource) {
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}" val url = "$STOAT_FILES/attachments/${attachment.id}/${attachment.filename}"
var spoilerShown by remember { mutableStateOf(false) } var spoilerShown by remember { mutableStateOf(false) }
val hazeState = val hazeState =
if (attachment.filename?.startsWith("SPOILER_") == true) rememberHazeState() else null if (attachment.filename?.startsWith("SPOILER_") == true) rememberHazeState() else null
@ -149,7 +149,7 @@ fun VideoPlayButton() {
@SuppressLint("UnusedBoxWithConstraintsScope") @SuppressLint("UnusedBoxWithConstraintsScope")
@Composable @Composable
fun VideoAttachment(attachment: AutumnResource) { fun VideoAttachment(attachment: AutumnResource) {
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}" val url = "$STOAT_FILES/attachments/${attachment.id}/${attachment.filename}"
BoxWithConstraints { BoxWithConstraints {
Box( Box(
@ -179,7 +179,7 @@ fun VideoAttachment(attachment: AutumnResource) {
@Composable @Composable
fun AudioAttachment(attachment: AutumnResource) { fun AudioAttachment(attachment: AutumnResource) {
val url = "$REVOLT_FILES/attachments/${attachment.id}/${attachment.filename}" val url = "$STOAT_FILES/attachments/${attachment.id}/${attachment.filename}"
AudioPlayer( AudioPlayer(
url = url, url = url,
filename = attachment.filename ?: "Audio", filename = attachment.filename ?: "Audio",

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import android.net.Uri import android.net.Uri
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
@ -79,17 +79,17 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import chat.revolt.R import chat.stoat.R
import chat.revolt.activities.RevoltTweenFloat import chat.stoat.activities.StoatTweenFloat
import chat.revolt.activities.RevoltTweenInt import chat.stoat.activities.StoatTweenInt
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.internals.BrushCompat import chat.stoat.api.internals.BrushCompat
import chat.revolt.api.schemas.ChannelType import chat.stoat.api.schemas.ChannelType
import chat.revolt.api.schemas.Member import chat.stoat.api.schemas.Member
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
import chat.revolt.composables.generic.UserAvatar import chat.stoat.composables.generic.UserAvatar
import chat.revolt.composables.screens.chat.ChannelIcon import chat.stoat.composables.screens.chat.ChannelIcon
import chat.revolt.internals.Autocomplete import chat.stoat.internals.Autocomplete
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
fun Pair<Int, Int>.asTextRange(): TextRange { fun Pair<Int, Int>.asTextRange(): TextRange {
@ -111,25 +111,25 @@ private fun CharSequence.lastWordStartsAt(): Int {
sealed class AutocompleteSuggestion { sealed class AutocompleteSuggestion {
data class User( data class User(
val user: chat.revolt.api.schemas.User, val user: chat.stoat.api.schemas.User,
val member: Member?, val member: Member?,
val query: String val query: String
) : AutocompleteSuggestion() ) : AutocompleteSuggestion()
data class Channel( data class Channel(
val channel: chat.revolt.api.schemas.Channel, val channel: chat.stoat.api.schemas.Channel,
val query: String val query: String
) : AutocompleteSuggestion() ) : AutocompleteSuggestion()
data class Emoji( data class Emoji(
val shortcode: String, val shortcode: String,
val unicode: String?, val unicode: String?,
val custom: chat.revolt.api.schemas.Emoji?, val custom: chat.stoat.api.schemas.Emoji?,
val query: String val query: String
) : AutocompleteSuggestion() ) : AutocompleteSuggestion()
data class Role( data class Role(
val role: chat.revolt.api.schemas.Role, val role: chat.stoat.api.schemas.Role,
val id: String, val id: String,
val query: String val query: String
) : AutocompleteSuggestion() ) : AutocompleteSuggestion()
@ -318,7 +318,7 @@ fun MessageField(
userId = item.user.id ?: "", userId = item.user.id ?: "",
avatar = item.user.avatar, avatar = item.user.avatar,
rawUrl = item.member?.avatar?.id?.let { rawUrl = item.member?.avatar?.id?.let {
"$REVOLT_FILES/avatars/$it" "$STOAT_FILES/avatars/$it"
}, },
size = SuggestionChipDefaults.IconSize, size = SuggestionChipDefaults.IconSize,
) )
@ -444,7 +444,7 @@ fun MessageField(
) )
} else { } else {
RemoteImage( RemoteImage(
url = "$REVOLT_FILES/emojis/${item.custom?.id}", url = "$STOAT_FILES/emojis/${item.custom?.id}",
description = null, description = null,
contentScale = ContentScale.Fit, contentScale = ContentScale.Fit,
modifier = Modifier modifier = Modifier
@ -606,18 +606,18 @@ fun MessageField(
full.height full.height
) )
}) + slideInHorizontally( }) + slideInHorizontally(
animationSpec = RevoltTweenInt, animationSpec = StoatTweenInt,
initialOffsetX = { -it } initialOffsetX = { -it }
) + fadeIn(animationSpec = RevoltTweenFloat), ) + fadeIn(animationSpec = StoatTweenFloat),
exit = shrinkOut(targetSize = { full -> exit = shrinkOut(targetSize = { full ->
IntSize( IntSize(
0, 0,
full.height full.height
) )
}) + slideOutHorizontally( }) + slideOutHorizontally(
animationSpec = RevoltTweenInt, animationSpec = StoatTweenInt,
targetOffsetX = { it } targetOffsetX = { it }
) + fadeOut(animationSpec = RevoltTweenFloat) ) + fadeOut(animationSpec = StoatTweenFloat)
) { ) {
Icon( Icon(
painter = when { painter = when {

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState import androidx.compose.animation.animateColorAsState
@ -28,10 +28,10 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.revolt.api.REVOLT_FILES import chat.stoat.api.STOAT_FILES
import chat.revolt.api.RevoltAPI import chat.stoat.api.StoatAPI
import chat.revolt.api.internals.isUlid import chat.stoat.api.internals.isUlid
import chat.revolt.composables.generic.RemoteImage import chat.stoat.composables.generic.RemoteImage
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@ -41,7 +41,7 @@ fun Reaction(
onClick: (Boolean) -> Unit, onClick: (Boolean) -> Unit,
onLongClick: () -> Unit onLongClick: () -> Unit
) { ) {
val hasOwn = members.contains(RevoltAPI.selfId) val hasOwn = members.contains(StoatAPI.selfId)
val background by animateColorAsState( val background by animateColorAsState(
targetValue = if (hasOwn) { targetValue = if (hasOwn) {
@ -74,7 +74,7 @@ fun Reaction(
CompositionLocalProvider(LocalContentColor provides foreground) { CompositionLocalProvider(LocalContentColor provides foreground) {
if (emoji.isUlid()) { if (emoji.isUlid()) {
RemoteImage( RemoteImage(
url = "$REVOLT_FILES/emojis/${emoji}", url = "$STOAT_FILES/emojis/${emoji}",
description = null, description = null,
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )

View File

@ -1,4 +1,4 @@
package chat.revolt.composables.chat package chat.stoat.composables.chat
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box

Some files were not shown because too many files have changed in this diff Show More