Skip to content

Commit efc5d97

Browse files
authored
spec(frontend): Skeb Buttonの実装 (#573)
1 parent 197e397 commit efc5d97

File tree

16 files changed

+483
-4
lines changed

16 files changed

+483
-4
lines changed

locales/en-US.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2575,4 +2575,18 @@ _reversi:
25752575
_offlineScreen:
25762576
title: "Offline - cannot connect to the server"
25772577
header: "Unable to connect to the server"
2578-
2578+
_skebStatus:
2579+
_genres:
2580+
art: "Artwork"
2581+
comic: "Comic"
2582+
voice: "Voice"
2583+
novel: "Text"
2584+
video: "Movie"
2585+
music: "Music"
2586+
correction: "Advice"
2587+
seeking: "Seeking"
2588+
stopped: "Stopped"
2589+
client: "Client"
2590+
yenX: "JPY {x}"
2591+
nWorks: "Delivered {n} works"
2592+
nRequests: "Requested {n} times"

locales/index.d.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10113,6 +10113,62 @@ export interface Locale extends ILocale {
1011310113
*/
1011410114
"summaryProxyDescription2": string;
1011510115
};
10116+
"_skebStatus": {
10117+
"_genres": {
10118+
/**
10119+
* イラスト
10120+
*/
10121+
"art": string;
10122+
/**
10123+
* コミック
10124+
*/
10125+
"comic": string;
10126+
/**
10127+
* ボイス
10128+
*/
10129+
"voice": string;
10130+
/**
10131+
* テキスト
10132+
*/
10133+
"novel": string;
10134+
/**
10135+
* ムービー
10136+
*/
10137+
"video": string;
10138+
/**
10139+
* ミュージック
10140+
*/
10141+
"music": string;
10142+
/**
10143+
* アドバイス
10144+
*/
10145+
"correction": string;
10146+
};
10147+
/**
10148+
* 募集中
10149+
*/
10150+
"seeking": string;
10151+
/**
10152+
* 停止中
10153+
*/
10154+
"stopped": string;
10155+
/**
10156+
* クライアント
10157+
*/
10158+
"client": string;
10159+
/**
10160+
* {x}円
10161+
*/
10162+
"yenX": ParameterizedString<"x">;
10163+
/**
10164+
* 納品実績 {n}件
10165+
*/
10166+
"nWorks": ParameterizedString<"n">;
10167+
/**
10168+
* 取引実績 {n}件
10169+
*/
10170+
"nRequests": ParameterizedString<"n">;
10171+
};
1011610172
}
1011710173
declare const locales: {
1011810174
[lang: string]: Locale;

locales/ja-JP.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2693,3 +2693,19 @@ _urlPreviewSetting:
26932693
summaryProxy: "プレビューを生成するプロキシのエンドポイント"
26942694
summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
26952695
summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
2696+
2697+
_skebStatus:
2698+
_genres:
2699+
art: "イラスト"
2700+
comic: "コミック"
2701+
voice: "ボイス"
2702+
novel: "テキスト"
2703+
video: "ムービー"
2704+
music: "ミュージック"
2705+
correction: "アドバイス"
2706+
seeking: "募集中"
2707+
stopped: "停止中"
2708+
client: "クライアント"
2709+
yenX: "{x}円"
2710+
nWorks: "納品実績 {n}件"
2711+
nRequests: "取引実績 {n}件"

locales/ko-KR.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2549,4 +2549,18 @@ _reversi:
25492549
_offlineScreen:
25502550
title: "오프라인 - 서버에 접속할 수 없습니다"
25512551
header: "서버에 접속할 수 없습니다"
2552-
2552+
_skebStatus:
2553+
_genres:
2554+
art: "작품"
2555+
comic: "만화"
2556+
voice: "음성"
2557+
novel: "텍스트"
2558+
video: "동영상"
2559+
music: "음악"
2560+
correction: "조언"
2561+
seeking: "모집 중"
2562+
stopped: "정지 중"
2563+
client: "클라이언트"
2564+
yenX: "JPY {x}"
2565+
nWorks: "납품 실적 {n}건"
2566+
nRequests: "거래 실적 {n}건"

packages/backend/src/config.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ type Source = {
6666
scope?: 'local' | 'global' | string[];
6767
};
6868

69+
skebStatus?: {
70+
method: string;
71+
endpoint: string;
72+
headers: { [x: string]: string };
73+
parameters: { [x: string]: string };
74+
userIdParameterName: string;
75+
}
76+
6977
proxy?: string;
7078
proxySmtp?: string;
7179
proxyBypassHosts?: string[];
@@ -140,6 +148,13 @@ export type Config = {
140148
index: string;
141149
scope?: 'local' | 'global' | string[];
142150
} | undefined;
151+
skebStatus: {
152+
method: string;
153+
endpoint: string;
154+
headers: { [x: string]: string };
155+
parameters: { [x: string]: string };
156+
userIdParameterName: string;
157+
} | undefined;
143158
proxy: string | undefined;
144159
proxySmtp: string | undefined;
145160
proxyBypassHosts: string[] | undefined;
@@ -266,6 +281,7 @@ export function loadConfig(): Config {
266281
redisForObjectStorageQueue: config.redisForObjectStorageQueue ? convertRedisOptions(config.redisForObjectStorageQueue, host) : redisForJobQueue,
267282
redisForWebhookDeliverQueue: config.redisForWebhookDeliverQueue ? convertRedisOptions(config.redisForWebhookDeliverQueue, host) : redisForJobQueue,
268283
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
284+
skebStatus: config.skebStatus,
269285
id: config.id,
270286
proxy: config.proxy,
271287
proxySmtp: config.proxySmtp,

packages/backend/src/core/entities/MetaEntityService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export class MetaEntityService {
112112

113113
mediaProxy: this.config.mediaProxy,
114114
enableUrlPreview: instance.urlPreviewEnabled,
115+
enableSkebStatus: !!this.config.skebStatus,
115116
};
116117
}
117118

packages/backend/src/models/json-schema/meta.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ export const packedMetaLiteSchema = {
216216
type: 'boolean',
217217
optional: false, nullable: false,
218218
},
219+
enableSkebStatus: {
220+
type: 'boolean',
221+
optional: false, nullable: false,
222+
},
219223
backgroundImageUrl: {
220224
type: 'string',
221225
optional: false, nullable: true,

packages/backend/src/server/api/EndpointsModule.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ import * as ep___users_following from './endpoints/users/following.js';
350350
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
351351
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
352352
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
353+
import * as ep___users_getSkebStatus from './endpoints/users/get-skeb-status.js';
353354
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
354355
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
355356
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
@@ -736,6 +737,7 @@ const $users_following: Provider = { provide: 'ep:users/following', useClass: ep
736737
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
737738
const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default };
738739
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
740+
const $users_getSkebStatus: Provider = { provide: 'ep:users/get-skeb-status', useClass: ep___users_getSkebStatus.default };
739741
const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
740742
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
741743
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
@@ -1126,6 +1128,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
11261128
$users_gallery_posts,
11271129
$users_getFollowingBirthdayUsers,
11281130
$users_getFrequentlyRepliedUsers,
1131+
$users_getSkebStatus,
11291132
$users_featuredNotes,
11301133
$users_lists_create,
11311134
$users_lists_delete,
@@ -1508,6 +1511,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
15081511
$users_gallery_posts,
15091512
$users_getFollowingBirthdayUsers,
15101513
$users_getFrequentlyRepliedUsers,
1514+
$users_getSkebStatus,
15111515
$users_featuredNotes,
15121516
$users_lists_create,
15131517
$users_lists_delete,

packages/backend/src/server/api/endpoints.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ import * as ep___users_following from './endpoints/users/following.js';
350350
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
351351
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
352352
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
353+
import * as ep___users_getSkebStatus from './endpoints/users/get-skeb-status.js';
353354
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
354355
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
355356
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
@@ -734,6 +735,7 @@ const eps = [
734735
['users/gallery/posts', ep___users_gallery_posts],
735736
['users/get-following-birthday-users', ep___users_getFollowingBirthdayUsers],
736737
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
738+
['users/get-skeb-status', ep___users_getSkebStatus],
737739
['users/featured-notes', ep___users_featuredNotes],
738740
['users/lists/create', ep___users_lists_create],
739741
['users/lists/delete', ep___users_lists_delete],
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { Inject, Injectable } from '@nestjs/common';
2+
import { DI } from '@/di-symbols.js';
3+
import { Endpoint } from '@/server/api/endpoint-base.js';
4+
import { LoggerService } from '@/core/LoggerService.js';
5+
import { HttpRequestService } from '@/core/HttpRequestService.js';
6+
import type { Config } from '@/config.js';
7+
import { ApiError } from '../../error.js';
8+
9+
export const meta = {
10+
tags: ['users'],
11+
12+
requireCredential: false,
13+
allowGet: true,
14+
cacheSec: 60 * 5,
15+
16+
res: {
17+
type: 'object',
18+
properties: {
19+
screenName: {
20+
type: 'string',
21+
optional: false, nullable: false,
22+
},
23+
isCreator: {
24+
type: 'boolean',
25+
optional: false, nullable: false,
26+
},
27+
isAcceptable: {
28+
type: 'boolean',
29+
optional: false, nullable: false,
30+
},
31+
creatorRequestCount: {
32+
type: 'integer',
33+
optional: false, nullable: false,
34+
},
35+
clientRequestCount: {
36+
type: 'integer',
37+
optional: false, nullable: false,
38+
},
39+
skills: {
40+
type: 'array',
41+
optional: false, nullable: false,
42+
items: {
43+
type: 'object',
44+
properties: {
45+
amount: {
46+
type: 'integer',
47+
optional: false, nullable: false,
48+
},
49+
genre: {
50+
type: 'string',
51+
optional: false, nullable: false,
52+
enum: ['art', 'comic', 'voice', 'novel', 'video', 'music', 'correction'],
53+
},
54+
},
55+
},
56+
},
57+
},
58+
},
59+
60+
errors: {
61+
skebStatusNotAvailable: {
62+
message: 'Skeb status is not available.',
63+
code: 'SKEB_STATUS_NOT_AVAILABLE',
64+
id: '1dd37c9c-7e97-4c24-be98-227a78b21d80',
65+
httpStatusCode: 403,
66+
},
67+
68+
noSuchUser: {
69+
message: 'No such user.',
70+
code: 'NO_SUCH_USER',
71+
id: '88d582ae-69d9-45e0-a8b3-13f9945e48bf',
72+
httpStatusCode: 404,
73+
},
74+
},
75+
} as const;
76+
77+
export const paramDef = {
78+
type: 'object',
79+
properties: {
80+
userId: { type: 'string', format: 'misskey:id' },
81+
},
82+
required: ['userId'],
83+
} as const;
84+
85+
@Injectable()
86+
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
87+
constructor(
88+
@Inject(DI.config)
89+
private config: Config,
90+
91+
private loggerService: LoggerService,
92+
private httpRequestService: HttpRequestService,
93+
) {
94+
super(meta, paramDef, async (ps) => {
95+
if (!this.config.skebStatus) throw new ApiError(meta.errors.skebStatusNotAvailable);
96+
const logger = this.loggerService.getLogger('api:users:get-skeb-status');
97+
98+
const url = new URL(this.config.skebStatus.endpoint);
99+
for (const [key, value] of Object.entries(this.config.skebStatus.parameters)) {
100+
url.searchParams.set(key, value);
101+
}
102+
url.searchParams.set(this.config.skebStatus.userIdParameterName, ps.userId);
103+
104+
logger.info('Requesting Skeb status', { url: url.href, userId: ps.userId });
105+
const res = await this.httpRequestService.send(
106+
url.href,
107+
{
108+
method: this.config.skebStatus.method,
109+
headers: {
110+
...this.config.skebStatus.headers,
111+
Accept: 'application/json',
112+
},
113+
timeout: 5000,
114+
},
115+
{
116+
throwErrorWhenResponseNotOk: false,
117+
},
118+
);
119+
120+
const json = (await res.json()) as {
121+
screen_name: string,
122+
is_creator: boolean,
123+
is_acceptable: boolean,
124+
creator_request_count: number,
125+
client_request_count: number,
126+
skills: { amount: number, genre: 'art' | 'comic' | 'voice' | 'novel' | 'video' | 'music' | 'correction' }[],
127+
ban_reason?: string | null
128+
error?: unknown,
129+
};
130+
131+
if (res.status > 399 || (json.error ?? json.ban_reason)) {
132+
logger.error('Skeb status response error', { url: url.href, userId: ps.userId, status: res.status, statusText: res.statusText, error: json.error ?? json.ban_reason });
133+
throw new ApiError(meta.errors.noSuchUser);
134+
}
135+
136+
logger.info('Skeb status response', { url: url.href, userId: ps.userId, status: res.status, statusText: res.statusText, skebStatus: json });
137+
138+
return {
139+
screenName: json.screen_name,
140+
isCreator: json.is_creator,
141+
isAcceptable: json.is_acceptable,
142+
creatorRequestCount: json.creator_request_count,
143+
clientRequestCount: json.client_request_count,
144+
skills: json.skills,
145+
};
146+
});
147+
}
148+
}

0 commit comments

Comments
 (0)