feat: 絵文字の申請機能の追加
What
Tanukeyの物をベースにカスタム絵文字の申請機能を追加しました。
- apiに emoji/add-request emoji/update-request emoji-requests admin/emoji/list-request が追加されました
- ロールに カスタム絵文字のリクエスト が追加されました 画面上での変更箇所です
絵文字リクエストの権限がある人が見たとき
申請中の絵文字(ドラフトされている絵文字)
コントロールパネルのカスタム絵文字のところ(ドラフトされている絵文字 のタブが追加されてる)
カスタム絵文字の編集のところに ドラフト が追加されました
Why
絵文字申請などもMisskey内で完結したほうがユーザビリティが良いと考えるため。
Additional info (optional)
Checklist
- [x] Read the contribution guide
- [x] Test working in a local environment
- [ ] (If needed) Add story of storybook
- [x] (If needed) Update CHANGELOG.md
- [ ] (If possible) Add tests
Codecov Report
Attention: 255 lines in your changes are missing coverage. Please review.
Comparison is base (
44c0b5f) 78.96% compared to head (d0db959) 78.78%.
Additional details and impacted files
@@ Coverage Diff @@
## develop #12085 +/- ##
===========================================
- Coverage 78.96% 78.78% -0.19%
===========================================
Files 947 952 +5
Lines 102207 102908 +701
Branches 8269 8291 +22
===========================================
+ Hits 80712 81073 +361
- Misses 21495 21835 +340
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
サーバーサイド詳しくないので一般的な懸念事項をば(すでに対応済んでいたらすみません)
- 絵文字IDがダブっていた際にサーバーサイドでエラーを返すようになっていますか?
- API等を使ってリアクションした際に、絵文字がドラフトだった場合、エラーを返す(リアクションできない)ようになっていますか?
- ドラフトに入っている絵文字をMFMでのパースで利用できないようになっていますか?
リクエスト中絵文字と実際の絵文字はテーブル分けた方が良いかもしれないわね
そうしないとあらゆる個所にチェック & 分岐しこまないといけなくなって大変そう
テーブル分けるようにします
テーブル分けるようにします
ややこしいので一旦Draftにしておきました🙏
一応テーブル分けて動くようになりました 👀
修正しました 🙏
draft というよりも request だから一括置換でリネームしたい
このPRによるapi.jsonの差分
差分はこちら
--- base
+++ head
@@ -4900,6 +4900,200 @@
}
}
},
+ "/admin/emoji/add-request": {
+ "post": {
+ "operationId": "admin/emoji/add-request",
+ "summary": "admin/emoji/add-request",
+ "description": "No description provided.\n\n**Credential required**: *Yes*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/admin/emoji/add-request.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9_]+$"
+ },
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "description": "Use `null` to reset the category."
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "license": {
+ "type": "string",
+ "nullable": true
+ },
+ "isSensitive": {
+ "type": "boolean",
+ "nullable": true
+ },
+ "localOnly": {
+ "type": "boolean",
+ "nullable": true
+ },
+ "fileId": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "required": [
+ "name",
+ "fileId"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "OK (without any results)"
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_FILE": {
+ "value": {
+ "error": {
+ "message": "No such file.",
+ "code": "NO_SUCH_FILE",
+ "id": "fc46b5a4-6b92-4c33-ac66-b806659bb5cf"
+ }
+ }
+ },
+ "DUPLICATE_NAME": {
+ "value": {
+ "error": {
+ "message": "Duplicate name.",
+ "code": "DUPLICATE_NAME",
+ "id": "f7a3462c-4e6e-4069-8421-b9bd4f4c3975"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/admin/emoji/copy": {
"post": {
"operationId": "admin/emoji/copy",
@@ -5835,6 +6029,221 @@
}
}
},
+ "/admin/emoji/list-request": {
+ "post": {
+ "operationId": "admin/emoji/list-request",
+ "summary": "admin/emoji/list-request",
+ "description": "No description provided.\n\n**Credential required**: *Yes*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/admin/emoji/list-request.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "query": {
+ "type": "string",
+ "nullable": true,
+ "default": null
+ },
+ "limit": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 100,
+ "default": 10
+ },
+ "sinceId": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "untilId": {
+ "type": "string",
+ "format": "misskey:id"
+ }
+ },
+ "required": []
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "array",
+ "optional": false,
+ "nullable": false,
+ "items": {
+ "type": "object",
+ "optional": false,
+ "nullable": false,
+ "properties": {
+ "id": {
+ "type": "string",
+ "optional": false,
+ "nullable": false,
+ "format": "id"
+ },
+ "aliases": {
+ "type": "array",
+ "optional": false,
+ "nullable": false,
+ "items": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ }
+ },
+ "name": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ },
+ "category": {
+ "type": "string",
+ "optional": false,
+ "nullable": true
+ },
+ "url": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ }
+ },
+ "required": [
+ "id",
+ "aliases",
+ "name",
+ "category",
+ "url"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/admin/emoji/remove-aliases-bulk": {
"post": {
"operationId": "admin/emoji/remove-aliases-bulk",
@@ -6525,6 +6934,224 @@
"items": {
"type": "string"
}
+ },
+ "Request": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "id",
+ "name",
+ "aliases"
+ ]
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "OK (without any results)"
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "NO_SUCH_EMOJI": {
+ "value": {
+ "error": {
+ "message": "No such emoji.",
+ "code": "NO_SUCH_EMOJI",
+ "id": "684dec9d-a8c2-4364-9aa8-456c49cb1dc8"
+ }
+ }
+ },
+ "NO_SUCH_FILE": {
+ "value": {
+ "error": {
+ "message": "No such file.",
+ "code": "NO_SUCH_FILE",
+ "id": "14fb9fd9-0731-4e2f-aeb9-f09e4740333d"
+ }
+ }
+ },
+ "SAME_NAME_EMOJI_EXISTS": {
+ "value": {
+ "error": {
+ "message": "Emoji that have same name already exists.",
+ "code": "SAME_NAME_EMOJI_EXISTS",
+ "id": "7180fe9d-1ee3-bff9-647d-fe9896d2ffb8"
+ }
+ }
+ },
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/admin/emoji/update-request": {
+ "post": {
+ "operationId": "admin/emoji/update-request",
+ "summary": "admin/emoji/update-request",
+ "description": "No description provided.\n\n**Credential required**: *Yes*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/admin/emoji/update-request.ts"
+ },
+ "tags": [
+ "admin"
+ ],
+ "security": [
+ {
+ "ApiKeyAuth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "name": {
+ "type": "string",
+ "pattern": "^[a-zA-Z0-9_]+$"
+ },
+ "fileId": {
+ "type": "string",
+ "format": "misskey:id"
+ },
+ "category": {
+ "type": "string",
+ "nullable": true,
+ "description": "Use `null` to reset the category."
+ },
+ "aliases": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "license": {
+ "type": "string",
+ "nullable": true
+ },
+ "isSensitive": {
+ "type": "boolean"
+ },
+ "localOnly": {
+ "type": "boolean"
+ },
+ "roleIdsThatCanBeUsedThisEmojiAsReaction": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "Request": {
+ "type": "boolean"
}
},
"required": [
@@ -39450,6 +40077,168 @@
}
}
},
+ "/emoji-requests": {
+ "post": {
+ "operationId": "emoji-requests",
+ "summary": "emoji-requests",
+ "description": "No description provided.\n\n**Credential required**: *No*",
+ "externalDocs": {
+ "description": "Source code",
+ "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/emoji-requests.ts"
+ },
+ "tags": [
+ "meta"
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "properties": {},
+ "required": []
+ }
+ }
+ }
+ },
+ "responses": {
+ "200": {
+ "description": "OK (with results)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "optional": false,
+ "nullable": false,
+ "properties": {
+ "emojis": {
+ "type": "array",
+ "optional": false,
+ "nullable": false,
+ "items": {
+ "type": "object",
+ "optional": false,
+ "nullable": false,
+ "ref": "EmojiRequestSimple",
+ "$ref": "#/components/schemas/EmojiRequestSimple"
+ }
+ }
+ },
+ "required": [
+ "emojis"
+ ]
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Client error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INVALID_PARAM": {
+ "value": {
+ "error": {
+ "message": "Invalid param.",
+ "code": "INVALID_PARAM",
+ "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Authentication error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "CREDENTIAL_REQUIRED": {
+ "value": {
+ "error": {
+ "message": "Credential required.",
+ "code": "CREDENTIAL_REQUIRED",
+ "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "Forbidden error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "AUTHENTICATION_FAILED": {
+ "value": {
+ "error": {
+ "message": "Authentication failed. Please ensure your token is correct.",
+ "code": "AUTHENTICATION_FAILED",
+ "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "418": {
+ "description": "I'm Ai",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "I_AM_AI": {
+ "value": {
+ "error": {
+ "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+ "code": "I_AM_AI",
+ "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "500": {
+ "description": "Internal server error",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/Error"
+ },
+ "examples": {
+ "INTERNAL_ERROR": {
+ "value": {
+ "error": {
+ "message": "Internal error occurred. Please contact us if the error persists.",
+ "code": "INTERNAL_ERROR",
+ "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/emoji": {
"post": {
"operationId": "emoji",
@@ -61734,6 +62523,48 @@
"url"
]
},
+ "EmojiRequestSimple": {
+ "type": "object",
+ "properties": {
+ "aliases": {
+ "type": "array",
+ "optional": false,
+ "nullable": false,
+ "items": {
+ "type": "string",
+ "optional": false,
+ "nullable": false,
+ "format": "id"
+ }
+ },
+ "name": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ },
+ "category": {
+ "type": "string",
+ "optional": false,
+ "nullable": true
+ },
+ "url": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ },
+ "isSensitive": {
+ "type": "boolean",
+ "optional": true,
+ "nullable": false
+ }
+ },
+ "required": [
+ "aliases",
+ "name",
+ "category",
+ "url"
+ ]
+ },
"EmojiDetailed": {
"type": "object",
"properties": {
@@ -61815,6 +62646,74 @@
"roleIdsThatCanBeUsedThisEmojiAsReaction"
]
},
+ "EmojiRequestDetailed": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "optional": false,
+ "nullable": false,
+ "format": "id"
+ },
+ "aliases": {
+ "type": "array",
+ "optional": false,
+ "nullable": false,
+ "items": {
+ "type": "string",
+ "optional": false,
+ "nullable": false,
+ "format": "id"
+ }
+ },
+ "name": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ },
+ "category": {
+ "type": "string",
+ "optional": false,
+ "nullable": true
+ },
+ "url": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ },
+ "license": {
+ "type": "string",
+ "optional": false,
+ "nullable": true
+ },
+ "isSensitive": {
+ "type": "boolean",
+ "optional": false,
+ "nullable": false
+ },
+ "localOnly": {
+ "type": "boolean",
+ "optional": false,
+ "nullable": false
+ },
+ "fileId": {
+ "type": "string",
+ "optional": false,
+ "nullable": false
+ }
+ },
+ "required": [
+ "id",
+ "aliases",
+ "name",
+ "category",
+ "url",
+ "license",
+ "isSensitive",
+ "localOnly",
+ "fileId"
+ ]
+ },
"Flash": {
"type": "object",
"properties": {
多分draftの全部をrequestにできました👀
修正しました 🙏
ldhfsamyというクラス名が複数個所で使われてる
新しく作られるコンポーネントに関してはCSS Modulesで統一してる
修正しました
お忙しいところ恐れ入ります。 こちらのPRですが、いつ頃マージされるか、どのバージョンに同梱されるか予定はありますでしょうか? 機能追加を楽しみにしているのですが、しばらく動きがなく気になりまして。
v2024.1.0あたりに入れられたらと考えてます
このPRがmergeされたら
もしくはユーザーがadminにカスタム絵文字を提案できる機能を
を満たすため、#3082 がclose出来そうです。 ("ユーザーが定義"については、現状ロールで制御可能なはずです)
#10221 にある各種機能要望ってこの中にも含まれていますかね…?