Skip to content

Commit

Permalink
Merge branch 'develop' into img-max
Browse files Browse the repository at this point in the history
  • Loading branch information
tamaina committed Apr 12, 2023
2 parents af7d86f + 4c0ef07 commit 4188cc7
Show file tree
Hide file tree
Showing 32 changed files with 1,549 additions and 43 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@
## 13.x.x (unreleased)

### General
- カスタム絵文字関連の変更
- 指定したロールを持つユーザーのノートのみが流れるロールタイムラインを追加
- Deckのカラムとしても追加可能
- カスタム絵文字関連の改善
* ノートなどに含まれるemojis(populateEmojiの結果)は(プロキシされたURLではなく)オリジナルのURLを指すように
* MFMでx3/x4もしくはscale.x/yが2.5以上に指定されていた場合にはオリジナル品質の絵文字を使用するように
- カスタム絵文字でリアクションできないことがある問題を修正

### Client
-

### Server
- Misskey Webでのサーバーサイドエラー画面を改善
- Misskey Webでのサーバーサイドエラーのログが残るように
- ノート作成時のアンテナ追加パフォーマンスを改善
- フォローインポートなどでの大量のフォロー等操作をキューイングするように #10544 @nmkj-io

Expand Down
2 changes: 1 addition & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
});

gulp.task('build:backend:style', () => {
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css'])
.pipe(cssnano({
zindex: false
}))
Expand Down
1 change: 1 addition & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,7 @@ _deck:
channel: "チャンネル"
mentions: "あなた宛て"
direct: "ダイレクト"
roleTimeline: "ロールタイムライン"

_dialog:
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
Expand Down
9 changes: 8 additions & 1 deletion packages/backend/src/core/CustomEmojiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
import { UtilityService } from '@/core/UtilityService.js';
import type { Config } from '@/config.js';
import { query } from '@/misc/prelude/url.js';
import type { Serialized } from '@/server/api/stream/types.js';

@Injectable()
export class CustomEmojiService {
Expand Down Expand Up @@ -44,7 +45,13 @@ export class CustomEmojiService {
memoryCacheLifetime: 1000 * 60 * 3, // 3m
fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
fromRedisConverter: (value) => new Map(JSON.parse(value).map((x: Emoji) => [x.name, x])), // TODO: Date型の変換
fromRedisConverter: (value) => {
if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す)
return new Map(JSON.parse(value).map((x: Serialized<Emoji>) => [x.name, {
...x,
updatedAt: x.updatedAt && new Date(x.updatedAt),
}]));
},
});
}

Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/core/GlobalEventService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import type {
MainStreamTypes,
NoteStreamTypes,
UserListStreamTypes,
RoleTimelineStreamTypes,
} from '@/server/api/stream/types.js';
import type { Packed } from '@/misc/json-schema.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import { bindThis } from '@/decorators.js';
import { Role } from '@/models';

@Injectable()
export class GlobalEventService {
Expand Down Expand Up @@ -81,6 +83,11 @@ export class GlobalEventService {
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
}

@bindThis
public publishRoleTimelineStream<K extends keyof RoleTimelineStreamTypes>(roleId: Role['id'], type: K, value?: RoleTimelineStreamTypes[K]): void {
this.publish(`roleTimelineStream:${roleId}`, type, typeof value === 'undefined' ? null : value);
}

@bindThis
public publishNotesStream(note: Packed<'Note'>): void {
this.publish('notesStream', null, note);
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,8 @@ export class NoteCreateService implements OnApplicationShutdown {

this.globalEventService.publishNotesStream(noteObj);

this.roleService.addNoteToRoleTimeline(noteObj);

this.webhookService.getActiveWebhooks().then(webhooks => {
webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
for (const webhook of webhooks) {
Expand Down
23 changes: 23 additions & 0 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { StreamMessages } from '@/server/api/stream/types.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import type { Packed } from '@/misc/json-schema';
import type { OnApplicationShutdown } from '@nestjs/common';

export type RolePolicies = {
Expand Down Expand Up @@ -64,6 +65,9 @@ export class RoleService implements OnApplicationShutdown {
public static NotAssignedError = class extends Error {};

constructor(
@Inject(DI.redis)
private redisClient: Redis.Redis,

@Inject(DI.redisForSub)
private redisForSub: Redis.Redis,

Expand Down Expand Up @@ -398,6 +402,25 @@ export class RoleService implements OnApplicationShutdown {
this.globalEventService.publishInternalEvent('userRoleUnassigned', existing);
}

@bindThis
public async addNoteToRoleTimeline(note: Packed<'Note'>): Promise<void> {
const roles = await this.getUserRoles(note.userId);

const redisPipeline = this.redisClient.pipeline();

for (const role of roles) {
redisPipeline.xadd(
`roleTimeline:${role.id}`,
'MAXLEN', '~', '1000',
'*',
'note', note.id);

this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
}

redisPipeline.exec();
}

@bindThis
public onApplicationShutdown(signal?: string | undefined) {
this.redisForSub.off('message', this.onMessage);
Expand Down
6 changes: 5 additions & 1 deletion packages/backend/src/core/UserFollowingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { MetaService } from '@/core/MetaService.js';
import { CacheService } from '@/core/CacheService.js';
import type { Config } from '@/config.js';
import Logger from '../logger.js';

const logger = new Logger('following/create');
Expand All @@ -44,6 +45,9 @@ export class UserFollowingService implements OnModuleInit {
constructor(
private moduleRef: ModuleRef,

@Inject(DI.config)
private config: Config,

@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

Expand Down Expand Up @@ -411,7 +415,7 @@ export class UserFollowingService implements OnModuleInit {
}

if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee));
const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, requestId ?? `${this.config.url}/follows/${followRequest.id}`));
this.queueService.deliver(follower, content, followee.inbox, false);
}
}
Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/misc/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class RedisKVCache<T> {
private memoryCache: MemoryKVCache<T>;
private fetcher: (key: string) => Promise<T>;
private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T;
private fromRedisConverter: (value: string) => T | undefined;

constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: {
lifetime: RedisKVCache<T>['lifetime'];
Expand Down Expand Up @@ -92,7 +92,7 @@ export class RedisSingleCache<T> {
private memoryCache: MemorySingleCache<T>;
private fetcher: () => Promise<T>;
private toRedisConverter: (value: T) => string;
private fromRedisConverter: (value: string) => T;
private fromRedisConverter: (value: string) => T | undefined;

constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: {
lifetime: RedisSingleCache<T>['lifetime'];
Expand Down
88 changes: 63 additions & 25 deletions packages/backend/src/server/ActivityPubServerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Brackets, In, IsNull, LessThan, Not } from 'typeorm';
import accepts from 'accepts';
import vary from 'vary';
import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository } from '@/models/index.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/index.js';
import * as url from '@/misc/prelude/url.js';
import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
Expand Down Expand Up @@ -54,6 +54,9 @@ export class ActivityPubServerService {
@Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository,

@Inject(DI.followRequestsRepository)
private followRequestsRepository: FollowRequestsRepository,

private utilityService: UtilityService,
private userEntityService: UserEntityService,
private apRendererService: ApRendererService,
Expand Down Expand Up @@ -205,22 +208,22 @@ export class ActivityPubServerService {
reply.code(400);
return;
}

const page = request.query.page === 'true';

const user = await this.usersRepository.findOneBy({
id: userId,
host: IsNull(),
});

if (user == null) {
reply.code(404);
return;
}

//#region Check ff visibility
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });

if (profile.ffVisibility === 'private') {
reply.code(403);
reply.header('Cache-Control', 'public, max-age=30');
Expand All @@ -231,31 +234,31 @@ export class ActivityPubServerService {
return;
}
//#endregion

const limit = 10;
const partOf = `${this.config.url}/users/${userId}/following`;

if (page) {
const query = {
followerId: user.id,
} as FindOptionsWhere<Following>;

// カーソルが指定されている場合
if (cursor) {
query.id = LessThan(cursor);
}

// Get followings
const followings = await this.followingsRepository.find({
where: query,
take: limit + 1,
order: { id: -1 },
});

// 「次のページ」があるかどうか
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();

const renderedFollowees = await Promise.all(followings.map(following => this.apRendererService.renderFollowUser(following.followeeId)));
const rendered = this.apRendererService.renderOrderedCollectionPage(
`${partOf}?${url.query({
Expand All @@ -269,7 +272,7 @@ export class ActivityPubServerService {
cursor: followings[followings.length - 1].id,
})}` : undefined,
);

this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
} else {
Expand Down Expand Up @@ -330,33 +333,33 @@ export class ActivityPubServerService {
reply.code(400);
return;
}

const untilId = request.query.until_id;
if (untilId != null && typeof untilId !== 'string') {
reply.code(400);
return;
}

const page = request.query.page === 'true';

if (countIf(x => x != null, [sinceId, untilId]) > 1) {
reply.code(400);
return;
}

const user = await this.usersRepository.findOneBy({
id: userId,
host: IsNull(),
});

if (user == null) {
reply.code(404);
return;
}

const limit = 20;
const partOf = `${this.config.url}/users/${userId}/outbox`;

if (page) {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
.andWhere('note.userId = :userId', { userId: user.id })
Expand All @@ -365,11 +368,11 @@ export class ActivityPubServerService {
.orWhere('note.visibility = \'home\'');
}))
.andWhere('note.localOnly = FALSE');

const notes = await query.take(limit).getMany();

if (sinceId) notes.reverse();

const activities = await Promise.all(notes.map(note => this.packActivity(note)));
const rendered = this.apRendererService.renderOrderedCollectionPage(
`${partOf}?${url.query({
Expand All @@ -387,7 +390,7 @@ export class ActivityPubServerService {
until_id: notes[notes.length - 1].id,
})}` : undefined,
);

this.setResponseType(request, reply);
return (this.apRendererService.addContext(rendered));
} else {
Expand Down Expand Up @@ -457,7 +460,7 @@ export class ActivityPubServerService {
// note
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept');

const note = await this.notesRepository.findOneBy({
id: request.params.note,
visibility: In(['public', 'home']),
Expand Down Expand Up @@ -639,6 +642,41 @@ export class ActivityPubServerService {
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
});

// follow
fastify.get<{ Params: { followRequestId: string ; } }>('/follows/:followRequestId', async (request, reply) => {
// This may be used before the follow is completed, so we do not
// check if the following exists and only check if the follow request exists.

const followRequest = await this.followRequestsRepository.findOneBy({
id: request.params.followRequestId,
});

if (followRequest == null) {
reply.code(404);
return;
}

const [follower, followee] = await Promise.all([
this.usersRepository.findOneBy({
id: followRequest.followerId,
host: IsNull(),
}),
this.usersRepository.findOneBy({
id: followRequest.followeeId,
host: Not(IsNull()),
}),
]);

if (follower == null || followee == null) {
reply.code(404);
return;
}

reply.header('Cache-Control', 'public, max-age=180');
this.setResponseType(request, reply);
return (this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee)));
});

done();
}
}
Loading

0 comments on commit 4188cc7

Please sign in to comment.