Compare commits

...

9 commits

Author SHA1 Message Date
16902a26a3 resolve merge conflicts 2023-02-12 08:58:17 +01:00
1ffa4b08e0
client: reformat notification component
who the hell is supposed to read this
2023-02-11 19:25:51 +01:00
c9d395961e
server: refactor packing User 2023-02-11 19:17:11 +01:00
3a7e8cfe50
server: check instance description length limit
Changelog: Fixed
2023-02-11 19:16:28 +01:00
b8796cb1fa
activitypub: remove _misskey_votes property
This is a duplication of `replies.totalItems` and seems unnecessary,
it is even only parsed by Misskey if the afforementioned property is
not available.

Changelog: Removed
2023-02-11 17:49:12 +01:00
68bc2e314b
activitypub: remove _misskey_reaction property
This property is duplicated by the `content` property so seems unnecessary.

Changelog: Removed
2023-02-11 17:43:44 +01:00
fff93c6965
activitypub: remove _misskey_content attribute
As already noted back in https://github.com/misskey-dev/misskey/pull/8787
the intention was to replace the `_misskey_content` attribute with the
ActivityPub-defined `source` property. Misskey and by extension Foundkey
have shipped with the `source` property and the respective parsing for
quite a while so it seems reasonable to remove it now.

Changelog: Removed
2023-02-11 17:25:24 +01:00
7c89e99243
fix registry migration
It can happen that registry items were created at exactly the same time for some reason.
2023-02-11 12:52:28 +01:00
6ed13ea9a7
fix typo, the 2nd 2023-02-11 10:23:15 +01:00
13 changed files with 44 additions and 37 deletions

View file

@ -5,8 +5,8 @@ export class registryRemoveDomain1675375940759 {
await queryRunner.query(`DROP INDEX "public"."IDX_0a72bdfcdb97c0eca11fe7ecad"`); await queryRunner.query(`DROP INDEX "public"."IDX_0a72bdfcdb97c0eca11fe7ecad"`);
await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "domain"`); await queryRunner.query(`ALTER TABLE "registry_item" DROP COLUMN "domain"`);
await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "key" TYPE text USING "key"::text`); await queryRunner.query(`ALTER TABLE "registry_item" ALTER COLUMN "key" TYPE text USING "key"::text`);
// delete existing duplicated entries, keeping the latest updated one // delete existing duplicated entries, keeping the latest created one
await queryRunner.query(`DELETE FROM "registry_item" AS "a" WHERE "updatedAt" != (SELECT MAX("updatedAt") FROM "registry_item" AS "b" WHERE "a"."userId" = "b"."userId" AND "a"."key" = "b"."key" AND "a"."scope" = "b"."scope" GROUP BY "userId", "key", "scope")`); await queryRunner.query(`DELETE FROM "registry_item" AS "a" WHERE "id" != (SELECT MAX("id") FROM "registry_item" AS "b" WHERE "a"."userId" = "b"."userId" AND "a"."key" = "b"."key" AND "a"."scope" = "b"."scope" GROUP BY "userId", "key", "scope")`);
await queryRunner.query(`ALTER TABLE "registry_item" ADD CONSTRAINT "UQ_b8d6509f847331273ab99daccc7" UNIQUE ("userId", "key", "scope")`); await queryRunner.query(`ALTER TABLE "registry_item" ADD CONSTRAINT "UQ_b8d6509f847331273ab99daccc7" UNIQUE ("userId", "key", "scope")`);
} }

View file

@ -270,16 +270,14 @@ export const UserRepository = db.getRepository(User).extend({
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followingCount : (profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : (profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followingCount :
null; null;
const followersCount = profile == null ? null : const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || isMe ? user.followersCount : (profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : (profile.ffVisibility === 'followers') && relation?.isFollowing ? user.followersCount :
null; null;
const falsy = opts.detail ? false : undefined;
const packed = { const packed = {
id: user.id, id: user.id,
name: user.name, name: user.name,
@ -287,10 +285,10 @@ export const UserRepository = db.getRepository(User).extend({
host: user.host, host: user.host,
avatarUrl: this.getAvatarUrlSync(user), avatarUrl: this.getAvatarUrlSync(user),
avatarBlurhash: user.avatar?.blurhash || null, avatarBlurhash: user.avatar?.blurhash || null,
isAdmin: user.isAdmin || falsy, isAdmin: user.isAdmin,
isModerator: user.isModerator || falsy, isModerator: user.isModerator,
isBot: user.isBot || falsy, isBot: user.isBot,
isCat: user.isCat || falsy, isCat: user.isCat,
instance: !user.host ? undefined : userInstanceCache.fetch(user.host) instance: !user.host ? undefined : userInstanceCache.fetch(user.host)
.then(instance => !instance ? undefined : { .then(instance => !instance ? undefined : {
name: instance.name, name: instance.name,
@ -312,8 +310,8 @@ export const UserRepository = db.getRepository(User).extend({
bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null, bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null,
bannerBlurhash: user.banner?.blurhash || null, bannerBlurhash: user.banner?.blurhash || null,
isLocked: user.isLocked, isLocked: user.isLocked,
isSilenced: user.isSilenced || falsy, isSilenced: user.isSilenced,
isSuspended: user.isSuspended || falsy, isSuspended: user.isSuspended,
description: profile!.description, description: profile!.description,
location: profile!.location, location: profile!.location,
birthday: profile!.birthday, birthday: profile!.birthday,
@ -369,7 +367,7 @@ export const UserRepository = db.getRepository(User).extend({
mutedInstances: profile!.mutedInstances, mutedInstances: profile!.mutedInstances,
mutingNotificationTypes: profile!.mutingNotificationTypes, mutingNotificationTypes: profile!.mutingNotificationTypes,
emailNotificationTypes: profile!.emailNotificationTypes, emailNotificationTypes: profile!.emailNotificationTypes,
showTimelineReplies: user.showTimelineReplies || falsy, showTimelineReplies: user.showTimelineReplies,
federateBlocks: user!.federateBlocks, federateBlocks: user!.federateBlocks,
} : {}), } : {}),

View file

@ -11,7 +11,7 @@ export default async (actor: CacheableRemoteUser, activity: ILike) => {
await extractEmojis(activity.tag || [], actor.host).catch(() => null); await extractEmojis(activity.tag || [], actor.host).catch(() => null);
return await createReaction(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { return await createReaction(actor, note, activity.content || activity.name).catch(e => {
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') {
return 'skip: already reacted'; return 'skip: already reacted';
} else { } else {

View file

@ -207,8 +207,6 @@ export async function createNote(value: string | IObject, resolver: Resolver, si
let text: string | null = null; let text: string | null = null;
if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') { if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
text = note.source.content; text = note.source.content;
} else if (typeof note._misskey_content !== 'undefined') {
text = note._misskey_content;
} else if (typeof note.content === 'string') { } else if (typeof note.content === 'string') {
text = fromHtml(note.content, quote?.uri); text = fromHtml(note.content, quote?.uri);
} }

View file

@ -23,7 +23,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
.map(x => x.name!); .map(x => x.name!);
const votes = question[multiple ? 'anyOf' : 'oneOf']! const votes = question[multiple ? 'anyOf' : 'oneOf']!
.map(x => x.replies && x.replies.totalItems || x._misskey_votes || 0); .map(x => x.replies && x.replies.totalItems || 0);
return { return {
choices, choices,

View file

@ -35,10 +35,7 @@ export const renderActivity = (x: any): IActivity | null => {
value: 'schema:value', value: 'schema:value',
// Misskey // Misskey
misskey: 'https://misskey-hub.net/ns#', misskey: 'https://misskey-hub.net/ns#',
'_misskey_content': 'misskey:_misskey_content',
'_misskey_quote': 'misskey:_misskey_quote', '_misskey_quote': 'misskey:_misskey_quote',
'_misskey_reaction': 'misskey:_misskey_reaction',
'_misskey_votes': 'misskey:_misskey_votes',
'_misskey_talk': 'misskey:_misskey_talk', '_misskey_talk': 'misskey:_misskey_talk',
'isCat': 'misskey:isCat', 'isCat': 'misskey:isCat',
// vcard // vcard

View file

@ -13,10 +13,7 @@ export const renderLike = async (noteReaction: NoteReaction, note: Note) => {
id: `${config.url}/likes/${noteReaction.id}`, id: `${config.url}/likes/${noteReaction.id}`,
actor: `${config.url}/users/${noteReaction.userId}`, actor: `${config.url}/users/${noteReaction.userId}`,
object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`, object: note.uri ? note.uri : `${config.url}/notes/${noteReaction.noteId}`,
... (reaction !== '\u2b50' ? {
content: reaction, content: reaction,
_misskey_reaction: reaction,
} : {}),
} as any; } as any;
if (reaction.startsWith(':')) { if (reaction.startsWith(':')) {

View file

@ -145,7 +145,6 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
attributedTo, attributedTo,
summary, summary,
content, content,
_misskey_content: text,
source: { source: {
content: text, content: text,
mediaType: 'text/x.misskeymarkdown', mediaType: 'text/x.misskeymarkdown',

View file

@ -11,7 +11,6 @@ export default async function renderQuestion(user: { id: User['id'] }, note: Not
content: note.text || '', content: note.text || '',
[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({ [poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
name: text, name: text,
_misskey_votes: poll.votes[i],
replies: { replies: {
type: 'Collection', type: 'Collection',
totalItems: poll.votes[i], totalItems: poll.votes[i],

View file

@ -171,7 +171,6 @@ export const isQuestion = (object: IObject): object is IQuestion =>
interface IQuestionChoice { interface IQuestionChoice {
name?: string; name?: string;
replies?: ICollection; replies?: ICollection;
_misskey_votes?: number;
} }
export interface ITombstone extends IObject { export interface ITombstone extends IObject {
type: 'Tombstone'; type: 'Tombstone';
@ -282,7 +281,7 @@ export interface IRemove extends IActivity {
export interface ILike extends IActivity { export interface ILike extends IActivity {
type: 'Like' | 'EmojiReaction' | 'EmojiReact'; type: 'Like' | 'EmojiReaction' | 'EmojiReact';
_misskey_reaction?: string; content?: string;
} }
export interface IAnnounce extends IActivity { export interface IAnnounce extends IActivity {

View file

@ -246,19 +246,20 @@ async function getSiteName(info: NodeInfo | null, doc: DOMWindow['document'] | n
async function getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> { async function getDescription(info: NodeInfo | null, doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
if (info && info.metadata) { if (info && info.metadata) {
if (info.metadata.nodeDescription || info.metadata.description) { const description = info.metadata.nodeDescription || info.metadata.description;
return info.metadata.nodeDescription || info.metadata.description; if (description && description.length < 4096) {
return description;
} }
} }
if (doc) { if (doc) {
const meta = doc.querySelector('meta[name="description"]')?.getAttribute('content'); const meta = doc.querySelector('meta[name="description"]')?.getAttribute('content');
if (meta) { if (meta && meta.length < 4096) {
return meta; return meta;
} }
const og = doc.querySelector('meta[property="og:description"]')?.getAttribute('content'); const og = doc.querySelector('meta[property="og:description"]')?.getAttribute('content');
if (og) { if (og && og.length < 4096) {
return og; return og;
} }
} }

View file

@ -35,7 +35,7 @@ const props = withDefaults(defineProps<{
const self = props.url.startsWith(local); const self = props.url.startsWith(local);
const uri = new URL(props.url); const uri = new URL(props.url);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url'); if (!['http:', 'https:'].includes(uri.protocol)) throw new Error('invalid url');
let el: HTMLElement | null = $ref(null); let el: HTMLElement | null = $ref(null);
let schema = $ref(uri.protocol); let schema = $ref(uri.protocol);

View file

@ -63,10 +63,29 @@
<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/> <Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full" :custom-emojis="notification.note.emojis"/>
<i class="fas fa-quote-right"></i> <i class="fas fa-quote-right"></i>
</MkA> </MkA>
<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span> <span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span> {{ i18n.ts.youGotNewFollower }}
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span> <div v-if="full">
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span> <MkFollowButton :user="notification.user" :full="true"/>
</div>
</span>
<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">
{{ i18n.ts.followRequestAccepted }}
</span>
<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">
{{ i18n.ts.receiveFollowRequest }}
<div v-if="full && !followRequestDone">
<button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> |
<button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button>
</div>
</span>
<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">
{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b>
<div v-if="full && !groupInviteDone">
<button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> |
<button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button>
</div>
</span>
<span v-if="notification.type === 'app'" class="text"> <span v-if="notification.type === 'app'" class="text">
<Mfm :text="notification.body" :nowrap="!full"/> <Mfm :text="notification.body" :nowrap="!full"/>
</span> </span>