diff --git a/locales/en-US.yml b/locales/en-US.yml index 3cf165e3e..2c28f8dae 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -305,6 +305,7 @@ activity: "Activity" images: "Images" image: "Image" birthday: "Birthday" +listenbrainzDescription: "Input your listenbrainz username here" yearsOld: "{age} years old" registeredDate: "Joined on" location: "Location" diff --git a/locales/index.d.ts b/locales/index.d.ts index da14073e8..a659ead4a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1244,6 +1244,10 @@ export interface Locale extends ILocale { * 誕生日 */ "birthday": string; + /** + * listenbrainzのユーザー名を入力してください。 + */ + "listenbrainzDescription": string; /** * {age}歳 */ @@ -6843,7 +6847,7 @@ export interface Locale extends ILocale { }; "_tutorialCompleted": { /** - * Misskey初心者講座 修了証 + * Forkey初心者講座 修了証 */ "title": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0e4770453..64d734f69 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -307,6 +307,7 @@ activity: "アクティビティ" images: "画像" image: "画像" birthday: "誕生日" +listenbrainzDescription: "listenbrainzのユーザー名を入力してください。" yearsOld: "{age}歳" registeredDate: "登録日" location: "場所" diff --git a/packages/backend/migration/1691264431000-add-lb-to-user.js b/packages/backend/migration/1691264431000-add-lb-to-user.js new file mode 100644 index 000000000..4bfbf34bb --- /dev/null +++ b/packages/backend/migration/1691264431000-add-lb-to-user.js @@ -0,0 +1,20 @@ +export class AddLbToUser1691264431000 { + name = "AddLbToUser1691264431000"; + + async up(queryRunner) { + await queryRunner.query(` + ALTER TABLE "user_profile" + ADD "listenbrainz" character varying(128) NULL + `); + await queryRunner.query(` + COMMENT ON COLUMN "user_profile"."listenbrainz" + IS 'listenbrainz username to fetch currently playing.' + `); + } + + async down(queryRunner) { + await queryRunner.query(` + ALTER TABLE "user_profile" DROP COLUMN "listenbrainz" + `); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index e366a86b4..bdee50c0a 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -530,6 +530,7 @@ export class UserEntityService implements OnModuleInit { description: profile!.description, location: profile!.location, birthday: profile!.birthday, + listenbrainz: profile!.listenbrainz, lang: profile!.lang, fields: profile!.fields, verifiedLinks: profile!.verifiedLinks, diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 7db66f1f8..e9650e8d7 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -307,4 +307,5 @@ export const passwordSchema = { type: 'string', minLength: 1 } as const; export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const; export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const; +export const listenbrainzSchema = { type: "string", minLength: 1, maxLength: 128 } as const; export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const; diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 6a1d68625..49f387479 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -37,6 +37,13 @@ export class MiUserProfile { }) public birthday: string | null; + @Column("varchar", { + length: 128, + nullable: true, + comment: "The ListenBrainz username of the User.", + }) + public listenbrainz: string | null; + @Column('varchar', { length: 2048, nullable: true, comment: 'The description (bio) of the User.', diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 1fc71c2ac..2d3129c94 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -268,6 +268,12 @@ export const packedUserDetailedNotMeOnlySchema = { nullable: true, optional: false, example: '2018-03-12', }, + ListenBrainz: { + type: "string", + nullable: true, + optional: false, + example: "Steve", + }, lang: { type: 'string', nullable: true, optional: false, diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 62d64a8ff..c32a41a0a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -13,7 +13,7 @@ import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; -import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; +import { birthdaySchema, listenbrainzSchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js'; import type { MiUserProfile } from '@/models/UserProfile.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import { langmap } from '@/misc/langmap.js'; @@ -150,6 +150,7 @@ export const paramDef = { description: { ...descriptionSchema, nullable: true }, location: { ...locationSchema, nullable: true }, birthday: { ...birthdaySchema, nullable: true }, + listenbrainz: { ...listenbrainzSchema, nullable: true }, lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true }, avatarId: { type: 'string', format: 'misskey:id', nullable: true }, avatarDecorations: { type: 'array', maxItems: 16, items: { @@ -306,6 +307,7 @@ export default class extends Endpoint { // eslint- if (ps.lang !== undefined) profileUpdates.lang = ps.lang; if (ps.location !== undefined) profileUpdates.location = ps.location; if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; + if (ps.listenbrainz !== undefined) profileUpdates.listenbrainz = ps.listenbrainz; if (ps.followingVisibility !== undefined) profileUpdates.followingVisibility = ps.followingVisibility; if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility; if (ps.mutedWords !== undefined) { diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index dfeba525a..66dfd5948 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -37,6 +37,12 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + + @@ -217,6 +223,7 @@ const profile = reactive({ description: $i.description, location: $i.location, birthday: $i.birthday, + listenbrainz: $i?.listenbrainz, lang: $i.lang, isBot: $i.isBot ?? false, isCat: $i.isCat ?? false, @@ -300,6 +307,7 @@ function save() { location: profile.location || null, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing birthday: profile.birthday || null, + listenbrainz: profile.listenbrainz || null, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing lang: profile.lang || null, isBot: !!profile.isBot, diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 2cd06bb7b..927aa16ee 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -209,6 +209,12 @@ SPDX-License-Identifier: AGPL-3.0-only
+
@@ -258,6 +264,7 @@ function calcAge(birthdate: string): number { const XFiles = defineAsyncComponent(() => import('./index.files.vue')); const XActivity = defineAsyncComponent(() => import('./index.activity.vue')); +const XListenBrainz = defineAsyncComponent(() => import("./index.listenbrainz.vue")); const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue')); const props = withDefaults(defineProps<{ @@ -283,6 +290,24 @@ const moderationNote = ref(props.user.moderationNote); const editModerationNote = ref(false); const movedFromLog = ref(null); +let listenbrainzdata = false; +if (props.user.listenbrainz) { + try { + const response = await fetch(`https://api.listenbrainz.org/1/user/${props.user.listenbrainz}/playing-now`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + }); + const data = await response.json(); + if (data.payload.listens && data.payload.listens.length !== 0) { + listenbrainzdata = true; + } + } catch(err) { + listenbrainzdata = false; + } +} + watch(moderationNote, async () => { await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value }); }); diff --git a/packages/frontend/src/pages/user/index.listenbrainz.vue b/packages/frontend/src/pages/user/index.listenbrainz.vue new file mode 100644 index 000000000..1c9ef8dd2 --- /dev/null +++ b/packages/frontend/src/pages/user/index.listenbrainz.vue @@ -0,0 +1,140 @@ + + + + + + +