From 42e436459c995f13ba5fee13e218800623dceef7 Mon Sep 17 00:00:00 2001
From: sn0w <92278018+realsn0w@users.noreply.github.com>
Date: Fri, 3 Jun 2022 14:22:03 +0200
Subject: [PATCH 01/16] fix(client): correctly handle MiAuth URLs with query
string (#8772)
---
packages/client/src/pages/miauth.vue | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index 6e85b784f..4032d7723 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue';
import MkButton from '@/components/ui/button.vue';
import * as os from '@/os';
import { login } from '@/account';
+import { appendQuery, query } from '@/scripts/url';
export default defineComponent({
components: {
@@ -82,7 +83,9 @@ export default defineComponent({
this.state = 'accepted';
if (this.callback) {
- location.href = `${this.callback}?session=${this.session}`;
+ location.href = appendQuery(this.callback, query({
+ session: this.session
+ }));
}
},
deny() {
From dbab6abde4b7eb1748280854f7869e6d29d192bd Mon Sep 17 00:00:00 2001
From: syuilo
Date: Fri, 3 Jun 2022 23:08:15 +0900
Subject: [PATCH 02/16] fix(test): reset redis in e2e test
#7986
---
packages/backend/src/db/postgre.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index e09e93f04..50a85d626 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -73,6 +73,7 @@ import { entities as charts } from '@/services/chart/entities.js';
import { Webhook } from '@/models/entities/webhook.js';
import { envOption } from '../env.js';
import { dbLogger } from './logger.js';
+import { redisClient } from './redis.js';
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
@@ -217,6 +218,7 @@ export async function initDb() {
export async function resetDb() {
const reset = async () => {
+ await redisClient.FLUSHDB();
const tables = await db.query(`SELECT relname AS "table"
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
From 186cac05a299ed5ed86ba3cc4c7c81a8548815cb Mon Sep 17 00:00:00 2001
From: PikaDude
Date: Sat, 4 Jun 2022 00:14:50 +1000
Subject: [PATCH 03/16] User moderation details (#8762)
* add more user details for admins to see
* fix some issues
* small style fix
as suggested by Johann150
Co-authored-by: Johann150
* fix
Co-authored-by: Johann150
Co-authored-by: Johann150
---
.../server/api/endpoints/admin/show-user.ts | 42 ++++++++++++++++---
packages/client/src/pages/user-info.vue | 3 ++
2 files changed, 40 insertions(+), 5 deletions(-)
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index bf6cc1653..78033aed5 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -1,5 +1,5 @@
+import { Signins, UserProfiles, Users } from '@/models/index.js';
import define from '../../define.js';
-import { Users } from '@/models/index.js';
export const meta = {
tags: ['admin'],
@@ -23,9 +23,12 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
- const user = await Users.findOneBy({ id: ps.userId });
+ const [user, profile] = await Promise.all([
+ Users.findOneBy({ id: ps.userId }),
+ UserProfiles.findOneBy({ userId: ps.userId })
+ ]);
- if (user == null) {
+ if (user == null || profile == null) {
throw new Error('user not found');
}
@@ -34,8 +37,37 @@ export default define(meta, paramDef, async (ps, me) => {
throw new Error('cannot show info of admin');
}
+ if (!_me.isAdmin) {
+ return {
+ isModerator: user.isModerator,
+ isSilenced: user.isSilenced,
+ isSuspended: user.isSuspended,
+ };
+ }
+
+ const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken'];
+ Object.keys(profile.integrations).forEach(integration => {
+ maskedKeys.forEach(key => profile.integrations[integration][key] = '');
+ });
+
+ const signins = await Signins.findBy({ userId: user.id });
+
return {
- ...user,
- token: user.token != null ? '' : user.token,
+ email: profile.email,
+ emailVerified: profile.emailVerified,
+ autoAcceptFollowed: profile.autoAcceptFollowed,
+ noCrawle: profile.noCrawle,
+ alwaysMarkNsfw: profile.alwaysMarkNsfw,
+ carefulBot: profile.carefulBot,
+ injectFeaturedNote: profile.injectFeaturedNote,
+ receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
+ integrations: profile.integrations,
+ mutedWords: profile.mutedWords,
+ mutedInstances: profile.mutedInstances,
+ mutingNotificationTypes: profile.mutingNotificationTypes,
+ isModerator: user.isModerator,
+ isSilenced: user.isSilenced,
+ isSuspended: user.isSuspended,
+ signins,
};
});
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 1b2682ed2..54e1f1302 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -54,6 +54,9 @@
{{ $ts.updateRemoteUser }}
+
+
+
From 336eea9d93ef14023765e578919e05f79246a7d4 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Fri, 3 Jun 2022 16:18:44 +0200
Subject: [PATCH 04/16] fix: correctly render empty note text (#8746)
Ensure that the _misskey_content attribute will always exist. Because
the API endpoint does not require the existence of the `text` field,
that field may be `undefined`. By using `?? null` it can be ensured
that the value is at least `null`.
Furthermore, the rendered HTML of a note with empty text will also be
the empty string. From git blame it seems that this behaviour was added
because of a Mastodon bug that might have previously existed. Hoever,
this seems to be no longer the case as I can find mastodon posts that
have empty content.
The code could be made a bit more succinct by using the null coercion
operator.
---
.../backend/src/remote/activitypub/misc/get-note-html.ts | 6 ++----
packages/backend/src/remote/activitypub/renderer/note.ts | 6 +++---
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts
index 3800b4060..389039ebe 100644
--- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts
+++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts
@@ -3,8 +3,6 @@ import { Note } from '@/models/entities/note.js';
import { toHtml } from '../../../mfm/to-html.js';
export default function(note: Note) {
- let html = note.text ? toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)) : null;
- if (html == null) html = '.
';
-
- return html;
+ if (!note.text) return '';
+ return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
}
diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index e8d429e5d..b7df0e9a3 100644
--- a/packages/backend/src/remote/activitypub/renderer/note.ts
+++ b/packages/backend/src/remote/activitypub/renderer/note.ts
@@ -82,15 +82,15 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
const files = await getPromisedFiles(note.fileIds);
- const text = note.text;
+ // text should never be undefined
+ const text = note.text ?? null;
let poll: Poll | null = null;
if (note.hasPoll) {
poll = await Polls.findOneBy({ noteId: note.id });
}
- let apText = text;
- if (apText == null) apText = '';
+ let apText = text ?? '';
if (quote) {
apText += `\n\nRE: ${quote}`;
From 86c04c4489c6f6b69428440462900503bf16ff25 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Sat, 4 Jun 2022 04:29:20 +0200
Subject: [PATCH 05/16] fix: ensure resolver does not fetch local resources via
HTTP(S) (#8733)
* refactor: parseUri types and checks
The type has been refined to better represent what it actually is. Uses of
parseUri are now also checking the parsed object type before resolving.
* cannot resolve URLs with fragments
* also take remaining part of URL into account
Needed for parsing the follows URIs.
* Resolver uses DbResolver for local
* remove unnecessary use of DbResolver
Using DbResolver would mean that the URL is parsed and handled again.
This duplicated processing can be avoided by querying the database directly.
* fix missing property name
---
.../src/remote/activitypub/db-resolver.ts | 101 +++++++++---------
.../src/remote/activitypub/resolver.ts | 64 ++++++++++-
2 files changed, 115 insertions(+), 50 deletions(-)
diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index ef07966e4..a9ed1a4a8 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -13,6 +13,44 @@ import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
const publicKeyCache = new Cache(Infinity);
const publicKeyByUserIdCache = new Cache(Infinity);
+export type UriParseResult = {
+ /** wether the URI was generated by us */
+ local: true;
+ /** id in DB */
+ id: string;
+ /** hint of type, e.g. "notes", "users" */
+ type: string;
+ /** any remaining text after type and id, not including the slash after id. undefined if empty */
+ rest?: string;
+} | {
+ /** wether the URI was generated by us */
+ local: false;
+ /** uri in DB */
+ uri: string;
+};
+
+export function parseUri(url: string) : UriParseResult {
+ const uri = getApId(value);
+
+ // the host part of a URL is case insensitive, so use the 'i' flag.
+ const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
+ const matchLocal = uri.match(localRegex);
+
+ if (matchLocal) {
+ return {
+ local: true,
+ type: matchLocal[1],
+ id: matchLocal[2],
+ rest: matchLocal[3],
+ };
+ } else {
+ return {
+ local: false,
+ uri,
+ };
+ }
+}
+
export default class DbResolver {
constructor() {
}
@@ -21,60 +59,54 @@ export default class DbResolver {
* AP Note => Misskey Note in DB
*/
public async getNoteFromApId(value: string | IObject): Promise {
- const parsed = this.parseUri(value);
+ const parsed = parseUri(value);
+
+ if (parsed.local) {
+ if (parsed.type !== 'notes') return null;
- if (parsed.id) {
return await Notes.findOneBy({
id: parsed.id,
});
- }
-
- if (parsed.uri) {
+ } else {
return await Notes.findOneBy({
uri: parsed.uri,
});
}
-
- return null;
}
public async getMessageFromApId(value: string | IObject): Promise {
- const parsed = this.parseUri(value);
+ const parsed = parseUri(value);
+
+ if (parsed.local) {
+ if (parsed.type !== 'notes') return null;
- if (parsed.id) {
return await MessagingMessages.findOneBy({
id: parsed.id,
});
- }
-
- if (parsed.uri) {
+ } else {
return await MessagingMessages.findOneBy({
uri: parsed.uri,
});
}
-
- return null;
}
/**
* AP Person => Misskey User in DB
*/
public async getUserFromApId(value: string | IObject): Promise {
- const parsed = this.parseUri(value);
+ const parsed = parseUri(value);
+
+ if (parsed.local) {
+ if (parsed.type !== 'users') return null;
- if (parsed.id) {
return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
id: parsed.id,
}).then(x => x ?? undefined)) ?? null;
- }
-
- if (parsed.uri) {
+ } else {
return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
uri: parsed.uri,
}));
}
-
- return null;
}
/**
@@ -120,31 +152,4 @@ export default class DbResolver {
key,
};
}
-
- public parseUri(value: string | IObject): UriParseResult {
- const uri = getApId(value);
-
- const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)');
- const matchLocal = uri.match(localRegex);
-
- if (matchLocal) {
- return {
- type: matchLocal[1],
- id: matchLocal[2],
- };
- } else {
- return {
- uri,
- };
- }
- }
}
-
-type UriParseResult = {
- /** id in DB (local object only) */
- id?: string;
- /** uri in DB (remote object only) */
- uri?: string;
- /** hint of type (local object only, ex: notes, users) */
- type?: string
-};
diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts
index 334eae984..2f9af43c0 100644
--- a/packages/backend/src/remote/activitypub/resolver.ts
+++ b/packages/backend/src/remote/activitypub/resolver.ts
@@ -3,9 +3,18 @@ import { getJson } from '@/misc/fetch.js';
import { ILocalUser } from '@/models/entities/user.js';
import { getInstanceActor } from '@/services/instance-actor.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { extractDbHost } from '@/misc/convert-host.js';
+import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
import { signedGet } from './request.js';
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
+import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js';
+import { parseUri } from './db-resolver.js';
+import renderNote from '@/remote/activitypub/renderer/note.js';
+import { renderLike } from '@/remote/activitypub/renderer/like.js';
+import { renderPerson } from '@/remote/activitypub/renderer/person.js';
+import renderQuestion from '@/remote/activitypub/renderer/question.js';
+import renderCreate from '@/remote/activitypub/renderer/create.js';
+import { renderActivity } from '@/remote/activitypub/renderer/index.js';
+import renderFollow from '@/remote/activitypub/renderer/follow.js';
export default class Resolver {
private history: Set;
@@ -40,14 +49,25 @@ export default class Resolver {
return value;
}
+ if (value.includes('#')) {
+ // URLs with fragment parts cannot be resolved correctly because
+ // the fragment part does not get transmitted over HTTP(S).
+ // Avoid strange behaviour by not trying to resolve these at all.
+ throw new Error(`cannot resolve URL with fragment: ${value}`);
+ }
+
if (this.history.has(value)) {
throw new Error('cannot resolve already resolved one');
}
this.history.add(value);
- const meta = await fetchMeta();
const host = extractDbHost(value);
+ if (isSelfHost(host)) {
+ return await this.resolveLocal(value);
+ }
+
+ const meta = await fetchMeta();
if (meta.blockedHosts.includes(host)) {
throw new Error('Instance is blocked');
}
@@ -70,4 +90,44 @@ export default class Resolver {
return object;
}
+
+ private resolveLocal(url: string): Promise {
+ const parsed = parseUri(url);
+ if (!parsed.local) throw new Error('resolveLocal: not local');
+
+ switch (parsed.type) {
+ case 'notes':
+ return Notes.findOneByOrFail({ id: parsed.id })
+ .then(note => {
+ if (parsed.rest === 'activity') {
+ // this refers to the create activity and not the note itself
+ return renderActivity(renderCreate(renderNote(note)));
+ } else {
+ return renderNote(note);
+ }
+ });
+ case 'users':
+ return Users.findOneByOrFail({ id: parsed.id })
+ .then(user => renderPerson(user as ILocalUser));
+ case 'questions':
+ // Polls are indexed by the note they are attached to.
+ return Promise.all([
+ Notes.findOneByOrFail({ id: parsed.id }),
+ Polls.findOneByOrFail({ noteId: parsed.id }),
+ ])
+ .then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll));
+ case 'likes':
+ return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null })));
+ case 'follows':
+ // rest should be
+ if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
+
+ return Promise.all(
+ [parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id }))
+ )
+ .then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
+ default:
+ throw new Error(`resolveLocal: type ${type} unhandled`);
+ }
+ }
}
From 102012aa9d9909c9a79d1ed0a24071e0966f45ea Mon Sep 17 00:00:00 2001
From: Johann150
Date: Sat, 4 Jun 2022 06:52:42 +0200
Subject: [PATCH 06/16] fix: add id for activitypub follows (#8689)
* add id for activitypub follows
* fix lint
* fix: follower must be local, followee must be remote
Misskey will only use ActivityPub follow requests for users that are local
and are requesting to follow a remote user. This check is to ensure that
this endpoint can not be used by other services or instances.
* fix: missing import
* render block with id
* fix comment
---
.../src/remote/activitypub/renderer/block.ts | 24 +++++++++++----
.../src/remote/activitypub/renderer/follow.ts | 3 +-
packages/backend/src/server/activitypub.ts | 29 ++++++++++++++++++-
.../backend/src/services/blocking/create.ts | 13 ++++++---
.../backend/src/services/blocking/delete.ts | 9 ++++--
5 files changed, 63 insertions(+), 15 deletions(-)
diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts
index 10a4fde51..13815fb76 100644
--- a/packages/backend/src/remote/activitypub/renderer/block.ts
+++ b/packages/backend/src/remote/activitypub/renderer/block.ts
@@ -1,8 +1,20 @@
import config from '@/config/index.js';
-import { ILocalUser, IRemoteUser } from '@/models/entities/user.js';
+import { Blocking } from '@/models/entities/blocking.js';
-export default (blocker: ILocalUser, blockee: IRemoteUser) => ({
- type: 'Block',
- actor: `${config.url}/users/${blocker.id}`,
- object: blockee.uri,
-});
+/**
+ * Renders a block into its ActivityPub representation.
+ *
+ * @param block The block to be rendered. The blockee relation must be loaded.
+ */
+export function renderBlock(block: Blocking) {
+ if (block.blockee?.url == null) {
+ throw new Error('renderBlock: missing blockee uri');
+ }
+
+ return {
+ type: 'Block',
+ id: `${config.url}/blocks/${block.id}`,
+ actor: `${config.url}/users/${block.blockerId}`,
+ object: block.blockee.uri,
+ };
+}
diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts
index 9e9692b77..00fac18ad 100644
--- a/packages/backend/src/remote/activitypub/renderer/follow.ts
+++ b/packages/backend/src/remote/activitypub/renderer/follow.ts
@@ -4,12 +4,11 @@ import { Users } from '@/models/index.js';
export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => {
const follow = {
+ id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow',
actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri,
} as any;
- if (requestId) follow.id = requestId;
-
return follow;
};
diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts
index a48c2d412..cd5f917c4 100644
--- a/packages/backend/src/server/activitypub.ts
+++ b/packages/backend/src/server/activitypub.ts
@@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js';
import { isSelfHost } from '@/misc/convert-host.js';
import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
import { ILocalUser, User } from '@/models/entities/user.js';
-import { In, IsNull } from 'typeorm';
+import { In, IsNull, Not } from 'typeorm';
import { renderLike } from '@/remote/activitypub/renderer/like.js';
import { getUserKeypair } from '@/misc/keypair-store.js';
+import renderFollow from '@/remote/activitypub/renderer/follow.js';
// Init router
const router = new Router();
@@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => {
setResponseType(ctx);
});
+// follow
+router.get('/follows/:follower/:followee', async ctx => {
+ // This may be used before the follow is completed, so we do not
+ // check if the following exists.
+
+ const [follower, followee] = await Promise.all([
+ Users.findOneBy({
+ id: ctx.params.follower,
+ host: IsNull(),
+ }),
+ Users.findOneBy({
+ id: ctx.params.followee,
+ host: Not(IsNull()),
+ }),
+ ]);
+
+ if (follower == null || followee == null) {
+ ctx.status = 404;
+ return;
+ }
+
+ ctx.body = renderActivity(renderFollow(follower, followee));
+ ctx.set('Cache-Control', 'public, max-age=180');
+ setResponseType(ctx);
+});
+
export default router;
diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index b2be78b22..a2c61cca2 100644
--- a/packages/backend/src/services/blocking/create.ts
+++ b/packages/backend/src/services/blocking/create.ts
@@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js';
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
import renderFollow from '@/remote/activitypub/renderer/follow.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js';
-import renderBlock from '@/remote/activitypub/renderer/block.js';
+import { renderBlock } from '@/remote/activitypub/renderer/block.js';
import { deliver } from '@/queue/index.js';
import renderReject from '@/remote/activitypub/renderer/reject.js';
+import { Blocking } from '@/models/entities/blocking.js';
import { User } from '@/models/entities/user.js';
import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js';
import { perUserFollowingChart } from '@/services/chart/index.js';
@@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) {
removeFromList(blockee, blocker),
]);
- await Blockings.insert({
+ const blocking = {
id: genId(),
createdAt: new Date(),
+ blocker,
blockerId: blocker.id,
+ blockee,
blockeeId: blockee.id,
- });
+ } as Blocking;
+
+ await Blockings.insert(blocking);
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
- const content = renderActivity(renderBlock(blocker, blockee));
+ const content = renderActivity(renderBlock(blocking));
deliver(blocker, content, blockee.inbox);
}
}
diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts
index d7b5ddd5f..cb16651bc 100644
--- a/packages/backend/src/services/blocking/delete.ts
+++ b/packages/backend/src/services/blocking/delete.ts
@@ -1,5 +1,5 @@
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
-import renderBlock from '@/remote/activitypub/renderer/block.js';
+import { renderBlock } from '@/remote/activitypub/renderer/block.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js';
import { deliver } from '@/queue/index.js';
import Logger from '../logger.js';
@@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) {
return;
}
+ // Since we already have the blocker and blockee, we do not need to fetch
+ // them in the query above and can just manually insert them here.
+ blocking.blocker = blocker;
+ blocking.blockee = blockee;
+
Blockings.delete(blocking.id);
// deliver if remote bloking
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
- const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker));
+ const content = renderActivity(renderUndo(renderBlock(blocking), blocker));
deliver(blocker, content, blockee.inbox);
}
}
From 170ea0f6c23ddc9b9a8bed5d121aecea0a2785d9 Mon Sep 17 00:00:00 2001
From: Balazs Nadasdi
Date: Sat, 4 Jun 2022 06:57:09 +0200
Subject: [PATCH 07/16] feat: option to collapse long notes (#8561)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* feat: option to collapse long notes
Closes #8559
* do not collapse if cw exists
* use '閉じる' to close / show less.
* make it sticky
* Change style of the Show less button
---
locales/ja-JP.yml | 1 +
packages/client/src/components/note.vue | 27 ++++++++++++++++++++++---
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 9cd1d1eed..57be9bfcb 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -52,6 +52,7 @@ searchUser: "ユーザーを検索"
reply: "返信"
loadMore: "もっと見る"
showMore: "もっと見る"
+showLess: "閉じる"
youGotNewFollower: "フォローされました"
receiveFollowRequest: "フォローリクエストされました"
followRequestAccepted: "フォローが承認されました"
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index bc8a0dd19..4840b0dc2 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -46,7 +46,7 @@
-
+
({{ i18n.ts.private }})
@@ -66,9 +66,12 @@
-
{{ appearNote.channel.name }}
@@ -166,7 +169,8 @@ const reactButton = ref
();
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
-const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
+const collapsed = ref(appearNote.cw == null);
+const isLong = ref(appearNote.cw == null && appearNote.text != null && (
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500)
));
@@ -452,6 +456,23 @@ function readPromo() {
}
> .content {
+ &.isLong {
+ > .showLess {
+ width: 100%;
+ margin-top: 1em;
+ position: sticky;
+ bottom: 1em;
+
+ > span {
+ display: inline-block;
+ background: var(--panel);
+ padding: 6px 10px;
+ font-size: 0.8em;
+ border-radius: 999px;
+ box-shadow: 0 0 7px 7px var(--bg);
+ }
+ }
+ }
&.collapsed {
position: relative;
max-height: 9em;
From 4bf3ed6c1e24980389a86d4f1af07041e217e404 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 14:25:30 +0900
Subject: [PATCH 08/16] fix test
---
packages/backend/test/activitypub.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts
index 5d8b28ec7..f4ae27e5e 100644
--- a/packages/backend/test/activitypub.ts
+++ b/packages/backend/test/activitypub.ts
@@ -2,11 +2,13 @@ process.env.NODE_ENV = 'test';
import * as assert from 'assert';
import rndstr from 'rndstr';
+import { initDb } from '../src/db/postgre.js';
import { initTestDb } from './utils.js';
describe('ActivityPub', () => {
before(async () => {
- await initTestDb();
+ //await initTestDb();
+ await initDb();
});
describe('Parse minimum object', () => {
From 3661d1bc3309e4a57908a0f3cb6896c1e9f02e1f Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 15:15:44 +0900
Subject: [PATCH 09/16] fix bug
---
packages/backend/src/remote/activitypub/db-resolver.ts | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index a9ed1a4a8..1a02f675c 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -5,10 +5,10 @@ import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/
import { UserPublickey } from '@/models/entities/user-publickey.js';
import { MessagingMessage } from '@/models/entities/messaging-message.js';
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
-import { IObject, getApId } from './type.js';
-import { resolvePerson } from './models/person.js';
import { Cache } from '@/misc/cache.js';
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
+import { IObject, getApId } from './type.js';
+import { resolvePerson } from './models/person.js';
const publicKeyCache = new Cache(Infinity);
const publicKeyByUserIdCache = new Cache(Infinity);
@@ -29,7 +29,7 @@ export type UriParseResult = {
uri: string;
};
-export function parseUri(url: string) : UriParseResult {
+export function parseUri(value: string | IObject): UriParseResult {
const uri = getApId(value);
// the host part of a URL is case insensitive, so use the 'i' flag.
From b262e19742b55aff3374dbe67a6a98ca6f652c04 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 15:23:53 +0900
Subject: [PATCH 10/16] Revert "feat: option to collapse long notes (#8561)"
This reverts commit e675ffcf38b07f5c70d00b49c171c7ab3460e810.
---
locales/ja-JP.yml | 1 -
packages/client/src/components/note.vue | 27 +++----------------------
2 files changed, 3 insertions(+), 25 deletions(-)
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 57be9bfcb..9cd1d1eed 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -52,7 +52,6 @@ searchUser: "ユーザーを検索"
reply: "返信"
loadMore: "もっと見る"
showMore: "もっと見る"
-showLess: "閉じる"
youGotNewFollower: "フォローされました"
receiveFollowRequest: "フォローリクエストされました"
followRequestAccepted: "フォローが承認されました"
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 4840b0dc2..bc8a0dd19 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -46,7 +46,7 @@
-
+
({{ i18n.ts.private }})
@@ -66,12 +66,9 @@
-
+
{{ i18n.ts.showMore }}
-
- {{ i18n.ts.showLess }}
-
{{ appearNote.channel.name }}
@@ -169,8 +166,7 @@ const reactButton = ref
();
let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const showContent = ref(false);
-const collapsed = ref(appearNote.cw == null);
-const isLong = ref(appearNote.cw == null && appearNote.text != null && (
+const collapsed = ref(appearNote.cw == null && appearNote.text != null && (
(appearNote.text.split('\n').length > 9) ||
(appearNote.text.length > 500)
));
@@ -456,23 +452,6 @@ function readPromo() {
}
> .content {
- &.isLong {
- > .showLess {
- width: 100%;
- margin-top: 1em;
- position: sticky;
- bottom: 1em;
-
- > span {
- display: inline-block;
- background: var(--panel);
- padding: 6px 10px;
- font-size: 0.8em;
- border-radius: 999px;
- box-shadow: 0 0 7px 7px var(--bg);
- }
- }
- }
&.collapsed {
position: relative;
max-height: 9em;
From 14a0143767122e75b34e8c455e63778969f2dea7 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 15:47:10 +0900
Subject: [PATCH 11/16] Update CHANGELOG.md
---
CHANGELOG.md | 44 +++++++++++++++++++++++++++++---------------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 968759501..299cbd091 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,14 +14,17 @@ You should also include the user name that made the change.
- From this version, Node 18.0.0 or later is required.
### Improvements
-- enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
-- enhance: API: notifications/readは配列でも受け付けるように #7667 @tamaina
-- enhance: プッシュ通知を複数アカウント対応に #7667 @tamaina
-- enhance: プッシュ通知にクリックやactionを設定 #7667 @tamaina
-- replaced webpack with Vite @tamaina
-- update dependencies @syuilo
-- enhance: display URL of QR code for TOTP registration @syuilo
-- enhance: Supports Unicode Emoji 14.0 @mei23
+- Supports Unicode Emoji 14.0 @mei23
+- プッシュ通知を複数アカウント対応に #7667 @tamaina
+- プッシュ通知にクリックやactionを設定 #7667 @tamaina
+- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
+- Server: always remove completed tasks of job queue @Johann150
+- Server: アンテナ、クリップ、リストの表示を速くする @xianonn
+- Client: make emoji stand out more on reaction button @Johann150
+- Client: display URL of QR code for TOTP registration @tamaina
+- API: notifications/readは配列でも受け付けるように #7667 @tamaina
+- API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
+- MFM: Allow speed changes in all animated MFMs @Johann150
- The theme color is now better validated. @Johann150
Your own theme color may be unset if it was in an invalid format.
Admins should check their instance settings if in doubt.
@@ -30,20 +33,31 @@ You should also include the user name that made the change.
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
### Bugfixes
-- Client: fix settings page @tamaina
-- Client: fix profile tabs @futchitwo
+- Server: keep file order of note attachement @Johann150
+- Server: fix caching @Johann150
- Server: await promises when following or unfollowing users @Johann150
-- Client: fix abuse reports page to be able to show all reports @Johann150
-- Federation: Add rel attribute to host-meta @mei23
-- Client: fix profile picture height in mentions @tamaina
-- MFM: more animated functions support `speed` parameter @futchitwo
-- Federation: Fix quote renotes containing no text being federated correctly @Johann150
- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
- Server: fix internal in-memory caching @Johann150
- Server: use correct order of attachments on notes @Johann150
- Server: prevent crash when processing certain PNGs @syuilo
- Server: Fix unable to generate video thumbnails @mei23
- Server: Fix `Cannot find module` issue @mei23
+- Federation: Add rel attribute to host-meta @mei23
+- Federation: add id for activitypub follows @Johann150
+- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
+- Federation: correctly render empty note text @Johann150
+- Federation: Fix quote renotes containing no text being federated correctly @Johann150
+- Federation: remove duplicate br tag/newline @Johann150
+- Federation: add missing authorization checks @Johann150
+- Client: fix profile picture height in mentions @tamaina
+- Client: fix abuse reports page to be able to show all reports @Johann150
+- Client: fix settings page @tamaina
+- Client: fix profile tabs @futchitwo
+- Client: fix popout URL @futchitwo
+- Client: correctly handle MiAuth URLs with query string @sn0w
+- Client: ノート詳細ページの新しいノートを表示する機能の動作が正しくなるように修正する @xianonn
+- MFM: more animated functions support `speed` parameter @futchitwo
+- MFM: limit large MFM @Johann150
## 12.110.1 (2022/04/23)
From db96a8dd8322f5c291a64928e55201b71118642d Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 16:01:11 +0900
Subject: [PATCH 12/16] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 135a3e140..adbdb1cab 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -71,13 +71,15 @@ For now, basically only @syuilo has the authority to merge PRs into develop beca
However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
## Release
-For now, basically only @syuilo has the authority to release Misskey.
-However, in case of emergency, a release can be made at the discretion of a contributor.
-
### Release Instructions
-1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
-2. follow the `master` branch to the `develop` branch.
-3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
+2. Create a release PR.
+ - Into `master` from `develop` branch.
+ - The title must be in the format `Release: x.y.z`.
+ - `x.y.z` is the new version you are trying to release.
+ - Assign about 2~3 reviewers.
+3. The release PR is approved, merge it.
+4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
- The target branch must be `master`
- The tag name must be the version
From 552f488a25946df2c1b73d4d7e6557a6e7eb3438 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 16:01:27 +0900
Subject: [PATCH 13/16] Update CONTRIBUTING.md
---
CONTRIBUTING.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index adbdb1cab..f70e2df00 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,8 +80,8 @@ However, minor fixes, refactoring, and urgent changes may be merged at the discr
- Assign about 2~3 reviewers.
3. The release PR is approved, merge it.
4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
- - The target branch must be `master`
- - The tag name must be the version
+ - The target branch must be `master`
+ - The tag name must be the version
## Localization (l10n)
Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
From e8dea6c79bc7cafc9c4ac20bf5c7aa87141393e7 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 16:04:59 +0900
Subject: [PATCH 14/16] Update CHANGELOG.md
---
CHANGELOG.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 299cbd091..05158278d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,7 +19,6 @@ You should also include the user name that made the change.
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
- Server: always remove completed tasks of job queue @Johann150
-- Server: アンテナ、クリップ、リストの表示を速くする @xianonn
- Client: make emoji stand out more on reaction button @Johann150
- Client: display URL of QR code for TOTP registration @tamaina
- API: notifications/readは配列でも受け付けるように #7667 @tamaina
From c9cd88a9db5a99be61049270a5fd688e6a450187 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 17:24:41 +0900
Subject: [PATCH 15/16] update summaly
---
packages/backend/package.json | 2 +-
packages/backend/yarn.lock | 9 +++++----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 4e0d60b74..32e4cf201 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -101,7 +101,7 @@
"strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0",
"style-loader": "3.3.1",
- "summaly": "2.5.0",
+ "summaly": "2.5.1",
"syslog-pro": "1.0.0",
"systeminformation": "5.11.15",
"tinycolor2": "1.4.2",
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index d131f70e3..303843c34 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -6520,16 +6520,17 @@ style-loader@3.3.1:
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==
-summaly@2.5.0:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea"
- integrity sha512-IzvO2s7yj/PUyH42qWjVjSPpIiPlgTRWGh33t4cIZKOqPQJ2INo7e83hXhHFr4hXTb3JRcIdCuM1ELjlrujiUQ==
+summaly@2.5.1:
+ version "2.5.1"
+ resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.1.tgz#742fe6631987f84ad2e95d2b0f7902ec57e0f6b3"
+ integrity sha512-WWvl7rLs3wm61Xc2JqgTbSuqtIOmGqKte+rkbnxe6ISy4089lQ+7F2ajooQNee6PWHl9kZ27SDd1ZMoL3/6R4A==
dependencies:
cheerio "0.22.0"
debug "4.3.3"
escape-regexp "0.0.1"
got "11.5.1"
html-entities "2.3.2"
+ iconv-lite "0.6.3"
jschardet "3.0.0"
koa "2.13.4"
private-ip "2.3.3"
From 3f4101d17bfc3b7af74e0afec4b3c9cefc213ce0 Mon Sep 17 00:00:00 2001
From: syuilo
Date: Sat, 4 Jun 2022 17:26:56 +0900
Subject: [PATCH 16/16] use node 16
---
.node-version | 2 +-
CHANGELOG.md | 3 ---
packages/backend/src/models/repositories/drive-file.ts | 4 +++-
packages/backend/src/server/web/manifest.ts | 4 +++-
packages/backend/src/services/relay.ts | 8 +++++---
5 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/.node-version b/.node-version
index 658984787..c9b6b29e0 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v18.0.0
+v16.0.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05158278d..c58714fd2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,9 +10,6 @@ You should also include the user name that made the change.
-->
## 12.x.x (unreleased)
-### NOTE
-- From this version, Node 18.0.0 or later is required.
-
### Improvements
- Supports Unicode Emoji 14.0 @mei23
- プッシュ通知を複数アカウント対応に #7667 @tamaina
diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index b626359d9..0d589d4f1 100644
--- a/packages/backend/src/models/repositories/drive-file.ts
+++ b/packages/backend/src/models/repositories/drive-file.ts
@@ -29,7 +29,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
getPublicProperties(file: DriveFile): DriveFile['properties'] {
if (file.properties.orientation != null) {
- const properties = structuredClone(file.properties);
+ // TODO
+ //const properties = structuredClone(file.properties);
+ const properties = JSON.parse(JSON.stringify(file.properties));
if (file.properties.orientation >= 5) {
[properties.width, properties.height] = [properties.height, properties.width];
}
diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts
index 61d766006..ee568b807 100644
--- a/packages/backend/src/server/web/manifest.ts
+++ b/packages/backend/src/server/web/manifest.ts
@@ -3,7 +3,9 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
import manifest from './manifest.json' assert { type: 'json' };
export const manifestHandler = async (ctx: Koa.Context) => {
- const res = structuredClone(manifest);
+ // TODO
+ //const res = structuredClone(manifest);
+ const res = JSON.parse(JSON.stringify(manifest));
const instance = await fetchMeta(true);
diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts
index 08bf72cc2..6bc430443 100644
--- a/packages/backend/src/services/relay.ts
+++ b/packages/backend/src/services/relay.ts
@@ -1,4 +1,4 @@
-import { createSystemUser } from './create-system-user.js';
+import { IsNull } from 'typeorm';
import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js';
import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js';
import renderUndo from '@/remote/activitypub/renderer/undo.js';
@@ -8,7 +8,7 @@ import { Users, Relays } from '@/models/index.js';
import { genId } from '@/misc/gen-id.js';
import { Cache } from '@/misc/cache.js';
import { Relay } from '@/models/entities/relay.js';
-import { IsNull } from 'typeorm';
+import { createSystemUser } from './create-system-user.js';
const ACTOR_USERNAME = 'relay.actor' as const;
@@ -88,7 +88,9 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
}));
if (relays.length === 0) return;
- const copy = structuredClone(activity);
+ // TODO
+ //const copy = structuredClone(activity);
+ const copy = JSON.parse(JSON.stringify(activity));
if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
const signed = await attachLdSignature(copy, user);