spec(frontend): Skeb Buttonの実装 (MisskeyIO#573)
This commit is contained in:
parent
197e39781d
commit
efc5d97eab
|
@ -2575,4 +2575,18 @@ _reversi:
|
||||||
_offlineScreen:
|
_offlineScreen:
|
||||||
title: "Offline - cannot connect to the server"
|
title: "Offline - cannot connect to the server"
|
||||||
header: "Unable to connect to the server"
|
header: "Unable to connect to the server"
|
||||||
|
_skebStatus:
|
||||||
|
_genres:
|
||||||
|
art: "Artwork"
|
||||||
|
comic: "Comic"
|
||||||
|
voice: "Voice"
|
||||||
|
novel: "Text"
|
||||||
|
video: "Movie"
|
||||||
|
music: "Music"
|
||||||
|
correction: "Advice"
|
||||||
|
seeking: "Seeking"
|
||||||
|
stopped: "Stopped"
|
||||||
|
client: "Client"
|
||||||
|
yenX: "JPY {x}"
|
||||||
|
nWorks: "Delivered {n} works"
|
||||||
|
nRequests: "Requested {n} times"
|
||||||
|
|
|
@ -10113,6 +10113,62 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"summaryProxyDescription2": string;
|
"summaryProxyDescription2": string;
|
||||||
};
|
};
|
||||||
|
"_skebStatus": {
|
||||||
|
"_genres": {
|
||||||
|
/**
|
||||||
|
* イラスト
|
||||||
|
*/
|
||||||
|
"art": string;
|
||||||
|
/**
|
||||||
|
* コミック
|
||||||
|
*/
|
||||||
|
"comic": string;
|
||||||
|
/**
|
||||||
|
* ボイス
|
||||||
|
*/
|
||||||
|
"voice": string;
|
||||||
|
/**
|
||||||
|
* テキスト
|
||||||
|
*/
|
||||||
|
"novel": string;
|
||||||
|
/**
|
||||||
|
* ムービー
|
||||||
|
*/
|
||||||
|
"video": string;
|
||||||
|
/**
|
||||||
|
* ミュージック
|
||||||
|
*/
|
||||||
|
"music": string;
|
||||||
|
/**
|
||||||
|
* アドバイス
|
||||||
|
*/
|
||||||
|
"correction": string;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 募集中
|
||||||
|
*/
|
||||||
|
"seeking": string;
|
||||||
|
/**
|
||||||
|
* 停止中
|
||||||
|
*/
|
||||||
|
"stopped": string;
|
||||||
|
/**
|
||||||
|
* クライアント
|
||||||
|
*/
|
||||||
|
"client": string;
|
||||||
|
/**
|
||||||
|
* {x}円
|
||||||
|
*/
|
||||||
|
"yenX": ParameterizedString<"x">;
|
||||||
|
/**
|
||||||
|
* 納品実績 {n}件
|
||||||
|
*/
|
||||||
|
"nWorks": ParameterizedString<"n">;
|
||||||
|
/**
|
||||||
|
* 取引実績 {n}件
|
||||||
|
*/
|
||||||
|
"nRequests": ParameterizedString<"n">;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
declare const locales: {
|
declare const locales: {
|
||||||
[lang: string]: Locale;
|
[lang: string]: Locale;
|
||||||
|
|
|
@ -2693,3 +2693,19 @@ _urlPreviewSetting:
|
||||||
summaryProxy: "プレビューを生成するプロキシのエンドポイント"
|
summaryProxy: "プレビューを生成するプロキシのエンドポイント"
|
||||||
summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
|
summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
|
||||||
summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
|
summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
|
||||||
|
|
||||||
|
_skebStatus:
|
||||||
|
_genres:
|
||||||
|
art: "イラスト"
|
||||||
|
comic: "コミック"
|
||||||
|
voice: "ボイス"
|
||||||
|
novel: "テキスト"
|
||||||
|
video: "ムービー"
|
||||||
|
music: "ミュージック"
|
||||||
|
correction: "アドバイス"
|
||||||
|
seeking: "募集中"
|
||||||
|
stopped: "停止中"
|
||||||
|
client: "クライアント"
|
||||||
|
yenX: "{x}円"
|
||||||
|
nWorks: "納品実績 {n}件"
|
||||||
|
nRequests: "取引実績 {n}件"
|
||||||
|
|
|
@ -2549,4 +2549,18 @@ _reversi:
|
||||||
_offlineScreen:
|
_offlineScreen:
|
||||||
title: "오프라인 - 서버에 접속할 수 없습니다"
|
title: "오프라인 - 서버에 접속할 수 없습니다"
|
||||||
header: "서버에 접속할 수 없습니다"
|
header: "서버에 접속할 수 없습니다"
|
||||||
|
_skebStatus:
|
||||||
|
_genres:
|
||||||
|
art: "작품"
|
||||||
|
comic: "만화"
|
||||||
|
voice: "음성"
|
||||||
|
novel: "텍스트"
|
||||||
|
video: "동영상"
|
||||||
|
music: "음악"
|
||||||
|
correction: "조언"
|
||||||
|
seeking: "모집 중"
|
||||||
|
stopped: "정지 중"
|
||||||
|
client: "클라이언트"
|
||||||
|
yenX: "JPY {x}"
|
||||||
|
nWorks: "납품 실적 {n}건"
|
||||||
|
nRequests: "거래 실적 {n}건"
|
||||||
|
|
|
@ -66,6 +66,14 @@ type Source = {
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
skebStatus?: {
|
||||||
|
method: string;
|
||||||
|
endpoint: string;
|
||||||
|
headers: { [x: string]: string };
|
||||||
|
parameters: { [x: string]: string };
|
||||||
|
userIdParameterName: string;
|
||||||
|
}
|
||||||
|
|
||||||
proxy?: string;
|
proxy?: string;
|
||||||
proxySmtp?: string;
|
proxySmtp?: string;
|
||||||
proxyBypassHosts?: string[];
|
proxyBypassHosts?: string[];
|
||||||
|
@ -140,6 +148,13 @@ export type Config = {
|
||||||
index: string;
|
index: string;
|
||||||
scope?: 'local' | 'global' | string[];
|
scope?: 'local' | 'global' | string[];
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
skebStatus: {
|
||||||
|
method: string;
|
||||||
|
endpoint: string;
|
||||||
|
headers: { [x: string]: string };
|
||||||
|
parameters: { [x: string]: string };
|
||||||
|
userIdParameterName: string;
|
||||||
|
} | undefined;
|
||||||
proxy: string | undefined;
|
proxy: string | undefined;
|
||||||
proxySmtp: string | undefined;
|
proxySmtp: string | undefined;
|
||||||
proxyBypassHosts: string[] | undefined;
|
proxyBypassHosts: string[] | undefined;
|
||||||
|
@ -266,6 +281,7 @@ export function loadConfig(): Config {
|
||||||
redisForObjectStorageQueue: config.redisForObjectStorageQueue ? convertRedisOptions(config.redisForObjectStorageQueue, host) : redisForJobQueue,
|
redisForObjectStorageQueue: config.redisForObjectStorageQueue ? convertRedisOptions(config.redisForObjectStorageQueue, host) : redisForJobQueue,
|
||||||
redisForWebhookDeliverQueue: config.redisForWebhookDeliverQueue ? convertRedisOptions(config.redisForWebhookDeliverQueue, host) : redisForJobQueue,
|
redisForWebhookDeliverQueue: config.redisForWebhookDeliverQueue ? convertRedisOptions(config.redisForWebhookDeliverQueue, host) : redisForJobQueue,
|
||||||
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
|
||||||
|
skebStatus: config.skebStatus,
|
||||||
id: config.id,
|
id: config.id,
|
||||||
proxy: config.proxy,
|
proxy: config.proxy,
|
||||||
proxySmtp: config.proxySmtp,
|
proxySmtp: config.proxySmtp,
|
||||||
|
|
|
@ -112,6 +112,7 @@ export class MetaEntityService {
|
||||||
|
|
||||||
mediaProxy: this.config.mediaProxy,
|
mediaProxy: this.config.mediaProxy,
|
||||||
enableUrlPreview: instance.urlPreviewEnabled,
|
enableUrlPreview: instance.urlPreviewEnabled,
|
||||||
|
enableSkebStatus: !!this.config.skebStatus,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,6 +216,10 @@ export const packedMetaLiteSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
enableSkebStatus: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
backgroundImageUrl: {
|
backgroundImageUrl: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
optional: false, nullable: true,
|
optional: false, nullable: true,
|
||||||
|
|
|
@ -350,6 +350,7 @@ import * as ep___users_following from './endpoints/users/following.js';
|
||||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||||
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
|
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
|
||||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||||
|
import * as ep___users_getSkebStatus from './endpoints/users/get-skeb-status.js';
|
||||||
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
||||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
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
|
||||||
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
|
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
|
||||||
const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default };
|
const $users_getFollowingBirthdayUsers: Provider = { provide: 'ep:users/get-following-birthday-users', useClass: ep___users_getFollowingBirthdayUsers.default };
|
||||||
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
|
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
|
||||||
|
const $users_getSkebStatus: Provider = { provide: 'ep:users/get-skeb-status', useClass: ep___users_getSkebStatus.default };
|
||||||
const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
|
const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
|
||||||
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
|
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
|
||||||
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
|
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__
|
||||||
$users_gallery_posts,
|
$users_gallery_posts,
|
||||||
$users_getFollowingBirthdayUsers,
|
$users_getFollowingBirthdayUsers,
|
||||||
$users_getFrequentlyRepliedUsers,
|
$users_getFrequentlyRepliedUsers,
|
||||||
|
$users_getSkebStatus,
|
||||||
$users_featuredNotes,
|
$users_featuredNotes,
|
||||||
$users_lists_create,
|
$users_lists_create,
|
||||||
$users_lists_delete,
|
$users_lists_delete,
|
||||||
|
@ -1508,6 +1511,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||||
$users_gallery_posts,
|
$users_gallery_posts,
|
||||||
$users_getFollowingBirthdayUsers,
|
$users_getFollowingBirthdayUsers,
|
||||||
$users_getFrequentlyRepliedUsers,
|
$users_getFrequentlyRepliedUsers,
|
||||||
|
$users_getSkebStatus,
|
||||||
$users_featuredNotes,
|
$users_featuredNotes,
|
||||||
$users_lists_create,
|
$users_lists_create,
|
||||||
$users_lists_delete,
|
$users_lists_delete,
|
||||||
|
|
|
@ -350,6 +350,7 @@ import * as ep___users_following from './endpoints/users/following.js';
|
||||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||||
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
|
import * as ep___users_getFollowingBirthdayUsers from './endpoints/users/get-following-birthday-users.js';
|
||||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||||
|
import * as ep___users_getSkebStatus from './endpoints/users/get-skeb-status.js';
|
||||||
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
|
||||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||||
|
@ -734,6 +735,7 @@ const eps = [
|
||||||
['users/gallery/posts', ep___users_gallery_posts],
|
['users/gallery/posts', ep___users_gallery_posts],
|
||||||
['users/get-following-birthday-users', ep___users_getFollowingBirthdayUsers],
|
['users/get-following-birthday-users', ep___users_getFollowingBirthdayUsers],
|
||||||
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
|
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
|
||||||
|
['users/get-skeb-status', ep___users_getSkebStatus],
|
||||||
['users/featured-notes', ep___users_featuredNotes],
|
['users/featured-notes', ep___users_featuredNotes],
|
||||||
['users/lists/create', ep___users_lists_create],
|
['users/lists/create', ep___users_lists_create],
|
||||||
['users/lists/delete', ep___users_lists_delete],
|
['users/lists/delete', ep___users_lists_delete],
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||||
|
import type { Config } from '@/config.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['users'],
|
||||||
|
|
||||||
|
requireCredential: false,
|
||||||
|
allowGet: true,
|
||||||
|
cacheSec: 60 * 5,
|
||||||
|
|
||||||
|
res: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
screenName: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isCreator: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
isAcceptable: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
creatorRequestCount: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
clientRequestCount: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
skills: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
amount: {
|
||||||
|
type: 'integer',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
genre: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
enum: ['art', 'comic', 'voice', 'novel', 'video', 'music', 'correction'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
skebStatusNotAvailable: {
|
||||||
|
message: 'Skeb status is not available.',
|
||||||
|
code: 'SKEB_STATUS_NOT_AVAILABLE',
|
||||||
|
id: '1dd37c9c-7e97-4c24-be98-227a78b21d80',
|
||||||
|
httpStatusCode: 403,
|
||||||
|
},
|
||||||
|
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: '88d582ae-69d9-45e0-a8b3-13f9945e48bf',
|
||||||
|
httpStatusCode: 404,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
userId: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
required: ['userId'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.config)
|
||||||
|
private config: Config,
|
||||||
|
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
private httpRequestService: HttpRequestService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps) => {
|
||||||
|
if (!this.config.skebStatus) throw new ApiError(meta.errors.skebStatusNotAvailable);
|
||||||
|
const logger = this.loggerService.getLogger('api:users:get-skeb-status');
|
||||||
|
|
||||||
|
const url = new URL(this.config.skebStatus.endpoint);
|
||||||
|
for (const [key, value] of Object.entries(this.config.skebStatus.parameters)) {
|
||||||
|
url.searchParams.set(key, value);
|
||||||
|
}
|
||||||
|
url.searchParams.set(this.config.skebStatus.userIdParameterName, ps.userId);
|
||||||
|
|
||||||
|
logger.info('Requesting Skeb status', { url: url.href, userId: ps.userId });
|
||||||
|
const res = await this.httpRequestService.send(
|
||||||
|
url.href,
|
||||||
|
{
|
||||||
|
method: this.config.skebStatus.method,
|
||||||
|
headers: {
|
||||||
|
...this.config.skebStatus.headers,
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
|
timeout: 5000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
throwErrorWhenResponseNotOk: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const json = (await res.json()) as {
|
||||||
|
screen_name: string,
|
||||||
|
is_creator: boolean,
|
||||||
|
is_acceptable: boolean,
|
||||||
|
creator_request_count: number,
|
||||||
|
client_request_count: number,
|
||||||
|
skills: { amount: number, genre: 'art' | 'comic' | 'voice' | 'novel' | 'video' | 'music' | 'correction' }[],
|
||||||
|
ban_reason?: string | null
|
||||||
|
error?: unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (res.status > 399 || (json.error ?? json.ban_reason)) {
|
||||||
|
logger.error('Skeb status response error', { url: url.href, userId: ps.userId, status: res.status, statusText: res.statusText, error: json.error ?? json.ban_reason });
|
||||||
|
throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Skeb status response', { url: url.href, userId: ps.userId, status: res.status, statusText: res.statusText, skebStatus: json });
|
||||||
|
|
||||||
|
return {
|
||||||
|
screenName: json.screen_name,
|
||||||
|
isCreator: json.is_creator,
|
||||||
|
isAcceptable: json.is_acceptable,
|
||||||
|
creatorRequestCount: json.creator_request_count,
|
||||||
|
clientRequestCount: json.client_request_count,
|
||||||
|
skills: json.skills,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user.fields.length > 0" class="fields">
|
<div v-if="user.fields.length > 0 || userSkebStatus" class="fields">
|
||||||
<dl v-for="(field, i) in user.fields" :key="i" class="field">
|
<dl v-for="(field, i) in user.fields" :key="i" class="field">
|
||||||
<dt class="name">
|
<dt class="name">
|
||||||
<Mfm :text="field.name" :plain="true" :colored="false"/>
|
<Mfm :text="field.name" :plain="true" :colored="false"/>
|
||||||
|
@ -104,6 +104,40 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ti ti-circle-check" :class="$style.verifiedLink"></i>
|
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ti ti-circle-check" :class="$style.verifiedLink"></i>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
<dl v-if="userSkebStatus" class="field">
|
||||||
|
<dt class="name">
|
||||||
|
<a :href="`https://skeb.jp/@${userSkebStatus.screenName}`" target="_blank" rel="noopener">
|
||||||
|
<!--
|
||||||
|
*** LICENSE NOTICE ***
|
||||||
|
* This SVG is derived from the https://skeb.jp/ website, All rights reserved to *Skeb Inc.* https://skeb.co.jp/
|
||||||
|
* This resource SHOULD NOT be considered as a part of the this project that has licensed under AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
<svg class="ti ti-fw" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<linearGradient id="a" x1="14.645309%" x2="85.354691%" y1="14.645309%" y2="85.363492%">
|
||||||
|
<stop offset=".2" stop-color="#30b396"/>
|
||||||
|
<stop offset="1" stop-color="#1e5e71"/>
|
||||||
|
</linearGradient>
|
||||||
|
<g fill="none">
|
||||||
|
<circle cx="19.99375" cy="19.98125" fill="url(#a)" r="17.753125"/>
|
||||||
|
<path d="m18.784375 13.9875-7.35625 21.546875c-.8125-.446875-1.58125-.959375-2.309375-1.525l1.134375-3.26875-2.528125-.86875.859375-2.56875 2.528125.86875 1.728125-5.05625-2.528125-.86875.86875-2.5375 2.528125.86875 1.728125-5.05625-2.528125-.86875.86875-2.5375 2.528125.86875 1.6875-4.99375-2.496875-.909375.440625-1.26875 5.05625 1.728125-.378125 1.1-1.76875 5.184375-.059375.159375zm-12.996875 16.640625c.6625.884375 1.40625 1.703125 2.21875 2.446875l.51875-1.515625zm3.29375-13.01875 3.8 1.296875.865625-2.534375-3.8-1.296875zm-2.590625 7.590625 3.8 1.296875.865625-2.534375-3.8-1.296875zm31.259375-5.21875c0-2.534375-.534375-4.940625-1.490625-7.11875l-13.325-4.559375-9.603125 28.134375c2.059375.834375 4.30625 1.3 6.665625 1.3 9.80625 0 17.753125-7.946875 17.753125-17.753125zm-26.071875-9.971875 3.8 1.296875.865625-2.534375-3.8-1.296875z" fill="#fff"/><path d="m19.99375 2.23125c9.80625 0 17.753125 7.946875 17.753125 17.753125s-7.946875 17.753125-17.753125 17.753125-17.753125-7.946875-17.753125-17.753125 7.946875-17.753125 17.753125-17.753125m0-2.228125c-11.034375 0-19.98125 8.946875-19.98125 19.98125s8.946875 19.98125 19.98125 19.98125 19.98125-8.946875 19.98125-19.98125-8.946875-19.98125-19.98125-19.98125z" fill="#1e5e71"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Skeb
|
||||||
|
</a>
|
||||||
|
</dt>
|
||||||
|
<dd class="value" style="display: flex; gap: 8px; align-items: center;">
|
||||||
|
<span v-if="userSkebStatus.isAcceptable" :class="$style.skebAcceptable">
|
||||||
|
{{ i18n.ts._skebStatus.seeking }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="userSkebStatus.isCreator" :class="$style.skebStopped">
|
||||||
|
{{ i18n.ts._skebStatus.stopped }}
|
||||||
|
</span>
|
||||||
|
<span v-else :class="$style.skebClient">
|
||||||
|
{{ i18n.ts._skebStatus.client }}
|
||||||
|
</span>
|
||||||
|
<Mfm :text="buildSkebStatus()" :author="user" :nyaize="false" :colored="false"/>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<MkA :to="userPage(user)">
|
<MkA :to="userPage(user)">
|
||||||
|
@ -167,10 +201,11 @@ import number from '@/filters/number.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
import { $i, iAmModerator } from '@/account.js';
|
import { $i, iAmModerator } from '@/account.js';
|
||||||
import { dateString } from '@/filters/date.js';
|
import { dateString } from '@/filters/date.js';
|
||||||
import { confetti } from '@/scripts/confetti.js';
|
import { confetti } from '@/scripts/confetti.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
|
||||||
import { useRouter } from '@/router/supplier.js';
|
import { useRouter } from '@/router/supplier.js';
|
||||||
|
|
||||||
|
@ -204,6 +239,7 @@ const props = withDefaults(defineProps<{
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const user = ref(props.user);
|
const user = ref(props.user);
|
||||||
|
const userSkebStatus = ref<Misskey.Endpoints['users/get-skeb-status']['res'] | null>(null);
|
||||||
const parallaxAnimationId = ref<null | number>(null);
|
const parallaxAnimationId = ref<null | number>(null);
|
||||||
const narrow = ref<null | boolean>(null);
|
const narrow = ref<null | boolean>(null);
|
||||||
const rootEl = ref<null | HTMLElement>(null);
|
const rootEl = ref<null | HTMLElement>(null);
|
||||||
|
@ -273,8 +309,44 @@ async function updateMemo() {
|
||||||
isEditingMemo.value = false;
|
isEditingMemo.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchSkebStatus() {
|
||||||
|
if (!instance.enableSkebStatus || !props.user.id) {
|
||||||
|
userSkebStatus.value = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('fetching skeb status');
|
||||||
|
userSkebStatus.value = await misskeyApiGet('users/get-skeb-status', { userId: props.user.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSkebStatus(): string {
|
||||||
|
if (!userSkebStatus.value) return '';
|
||||||
|
|
||||||
|
if (userSkebStatus.value.isCreator) {
|
||||||
|
let status = '';
|
||||||
|
|
||||||
|
if (userSkebStatus.value.isAcceptable) {
|
||||||
|
status += `${i18n.ts._skebStatus._genres[userSkebStatus.value.skills[0].genre]} ${i18n.tsx._skebStatus.yenX({ x: userSkebStatus.value.skills[0].amount.toLocaleString() })}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userSkebStatus.value.creatorRequestCount > 0) {
|
||||||
|
if (userSkebStatus.value.isAcceptable) {
|
||||||
|
status += ' | ';
|
||||||
|
}
|
||||||
|
status += i18n.tsx._skebStatus.nWorks({ n: userSkebStatus.value.creatorRequestCount.toLocaleString() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
} else if (userSkebStatus.value.clientRequestCount > 0) {
|
||||||
|
return i18n.tsx._skebStatus.nRequests({ n: userSkebStatus.value.clientRequestCount.toLocaleString() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
watch([props.user], () => {
|
watch([props.user], () => {
|
||||||
memoDraft.value = props.user.memo;
|
memoDraft.value = props.user.memo;
|
||||||
|
fetchSkebStatus();
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
@ -292,6 +364,7 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fetchSkebStatus();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
adjustMemoTextarea();
|
adjustMemoTextarea();
|
||||||
});
|
});
|
||||||
|
@ -685,4 +758,29 @@ onUnmounted(() => {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
color: var(--success);
|
color: var(--success);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.skebAcceptable,
|
||||||
|
.skebStopped,
|
||||||
|
.skebClient {
|
||||||
|
display: inline-flex;
|
||||||
|
border: solid 1px;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 85%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skebAcceptable {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
background-color: rgb(241, 70, 104);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skebStopped {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
background-color: rgb(54, 54, 54);
|
||||||
|
}
|
||||||
|
|
||||||
|
.skebClient {
|
||||||
|
color: rgb(255, 255, 255);
|
||||||
|
background-color: rgb(54, 54, 54);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1690,6 +1690,8 @@ declare namespace entities {
|
||||||
UsersGetFollowingBirthdayUsersResponse,
|
UsersGetFollowingBirthdayUsersResponse,
|
||||||
UsersGetFrequentlyRepliedUsersRequest,
|
UsersGetFrequentlyRepliedUsersRequest,
|
||||||
UsersGetFrequentlyRepliedUsersResponse,
|
UsersGetFrequentlyRepliedUsersResponse,
|
||||||
|
UsersGetSkebStatusRequest,
|
||||||
|
UsersGetSkebStatusResponse,
|
||||||
UsersFeaturedNotesRequest,
|
UsersFeaturedNotesRequest,
|
||||||
UsersFeaturedNotesResponse,
|
UsersFeaturedNotesResponse,
|
||||||
UsersListsCreateRequest,
|
UsersListsCreateRequest,
|
||||||
|
@ -3105,6 +3107,12 @@ type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
|
type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type UsersGetSkebStatusRequest = operations['users___get-skeb-status']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
// @public (undocumented)
|
||||||
|
type UsersGetSkebStatusResponse = operations['users___get-skeb-status']['responses']['200']['content']['application/json'];
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json'];
|
type UsersListsCreateFromPublicRequest = operations['users___lists___create-from-public']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
|
|
@ -3826,6 +3826,17 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
request<E extends 'users/get-skeb-status', P extends Endpoints[E]['req']>(
|
||||||
|
endpoint: E,
|
||||||
|
params: P,
|
||||||
|
credential?: string | null,
|
||||||
|
): Promise<SwitchCaseResponseType<E, P>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* No description provided.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
@ -510,6 +510,8 @@ import type {
|
||||||
UsersGetFollowingBirthdayUsersResponse,
|
UsersGetFollowingBirthdayUsersResponse,
|
||||||
UsersGetFrequentlyRepliedUsersRequest,
|
UsersGetFrequentlyRepliedUsersRequest,
|
||||||
UsersGetFrequentlyRepliedUsersResponse,
|
UsersGetFrequentlyRepliedUsersResponse,
|
||||||
|
UsersGetSkebStatusRequest,
|
||||||
|
UsersGetSkebStatusResponse,
|
||||||
UsersFeaturedNotesRequest,
|
UsersFeaturedNotesRequest,
|
||||||
UsersFeaturedNotesResponse,
|
UsersFeaturedNotesResponse,
|
||||||
UsersListsCreateRequest,
|
UsersListsCreateRequest,
|
||||||
|
@ -920,6 +922,7 @@ export type Endpoints = {
|
||||||
'users/gallery/posts': { req: UsersGalleryPostsRequest; res: UsersGalleryPostsResponse };
|
'users/gallery/posts': { req: UsersGalleryPostsRequest; res: UsersGalleryPostsResponse };
|
||||||
'users/get-following-birthday-users': { req: UsersGetFollowingBirthdayUsersRequest; res: UsersGetFollowingBirthdayUsersResponse };
|
'users/get-following-birthday-users': { req: UsersGetFollowingBirthdayUsersRequest; res: UsersGetFollowingBirthdayUsersResponse };
|
||||||
'users/get-frequently-replied-users': { req: UsersGetFrequentlyRepliedUsersRequest; res: UsersGetFrequentlyRepliedUsersResponse };
|
'users/get-frequently-replied-users': { req: UsersGetFrequentlyRepliedUsersRequest; res: UsersGetFrequentlyRepliedUsersResponse };
|
||||||
|
'users/get-skeb-status': { req: UsersGetSkebStatusRequest; res: UsersGetSkebStatusResponse };
|
||||||
'users/featured-notes': { req: UsersFeaturedNotesRequest; res: UsersFeaturedNotesResponse };
|
'users/featured-notes': { req: UsersFeaturedNotesRequest; res: UsersFeaturedNotesResponse };
|
||||||
'users/lists/create': { req: UsersListsCreateRequest; res: UsersListsCreateResponse };
|
'users/lists/create': { req: UsersListsCreateRequest; res: UsersListsCreateResponse };
|
||||||
'users/lists/delete': { req: UsersListsDeleteRequest; res: EmptyResponse };
|
'users/lists/delete': { req: UsersListsDeleteRequest; res: EmptyResponse };
|
||||||
|
|
|
@ -513,6 +513,8 @@ export type UsersGetFollowingBirthdayUsersRequest = operations['users___get-foll
|
||||||
export type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json'];
|
export type UsersGetFollowingBirthdayUsersResponse = operations['users___get-following-birthday-users']['responses']['200']['content']['application/json'];
|
||||||
export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json'];
|
export type UsersGetFrequentlyRepliedUsersRequest = operations['users___get-frequently-replied-users']['requestBody']['content']['application/json'];
|
||||||
export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
|
export type UsersGetFrequentlyRepliedUsersResponse = operations['users___get-frequently-replied-users']['responses']['200']['content']['application/json'];
|
||||||
|
export type UsersGetSkebStatusRequest = operations['users___get-skeb-status']['requestBody']['content']['application/json'];
|
||||||
|
export type UsersGetSkebStatusResponse = operations['users___get-skeb-status']['responses']['200']['content']['application/json'];
|
||||||
export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json'];
|
export type UsersFeaturedNotesRequest = operations['users___featured-notes']['requestBody']['content']['application/json'];
|
||||||
export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json'];
|
export type UsersFeaturedNotesResponse = operations['users___featured-notes']['responses']['200']['content']['application/json'];
|
||||||
export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json'];
|
export type UsersListsCreateRequest = operations['users___lists___create']['requestBody']['content']['application/json'];
|
||||||
|
|
|
@ -3294,6 +3294,22 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['users___get-frequently-replied-users'];
|
post: operations['users___get-frequently-replied-users'];
|
||||||
};
|
};
|
||||||
|
'/users/get-skeb-status': {
|
||||||
|
/**
|
||||||
|
* users/get-skeb-status
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
get: operations['users___get-skeb-status'];
|
||||||
|
/**
|
||||||
|
* users/get-skeb-status
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
post: operations['users___get-skeb-status'];
|
||||||
|
};
|
||||||
'/users/featured-notes': {
|
'/users/featured-notes': {
|
||||||
/**
|
/**
|
||||||
* users/featured-notes
|
* users/featured-notes
|
||||||
|
@ -5008,6 +5024,7 @@ export type components = {
|
||||||
translatorAvailable: boolean;
|
translatorAvailable: boolean;
|
||||||
mediaProxy: string;
|
mediaProxy: string;
|
||||||
enableUrlPreview: boolean;
|
enableUrlPreview: boolean;
|
||||||
|
enableSkebStatus: boolean;
|
||||||
backgroundImageUrl: string | null;
|
backgroundImageUrl: string | null;
|
||||||
impressumUrl: string | null;
|
impressumUrl: string | null;
|
||||||
logoImageUrl: string | null;
|
logoImageUrl: string | null;
|
||||||
|
@ -25669,6 +25686,71 @@ export type operations = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* users/get-skeb-status
|
||||||
|
* @description No description provided.
|
||||||
|
*
|
||||||
|
* **Credential required**: *No*
|
||||||
|
*/
|
||||||
|
'users___get-skeb-status': {
|
||||||
|
requestBody: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
/** Format: misskey:id */
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description OK (with results) */
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
screenName: string;
|
||||||
|
isCreator: boolean;
|
||||||
|
isAcceptable: boolean;
|
||||||
|
creatorRequestCount: number;
|
||||||
|
clientRequestCount: number;
|
||||||
|
skills: ({
|
||||||
|
amount: number;
|
||||||
|
/** @enum {string} */
|
||||||
|
genre: 'art' | 'comic' | 'voice' | 'novel' | 'video' | 'music' | 'correction';
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Client error */
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Authentication error */
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden error */
|
||||||
|
403: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description I'm Ai */
|
||||||
|
418: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Internal server error */
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
'application/json': components['schemas']['Error'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* users/featured-notes
|
* users/featured-notes
|
||||||
* @description No description provided.
|
* @description No description provided.
|
||||||
|
|
Loading…
Reference in New Issue