From 9c9a9ee261fd933f24ea572d197c536e6b619d88 Mon Sep 17 00:00:00 2001
From: ThatOneCalculator <kainoa@t1c.dev>
Date: Tue, 6 Jun 2023 18:43:05 -0700
Subject: [PATCH] =?UTF-8?q?feat:=20=E2=9C=A8=20patron=20labels?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/en-US.yml                             |  5 ++
 .../src/server/api/endpoints/patrons.ts       |  4 ++
 packages/client/src/instance.ts               |  8 ++++
 packages/client/src/pages/about-calckey.vue   |  5 +-
 packages/client/src/pages/user/home.vue       | 48 +++++++++++++++----
 5 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/locales/en-US.yml b/locales/en-US.yml
index 2cfb9c47b..e65ed8bee 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1099,6 +1099,11 @@ noGraze: "Please disable the \"Graze for Mastodon\" browser extension, as it int
   with Calckey."
 silencedWarning: "This page is showing because these users are from servers your admin
   silenced, so they may potentially be spam."
+isBot: "This account is a bot"
+isLocked: "This account has follow approvals"
+isModerator: "Moderator"
+isAdmin: "Administrator"
+isPatron: "Calckey Patron"
 
 _sensitiveMediaDetection:
   description: "Reduces the effort of server moderation through automatically recognizing
diff --git a/packages/backend/src/server/api/endpoints/patrons.ts b/packages/backend/src/server/api/endpoints/patrons.ts
index d6ac6c397..aa9d25cf0 100644
--- a/packages/backend/src/server/api/endpoints/patrons.ts
+++ b/packages/backend/src/server/api/endpoints/patrons.ts
@@ -1,4 +1,5 @@
 import define from "../define.js";
+import Logger from "@/services/logger.js";
 
 export const meta = {
 	tags: ["meta"],
@@ -22,6 +23,9 @@ export default define(meta, paramDef, async () => {
 		.then((response) => response.json())
 		.then((data) => {
 			patrons = data["patrons"];
+		})
+		.catch((error) => {
+			console.error("Error fetching patrons:", error);
 		});
 
 	return patrons;
diff --git a/packages/client/src/instance.ts b/packages/client/src/instance.ts
index 3381684a0..4b2c6a953 100644
--- a/packages/client/src/instance.ts
+++ b/packages/client/src/instance.ts
@@ -5,6 +5,7 @@ import type * as Misskey from "calckey-js";
 // TODO: 他のタブと永続化されたstateを同期
 
 const instanceData = localStorage.getItem("instance");
+const patronData = localStorage.getItem("patrons");
 
 // TODO: instanceをリアクティブにするかは再考の余地あり
 
@@ -16,6 +17,8 @@ export const instance: Misskey.entities.DetailedInstanceMetadata = reactive(
 		  },
 );
 
+export const patrons = patronData || [];
+
 export async function fetchInstance() {
 	const meta = await api("meta", {
 		detail: true,
@@ -28,6 +31,11 @@ export async function fetchInstance() {
 	localStorage.setItem("instance", JSON.stringify(instance));
 }
 
+export async function fetchPatrons() {
+	const patrons = await api("patrons");
+	localStorage.setItem("patrons", JSON.stringify(patrons));
+}
+
 export const emojiCategories = computed(() => {
 	if (instance.emojis == null) return [];
 	const categories = new Set();
diff --git a/packages/client/src/pages/about-calckey.vue b/packages/client/src/pages/about-calckey.vue
index 1e16356a5..01350d840 100644
--- a/packages/client/src/pages/about-calckey.vue
+++ b/packages/client/src/pages/about-calckey.vue
@@ -163,14 +163,15 @@ import { i18n } from "@/i18n";
 import { defaultStore } from "@/store";
 import * as os from "@/os";
 import { definePageMetadata } from "@/scripts/page-metadata";
-
-const patrons = await os.api("patrons");
+import { patrons, fetchPatrons } from "@/instance";
 
 let easterEggReady = false;
 let easterEggEmojis = $ref([]);
 let easterEggEngine = $ref(null);
 const containerEl = $ref<HTMLElement>();
 
+await fetchPatrons()
+
 function iconLoaded() {
 	const emojis = defaultStore.state.reactions;
 	const containerWidth = containerEl?.offsetWidth;
diff --git a/packages/client/src/pages/user/home.vue b/packages/client/src/pages/user/home.vue
index 1926fee88..61f3d3168 100644
--- a/packages/client/src/pages/user/home.vue
+++ b/packages/client/src/pages/user/home.vue
@@ -54,7 +54,7 @@
 									/></span>
 									<span
 										v-if="user.isAdmin"
-										:title="i18n.ts.isAdmin"
+										v-tooltip.noDelay="i18n.ts.isAdmin"
 										style="color: var(--badge)"
 										><i
 											class="ph-bookmark-simple ph-fill ph-lg"
@@ -62,22 +62,36 @@
 									></span>
 									<span
 										v-if="!user.isAdmin && user.isModerator"
-										:title="i18n.ts.isModerator"
+										v-tooltip.noDelay="i18n.ts.isModerator"
 										style="color: var(--badge)"
 										><i
-											class="ph-bookmark-simple ph-bold"
+											class="ph-bookmark-simple ph-bold ph-lg"
 										></i
 									></span>
 									<span
 										v-if="user.isLocked"
-										:title="i18n.ts.isLocked"
+										v-tooltip.noDelay="i18n.ts.isLocked"
 										><i class="ph-lock ph-bold ph-lg"></i
 									></span>
 									<span
 										v-if="user.isBot"
-										:title="i18n.ts.isBot"
+										v-tooltip.noDelay="i18n.ts.isBot"
 										><i class="ph-robot ph-bold ph-lg"></i
 									></span>
+									<span
+										v-if="
+											patrons?.includes(
+												`@${user.username}@${
+													user.host || host
+												}`
+											)
+										"
+										v-tooltip.noDelay="i18n.ts.isPatron"
+										style="color: var(--badge)"
+										><i
+											class="ph-hand-coins ph-bold ph-lg"
+										></i
+									></span>
 								</div>
 							</div>
 						</div>
@@ -110,7 +124,7 @@
 								/></span>
 								<span
 									v-if="user.isAdmin"
-									:title="i18n.ts.isAdmin"
+									v-tooltip.noDelay="i18n.ts.isAdmin"
 									style="color: var(--badge)"
 									><i
 										class="ph-bookmark-simple ph-fill ph-lg"
@@ -118,18 +132,32 @@
 								></span>
 								<span
 									v-if="!user.isAdmin && user.isModerator"
-									:title="i18n.ts.isModerator"
+									v-tooltip.noDelay="i18n.ts.isModerator"
 									style="color: var(--badge)"
 									><i class="ph-bookmark-simple ph-bold"></i
 								></span>
 								<span
 									v-if="user.isLocked"
-									:title="i18n.ts.isLocked"
+									v-tooltip.noDelay="i18n.ts.isLocked"
 									><i class="ph-lock ph-bold ph-lg"></i
 								></span>
-								<span v-if="user.isBot" :title="i18n.ts.isBot"
+								<span
+									v-if="user.isBot"
+									v-tooltip.noDelay="i18n.ts.isBot"
 									><i class="ph-robot ph-bold ph-lg"></i
 								></span>
+								<span
+									v-if="
+										patrons?.includes(
+											`@${user.username}@${
+												user.host || host
+											}`
+										)
+									"
+									v-tooltip.noDelay="i18n.ts.isPatron"
+									style="color: var(--badge)"
+									><i class="ph-hand-coins ph-bold ph-lg"></i
+								></span>
 							</div>
 						</div>
 						<div class="follow-container">
@@ -324,6 +352,8 @@ import * as os from "@/os";
 import { useRouter } from "@/router";
 import { i18n } from "@/i18n";
 import { $i } from "@/account";
+import { host } from "@/config";
+import { patrons } from "@/instance";
 
 const XPhotos = defineAsyncComponent(() => import("./index.photos.vue"));
 const XActivity = defineAsyncComponent(() => import("./index.activity.vue"));