From df3bbfb4161ac53bdff8fbc5c78c4e1c8f5173f8 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 6e85b784ff..4032d7723e 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 a3fed7d0fbb551e7cf42b648a841f7d78f7d5659 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 e09e93f04e..50a85d6267 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 6061937996ec90c4d59cab5d199760bc20ffae6e 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 bf6cc16532..78033aed58 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 1b2682ed29..54e1f13021 100644 --- a/packages/client/src/pages/user-info.vue +++ b/packages/client/src/pages/user-info.vue @@ -54,6 +54,9 @@ {{ $ts.updateRemoteUser }} + + + From 81109b14b585b2ca6ba85ebedcb41f9b8cca5382 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 3800b40608..389039ebed 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 e8d429e5de..b7df0e9a39 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 9954c054a7e9fa8148345c7ff3d0bfaaee05fcb1 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 ef07966e42..a9ed1a4a8d 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 334eae9843..2f9af43c0c 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 32dff2846003bb079891593b660869511fca5f01 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 10a4fde517..13815fb76f 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 9e9692b77a..00fac18ad5 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 a48c2d4122..cd5f917c40 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 b2be78b220..a2c61cca22 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 d7b5ddd5ff..cb16651bc0 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 e675ffcf38b07f5c70d00b49c171c7ab3460e810 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 9cd1d1eedb..57be9bfcbb 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 bc8a0dd19d..4840b0dc2a 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 702edfd3d3424a666091dd29dd5b4a9beaa822db 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 5d8b28ec7a..f4ae27e5ec 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 11afdf7e24549b66c000f4e1e598d82d92bc9f7e 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 a9ed1a4a8d..1a02f675ca 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 71150f21cd91df7bdd78a8f708db092418e85baa 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 57be9bfcbb..9cd1d1eedb 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 4840b0dc2a..bc8a0dd19d 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 @@
- -
{{ 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 bb3c6785c93084da5f56372ff6e5235877210620 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 9687595010..299cbd0919 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 0946d5091329815f49da3973f6925adad137777b 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 135a3e140c..adbdb1cab0 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 b62a050b2ccbb886ee75aac4bf9f6cb0af5c46be 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 adbdb1cab0..f70e2df001 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 7aae9987d5043f037a099c7b0e67c1152767ebfb 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 299cbd0919..05158278d0 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 abcd5bc9515b47a579d1cc88003108418febb33b 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 4e0d60b74e..32e4cf201b 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 d131f70e38..303843c346 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 89419c05b27d5419af75b3759bf62e2c4c3a29c3 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 658984787f..c9b6b29e00 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v18.0.0 +v16.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 05158278d0..c58714fd25 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 b626359d98..0d589d4f11 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 61d7660066..ee568b8077 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 08bf72cc26..6bc4304436 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);