mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-11-14 08:06:58 +01:00
feat: cloud update notice
This commit is contained in:
1 parent
86c60aa698
commit
0d69f2b475
15 files changed
+80
-24
No files matched your search
+53
-24
@@ -13228,10 +13228,30 @@ impl McpServer {
|
||||
/// to do next; a stable code and its parameters are something it can branch
|
||||
/// on, and it is the same envelope the desktop and the REST API answer with.
|
||||
fn cloud_error(err: crate::cookie_bot::CookieBotError) -> McpError {
|
||||
McpError {
|
||||
code: -32000,
|
||||
message: err.to_error_json(),
|
||||
data: None,
|
||||
Self::cloud_failure(err.to_error_json())
|
||||
}
|
||||
|
||||
/// A cloud failure for an agent. A code with a sentence of its own gets that
|
||||
/// sentence as the message and the envelope as data; any other code keeps
|
||||
/// the envelope as the message.
|
||||
fn cloud_failure(envelope: String) -> McpError {
|
||||
let parsed = serde_json::from_str::<serde_json::Value>(&envelope).ok();
|
||||
let sentence = parsed
|
||||
.as_ref()
|
||||
.and_then(|value| value.get("code"))
|
||||
.and_then(|code| code.as_str())
|
||||
.and_then(cloud_failure_sentence);
|
||||
match sentence {
|
||||
Some(text) => McpError {
|
||||
code: -32000,
|
||||
message: text.to_string(),
|
||||
data: parsed,
|
||||
},
|
||||
None => McpError {
|
||||
code: -32000,
|
||||
message: envelope,
|
||||
data: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13310,11 +13330,7 @@ impl McpServer {
|
||||
|
||||
let outcome = crate::remote_session::start_remote_session(app, &profile, url)
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: e.to_error_json(),
|
||||
data: None,
|
||||
})?;
|
||||
.map_err(|e| Self::cloud_failure(e.to_error_json()))?;
|
||||
Self::json_content(&outcome)
|
||||
}
|
||||
|
||||
@@ -13325,22 +13341,14 @@ impl McpServer {
|
||||
let session_id = Self::require_str(arguments, "session_id")?;
|
||||
let outcome = crate::remote_session::end_remote_session(session_id)
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: e.to_error_json(),
|
||||
data: None,
|
||||
})?;
|
||||
.map_err(|e| Self::cloud_failure(e.to_error_json()))?;
|
||||
Self::json_content(&outcome)
|
||||
}
|
||||
|
||||
async fn handle_list_remote_sessions() -> Result<serde_json::Value, McpError> {
|
||||
let sessions = crate::remote_session::list_remote_sessions()
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: e.to_error_json(),
|
||||
data: None,
|
||||
})?;
|
||||
.map_err(|e| Self::cloud_failure(e.to_error_json()))?;
|
||||
Self::json_content(&sessions)
|
||||
}
|
||||
|
||||
@@ -13350,11 +13358,7 @@ impl McpServer {
|
||||
let session_id = Self::require_str(arguments, "session_id")?;
|
||||
let state = crate::remote_session::get_remote_session(session_id)
|
||||
.await
|
||||
.map_err(|e| McpError {
|
||||
code: -32000,
|
||||
message: e.to_error_json(),
|
||||
data: None,
|
||||
})?;
|
||||
.map_err(|e| Self::cloud_failure(e.to_error_json()))?;
|
||||
Self::json_content(&state)
|
||||
}
|
||||
|
||||
@@ -13562,6 +13566,16 @@ impl McpServer {
|
||||
|
||||
static MCP_SERVER: std::sync::LazyLock<McpServer> = std::sync::LazyLock::new(McpServer::new);
|
||||
|
||||
/// What an agent reads for a cloud code it should relay to the person.
|
||||
fn cloud_failure_sentence(code: &str) -> Option<&'static str> {
|
||||
match code {
|
||||
"CLOUD_BROWSER_UPDATING" => Some(
|
||||
"The cloud browser is updating to this profile's Wayfern version. Try again in a few minutes.",
|
||||
),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "mcp_server_browser_tests.rs"]
|
||||
mod browser_tests;
|
||||
@@ -13570,6 +13584,21 @@ mod browser_tests;
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_cloud_update_refusal_reads_as_a_sentence_with_its_envelope() {
|
||||
let envelope = r#"{"code":"CLOUD_BROWSER_UPDATING","params":{"cloudVersion":"1.0.0.1","profileVersion":"1.0.0.2"}}"#;
|
||||
let error = McpServer::cloud_failure(envelope.to_string());
|
||||
assert!(error.message.starts_with("The cloud browser is updating"));
|
||||
let data = error.data.expect("envelope as data");
|
||||
assert_eq!(data["code"], "CLOUD_BROWSER_UPDATING");
|
||||
assert_eq!(data["params"]["profileVersion"], "1.0.0.2");
|
||||
|
||||
let other = r#"{"code":"REMOTE_NO_CAPACITY"}"#;
|
||||
let error = McpServer::cloud_failure(other.to_string());
|
||||
assert_eq!(error.message, other);
|
||||
assert!(error.data.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_engine_plan_refusal_is_explained_by_a_device_refusal() {
|
||||
let engine = || {
|
||||
|
||||
@@ -659,6 +659,7 @@ export const OUTCOME_KEYS: Record<string, string> = {
|
||||
quota_exhausted: "cookieBot.outcome.quotaExhausted",
|
||||
profile_locked: "cookieBot.outcome.profileLocked",
|
||||
no_capacity: "cookieBot.outcome.noCapacity",
|
||||
cloud_browser_updating: "cookieBot.outcome.cloudBrowserUpdating",
|
||||
manager_error: "cookieBot.outcome.managerError",
|
||||
budget_exceeded: "cookieBot.outcome.budgetExceeded",
|
||||
// The commonest real outcome of the five missing ones, and it was showing
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "Melden Sie sich bei Ihrem Donut Browser-Konto an, um dies zu nutzen.",
|
||||
"cloudUnreachable": "Die Server von Donut Browser sind nicht erreichbar. Prüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
|
||||
"cloudRequestFailed": "Die Anfrage ist fehlgeschlagen. Bitte versuchen Sie es gleich noch einmal.",
|
||||
"cloudBrowserUpdating": "Der Cloud-Browser wird auf Ihre Wayfern-Version aktualisiert. Versuchen Sie es in ein paar Minuten erneut.",
|
||||
"remoteRateLimited": "Zu viele Anfragen. Warten Sie einen Moment und versuchen Sie es erneut.",
|
||||
"remoteNoCapacity": "Derzeit ist kein entfernter Host frei. Versuchen Sie es in einigen Minuten erneut.",
|
||||
"remoteNotEntitled": "Ihr Tarif umfasst keine entfernte Ausführung.",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "Remote-Stunden aufgebraucht",
|
||||
"profileLocked": "Das Profil war an anderer Stelle geöffnet",
|
||||
"noCapacity": "Kein Rechner war frei",
|
||||
"cloudBrowserUpdating": "Der Cloud-Browser wurde gerade auf Ihre Wayfern-Version aktualisiert",
|
||||
"managerError": "Die Remote-Rechner konnten den Browser nicht starten",
|
||||
"budgetExceeded": "Das Zeitbudget des Durchlaufs war aufgebraucht",
|
||||
"browsingIncomplete": "Der Browser wurde geöffnet, aber das Surfen wurde nicht abgeschlossen",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "Sign in to your Donut Browser account to use this.",
|
||||
"cloudUnreachable": "Could not reach Donut Browser's servers. Check your connection and try again.",
|
||||
"cloudRequestFailed": "The request failed. Please try again in a moment.",
|
||||
"cloudBrowserUpdating": "The cloud browser is updating to your Wayfern version. Try again in a few minutes.",
|
||||
"remoteRateLimited": "Too many requests. Wait a moment and try again.",
|
||||
"remoteNoCapacity": "No remote host is free right now. Try again in a few minutes.",
|
||||
"remoteNotEntitled": "Your plan does not include remote execution.",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "Remote hours used up",
|
||||
"profileLocked": "The profile was open somewhere else",
|
||||
"noCapacity": "No machine was free",
|
||||
"cloudBrowserUpdating": "The cloud browser was updating to your Wayfern version",
|
||||
"managerError": "The fleet could not start the browser",
|
||||
"budgetExceeded": "The run's time budget ran out",
|
||||
"browsingIncomplete": "The browser opened but the browsing did not finish",
|
||||
|
||||
@@ -2708,6 +2708,7 @@
|
||||
"cloudNotSignedIn": "Inicia sesión en tu cuenta de Donut Browser para usar esto.",
|
||||
"cloudUnreachable": "No se pudo contactar con los servidores de Donut Browser. Comprueba tu conexión e inténtalo de nuevo.",
|
||||
"cloudRequestFailed": "La solicitud falló. Inténtalo de nuevo en un momento.",
|
||||
"cloudBrowserUpdating": "El navegador en la nube se está actualizando a tu versión de Wayfern. Vuelve a intentarlo en unos minutos.",
|
||||
"remoteRateLimited": "Demasiadas solicitudes. Espera un momento e inténtalo de nuevo.",
|
||||
"remoteNoCapacity": "Ahora mismo no hay ninguna máquina remota libre. Inténtalo de nuevo en unos minutos.",
|
||||
"remoteNotEntitled": "Tu plan no incluye la ejecución remota.",
|
||||
@@ -3759,6 +3760,7 @@
|
||||
"quotaExhausted": "Horas remotas agotadas",
|
||||
"profileLocked": "El perfil estaba abierto en otro lugar",
|
||||
"noCapacity": "No había ninguna máquina libre",
|
||||
"cloudBrowserUpdating": "El navegador en la nube se estaba actualizando a tu versión de Wayfern",
|
||||
"managerError": "La flota no pudo iniciar el navegador",
|
||||
"budgetExceeded": "Se agotó el tiempo previsto para la ejecución",
|
||||
"browsingIncomplete": "El navegador se abrió, pero la navegación no terminó",
|
||||
|
||||
@@ -2708,6 +2708,7 @@
|
||||
"cloudNotSignedIn": "Connectez-vous à votre compte Donut Browser pour utiliser cette fonction.",
|
||||
"cloudUnreachable": "Impossible de joindre les serveurs de Donut Browser. Vérifiez votre connexion et réessayez.",
|
||||
"cloudRequestFailed": "La requête a échoué. Réessayez dans un instant.",
|
||||
"cloudBrowserUpdating": "Le navigateur cloud se met à jour vers votre version de Wayfern. Réessayez dans quelques minutes.",
|
||||
"remoteRateLimited": "Trop de requêtes. Patientez un instant et réessayez.",
|
||||
"remoteNoCapacity": "Aucune machine distante n'est libre pour le moment. Réessayez dans quelques minutes.",
|
||||
"remoteNotEntitled": "Votre forfait n'inclut pas l'exécution à distance.",
|
||||
@@ -3759,6 +3760,7 @@
|
||||
"quotaExhausted": "Heures distantes épuisées",
|
||||
"profileLocked": "Le profil était ouvert ailleurs",
|
||||
"noCapacity": "Aucune machine n'était libre",
|
||||
"cloudBrowserUpdating": "Le navigateur cloud se mettait à jour vers votre version de Wayfern",
|
||||
"managerError": "La flotte n'a pas pu démarrer le navigateur",
|
||||
"budgetExceeded": "Le temps prévu pour l'exécution est épuisé",
|
||||
"browsingIncomplete": "Le navigateur s'est ouvert mais la navigation n'est pas allée à son terme",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "この機能を使うには Donut Browser アカウントにサインインしてください。",
|
||||
"cloudUnreachable": "Donut Browser のサーバーに接続できませんでした。接続を確認してもう一度お試しください。",
|
||||
"cloudRequestFailed": "リクエストに失敗しました。しばらくしてからもう一度お試しください。",
|
||||
"cloudBrowserUpdating": "クラウドブラウザーをお使いの Wayfern のバージョンに更新しています。数分後にもう一度お試しください。",
|
||||
"remoteRateLimited": "リクエストが多すぎます。少し待ってからもう一度お試しください。",
|
||||
"remoteNoCapacity": "現在空いているリモートマシンがありません。数分後にもう一度お試しください。",
|
||||
"remoteNotEntitled": "ご利用のプランにはリモート実行が含まれていません。",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "リモート時間を使い切りました",
|
||||
"profileLocked": "プロファイルが別の場所で開かれていました",
|
||||
"noCapacity": "空きマシンがありませんでした",
|
||||
"cloudBrowserUpdating": "クラウドブラウザーをお使いの Wayfern のバージョンに更新中でした",
|
||||
"managerError": "フリートがブラウザーを起動できませんでした",
|
||||
"budgetExceeded": "実行の持ち時間を使い切りました",
|
||||
"browsingIncomplete": "ブラウザーは起動しましたが、巡回が完了しませんでした",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "이 기능을 사용하려면 Donut Browser 계정에 로그인하세요.",
|
||||
"cloudUnreachable": "Donut Browser 서버에 연결하지 못했습니다. 연결 상태를 확인하고 다시 시도하세요.",
|
||||
"cloudRequestFailed": "요청이 실패했습니다. 잠시 후 다시 시도하세요.",
|
||||
"cloudBrowserUpdating": "클라우드 브라우저를 사용 중인 Wayfern 버전으로 업데이트하고 있습니다. 몇 분 후에 다시 시도하세요.",
|
||||
"remoteRateLimited": "요청이 너무 많습니다. 잠시 기다렸다가 다시 시도하세요.",
|
||||
"remoteNoCapacity": "지금은 사용 가능한 원격 머신이 없습니다. 몇 분 후에 다시 시도하세요.",
|
||||
"remoteNotEntitled": "현재 요금제에는 원격 실행이 포함되어 있지 않습니다.",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "원격 시간을 모두 사용했습니다",
|
||||
"profileLocked": "프로필이 다른 곳에서 열려 있었습니다",
|
||||
"noCapacity": "여유 머신이 없었습니다",
|
||||
"cloudBrowserUpdating": "클라우드 브라우저를 사용 중인 Wayfern 버전으로 업데이트하는 중이었습니다",
|
||||
"managerError": "플릿이 브라우저를 시작하지 못했습니다",
|
||||
"budgetExceeded": "실행 시간 예산이 소진되었습니다",
|
||||
"browsingIncomplete": "브라우저는 열렸지만 탐색이 끝나지 않았습니다",
|
||||
|
||||
@@ -2708,6 +2708,7 @@
|
||||
"cloudNotSignedIn": "Entre na sua conta do Donut Browser para usar este recurso.",
|
||||
"cloudUnreachable": "Não foi possível contatar os servidores do Donut Browser. Verifique sua conexão e tente novamente.",
|
||||
"cloudRequestFailed": "A solicitação falhou. Tente novamente em instantes.",
|
||||
"cloudBrowserUpdating": "O navegador na nuvem está sendo atualizado para a sua versão do Wayfern. Tente novamente em alguns minutos.",
|
||||
"remoteRateLimited": "Solicitações demais. Aguarde um momento e tente novamente.",
|
||||
"remoteNoCapacity": "Nenhuma máquina remota está livre agora. Tente novamente em alguns minutos.",
|
||||
"remoteNotEntitled": "Seu plano não inclui execução remota.",
|
||||
@@ -3759,6 +3760,7 @@
|
||||
"quotaExhausted": "Horas remotas esgotadas",
|
||||
"profileLocked": "O perfil estava aberto noutro lugar",
|
||||
"noCapacity": "Nenhuma máquina estava livre",
|
||||
"cloudBrowserUpdating": "O navegador na nuvem estava sendo atualizado para a sua versão do Wayfern",
|
||||
"managerError": "A frota não conseguiu iniciar o navegador",
|
||||
"budgetExceeded": "O tempo previsto para a execução acabou",
|
||||
"browsingIncomplete": "O navegador abriu, mas a navegação não terminou",
|
||||
|
||||
@@ -2727,6 +2727,7 @@
|
||||
"cloudNotSignedIn": "Войдите в аккаунт Donut Browser, чтобы использовать эту функцию.",
|
||||
"cloudUnreachable": "Не удалось связаться с серверами Donut Browser. Проверьте подключение и попробуйте снова.",
|
||||
"cloudRequestFailed": "Запрос не удался. Попробуйте ещё раз через минуту.",
|
||||
"cloudBrowserUpdating": "Облачный браузер обновляется до вашей версии Wayfern. Повторите попытку через несколько минут.",
|
||||
"remoteRateLimited": "Слишком много запросов. Подождите немного и попробуйте снова.",
|
||||
"remoteNoCapacity": "Сейчас нет свободных удалённых машин. Попробуйте через несколько минут.",
|
||||
"remoteNotEntitled": "Ваш тариф не включает удалённый запуск.",
|
||||
@@ -3799,6 +3800,7 @@
|
||||
"quotaExhausted": "Удалённые часы израсходованы",
|
||||
"profileLocked": "Профиль был открыт в другом месте",
|
||||
"noCapacity": "Свободных машин не было",
|
||||
"cloudBrowserUpdating": "Облачный браузер обновлялся до вашей версии Wayfern",
|
||||
"managerError": "Флот не смог запустить браузер",
|
||||
"budgetExceeded": "Отведённое на запуск время закончилось",
|
||||
"browsingIncomplete": "Браузер открылся, но обход сайтов не завершился",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "Bunu kullanmak için Donut Browser hesabınıza giriş yapın.",
|
||||
"cloudUnreachable": "Donut Browser sunucularına ulaşılamadı. Bağlantınızı kontrol edip tekrar deneyin.",
|
||||
"cloudRequestFailed": "İstek başarısız oldu. Birazdan tekrar deneyin.",
|
||||
"cloudBrowserUpdating": "Bulut tarayıcısı Wayfern sürümünüze güncelleniyor. Birkaç dakika sonra tekrar deneyin.",
|
||||
"remoteRateLimited": "Çok fazla istek. Biraz bekleyip tekrar deneyin.",
|
||||
"remoteNoCapacity": "Şu anda boş uzak makine yok. Birkaç dakika sonra tekrar deneyin.",
|
||||
"remoteNotEntitled": "Planınız uzaktan çalıştırmayı içermiyor.",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "Uzak saatler tükendi",
|
||||
"profileLocked": "Profil başka bir yerde açıktı",
|
||||
"noCapacity": "Boş makine yoktu",
|
||||
"cloudBrowserUpdating": "Bulut tarayıcısı Wayfern sürümünüze güncelleniyordu",
|
||||
"managerError": "Filo tarayıcıyı başlatamadı",
|
||||
"budgetExceeded": "Çalışmanın süresi doldu",
|
||||
"browsingIncomplete": "Tarayıcı açıldı ama gezinme tamamlanmadı",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "Hãy đăng nhập tài khoản Donut Browser để dùng tính năng này.",
|
||||
"cloudUnreachable": "Không kết nối được tới máy chủ Donut Browser. Hãy kiểm tra kết nối và thử lại.",
|
||||
"cloudRequestFailed": "Yêu cầu thất bại. Hãy thử lại sau giây lát.",
|
||||
"cloudBrowserUpdating": "Trình duyệt đám mây đang cập nhật lên phiên bản Wayfern của bạn. Hãy thử lại sau vài phút.",
|
||||
"remoteRateLimited": "Quá nhiều yêu cầu. Hãy đợi một lát rồi thử lại.",
|
||||
"remoteNoCapacity": "Hiện không có máy từ xa nào rảnh. Hãy thử lại sau vài phút.",
|
||||
"remoteNotEntitled": "Gói của bạn không bao gồm chạy từ xa.",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "Đã dùng hết số giờ từ xa",
|
||||
"profileLocked": "Hồ sơ đang mở ở nơi khác",
|
||||
"noCapacity": "Không có máy nào rảnh",
|
||||
"cloudBrowserUpdating": "Trình duyệt đám mây đang cập nhật lên phiên bản Wayfern của bạn",
|
||||
"managerError": "Cụm máy không khởi động được trình duyệt",
|
||||
"budgetExceeded": "Đã hết thời lượng dành cho lần chạy",
|
||||
"browsingIncomplete": "Trình duyệt đã mở nhưng phiên duyệt chưa hoàn tất",
|
||||
|
||||
@@ -2689,6 +2689,7 @@
|
||||
"cloudNotSignedIn": "请先登录你的 Donut Browser 账号再使用此功能。",
|
||||
"cloudUnreachable": "无法连接 Donut Browser 服务器。请检查网络后重试。",
|
||||
"cloudRequestFailed": "请求失败。请稍后再试。",
|
||||
"cloudBrowserUpdating": "云端浏览器正在更新到你的 Wayfern 版本。请几分钟后再试。",
|
||||
"remoteRateLimited": "请求过于频繁。请稍候再试。",
|
||||
"remoteNoCapacity": "当前没有空闲的远程机器。请几分钟后再试。",
|
||||
"remoteNotEntitled": "你的套餐不包含远程运行。",
|
||||
@@ -3721,6 +3722,7 @@
|
||||
"quotaExhausted": "远程时长已用完",
|
||||
"profileLocked": "该配置文件已在别处打开",
|
||||
"noCapacity": "没有空闲机器",
|
||||
"cloudBrowserUpdating": "云端浏览器当时正在更新到你的 Wayfern 版本",
|
||||
"managerError": "机器集群未能启动浏览器",
|
||||
"budgetExceeded": "本次运行的时长预算已用完",
|
||||
"browsingIncomplete": "浏览器已启动,但浏览未完成",
|
||||
|
||||
@@ -190,6 +190,7 @@ export type BackendErrorCode =
|
||||
| "CLOUD_NOT_SIGNED_IN"
|
||||
| "CLOUD_UNREACHABLE"
|
||||
| "CLOUD_REQUEST_FAILED"
|
||||
| "CLOUD_BROWSER_UPDATING"
|
||||
| "REMOTE_RATE_LIMITED"
|
||||
| "REMOTE_NO_CAPACITY"
|
||||
// The deployment has no fleet credential at all, which is not the same as a
|
||||
@@ -949,6 +950,8 @@ export function translateBackendError(t: TFunction, err: unknown): string {
|
||||
return t("backendErrors.cloudUnreachable");
|
||||
case "CLOUD_REQUEST_FAILED":
|
||||
return t("backendErrors.cloudRequestFailed");
|
||||
case "CLOUD_BROWSER_UPDATING":
|
||||
return t("backendErrors.cloudBrowserUpdating");
|
||||
case "REMOTE_RATE_LIMITED":
|
||||
return t("backendErrors.remoteRateLimited");
|
||||
case "REMOTE_NO_CAPACITY": {
|
||||
|
||||
@@ -42,6 +42,7 @@ const SERVER_OUTCOME_CODES = [
|
||||
"browsing_incomplete",
|
||||
"budget_exceeded",
|
||||
"cancelled_by_user",
|
||||
"cloud_browser_updating",
|
||||
"encrypted_sync",
|
||||
"manager_error",
|
||||
"no_capacity",
|
||||
|
||||
Reference in new issue
Block a user