Skip to content

Commit

Permalink
リモートのPlayを遊べるようにする (#447)
Browse files Browse the repository at this point in the history
  • Loading branch information
kozakura913 authored Sep 21, 2024
1 parent 64c50d7 commit 67e077c
Show file tree
Hide file tree
Showing 21 changed files with 534 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_YOJO.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Cherrypick 4.11.1
### General
- Enhance: 連合一覧のソートにリバーシのバージョンを追加
- Enhance: リモートのクリップをお気に入りに登録できるように
- Enhance: リモートのPlayを遊べるように
- Enhance: リモートのPlayをお気に入りに登録できるように

### Client
- Fix: リアクションが閲覧できる状態でも見れない問題を修正 [#429](https://github.com/yojo-art/cherrypick/pull/429)
Expand Down
24 changes: 24 additions & 0 deletions packages/backend/migration/1726452644817-FlashLikeRemote.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project, yojo-art team
* SPDX-License-Identifier: AGPL-3.0-only
*/

export class flashLikeRemote1726452644817 {
name = 'flashLikeRemote1726452644817'

async up(queryRunner) {
await queryRunner.query(`CREATE TABLE "flash_like_remote" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "flashId" character varying(32) NOT NULL, "host" character varying(128) NOT NULL, "authorId" character varying(32) NOT NULL, CONSTRAINT "PK_840a074b84bd1663054e020e43" PRIMARY KEY ("id"))`);
await queryRunner.query(`CREATE INDEX "IDX_ade312aad367a2902ed415abbc" ON "flash_like_remote" ("userId") `);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_f7c8a8fd916efed73a05bc1ea0" ON "flash_like_remote" ("userId", "flashId","host") `);
await queryRunner.query(`ALTER TABLE "flash_like_remote" ADD CONSTRAINT "FK_8c14417c4cc57f04b4d7376707a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "flash_like_remote" ADD CONSTRAINT "FK_75f247337676468f6bd6f22eb24" FOREIGN KEY ("authorId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}

async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "flash_like_remote" DROP CONSTRAINT "FK_75f247337676468f6bd6f22eb24"`);
await queryRunner.query(`ALTER TABLE "flash_like_remote" DROP CONSTRAINT "FK_8c14417c4cc57f04b4d7376707a"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f7c8a8fd916efed73a05bc1ea0"`);
await queryRunner.query(`DROP INDEX "public"."IDX_ade312aad367a2902ed415abbc"`);
await queryRunner.query(`DROP TABLE "flash_like_remote"`);
}
}
6 changes: 6 additions & 0 deletions packages/backend/src/core/CoreModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import { FileInfoService } from './FileInfoService.js';
import { SearchService } from './SearchService.js';
import { AdvancedSearchService } from './AdvancedSearchService.js';
import { ClipService } from './ClipService.js';
import { FlashService } from './FlashService.js';
import { FeaturedService } from './FeaturedService.js';
import { FanoutTimelineService } from './FanoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
Expand Down Expand Up @@ -229,6 +230,7 @@ const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: Fi
const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
const $AdvancedSearchService: Provider = { provide: 'AdvancedSearchService', useExisting: AdvancedSearchService };
const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
const $FlashService: Provider = { provide: 'FlashService', useExisting: FlashService };
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', useExisting: FanoutTimelineService };
const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
Expand Down Expand Up @@ -387,6 +389,7 @@ const $ApGameService: Provider = { provide: 'ApGameService', useExisting: ApGame
SearchService,
AdvancedSearchService,
ClipService,
FlashService,
FeaturedService,
FanoutTimelineService,
FanoutTimelineEndpointService,
Expand Down Expand Up @@ -541,6 +544,7 @@ const $ApGameService: Provider = { provide: 'ApGameService', useExisting: ApGame
$SearchService,
$AdvancedSearchService,
$ClipService,
$FlashService,
$FeaturedService,
$FanoutTimelineService,
$FanoutTimelineEndpointService,
Expand Down Expand Up @@ -696,6 +700,7 @@ const $ApGameService: Provider = { provide: 'ApGameService', useExisting: ApGame
SearchService,
AdvancedSearchService,
ClipService,
FlashService,
FeaturedService,
FanoutTimelineService,
FanoutTimelineEndpointService,
Expand Down Expand Up @@ -849,6 +854,7 @@ const $ApGameService: Provider = { provide: 'ApGameService', useExisting: ApGame
$SearchService,
$AdvancedSearchService,
$ClipService,
$FlashService,
$FeaturedService,
$FanoutTimelineService,
$FanoutTimelineEndpointService,
Expand Down
149 changes: 149 additions & 0 deletions packages/backend/src/core/FlashService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project, yojo-art team
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { Inject, Injectable } from '@nestjs/common';
import got, * as Got from 'got';
import * as Redis from 'ioredis';
import type { Config } from '@/config.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
import { DI } from '@/di-symbols.js';
import type { ClipsRepository, ClipNotesRepository, NotesRepository, MiUser } from '@/models/_.js';
import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js';
import { IdService } from '@/core/IdService.js';
import { Packed } from '@/misc/json-schema.js';
import { emojis } from '@/misc/remote-api-utils.js';

@Injectable()
export class FlashService {
public static FailedToResolveRemoteUserError = class extends Error {};

constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.redisForRemoteApis)
private redisForRemoteApis: Redis.Redis,
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,

@Inject(DI.clipNotesRepository)
private clipNotesRepository: ClipNotesRepository,

@Inject(DI.notesRepository)
private notesRepository: NotesRepository,

private httpRequestService: HttpRequestService,
private userEntityService: UserEntityService,
private remoteUserResolveService: RemoteUserResolveService,
private roleService: RoleService,
private idService: IdService,
) {
}
@bindThis
async showRemoteOrDummy(
flashId: string,
author: MiUser|null,
fetch_emoji = false,
) : Promise<Packed<'Flash'>> {
if (author == null) {
throw new Error();
}
try {
if (author.host == null) {
throw new Error();
}
return await this.showRemote(flashId, author.host, fetch_emoji);
} catch {
return await awaitAll({
id: flashId + '@' + (author.host ? author.host : ''),
createdAt: new Date(0).toISOString(),
updatedAt: new Date(0).toISOString(),
userId: author.id,
user: this.userEntityService.pack(author),
title: 'Unavailable',
summary: '',
script: '',
favoritedCount: 0,
visibility: 'public',
likedCount: 0,
isLiked: false, //後でLike対応する
});
}
}
@bindThis
public async showRemote(
flashId:string,
host:string,
fetch_emoji = false,
) : Promise<Packed<'Flash'>> {
const cache_key = 'flash:show:' + flashId + '@' + host;
const cache_value = await this.redisForRemoteApis.get(cache_key);
let remote_json = null;
if (cache_value === null) {
const timeout = 30 * 1000;
const operationTimeout = 60 * 1000;
const url = 'https://' + host + '/api/flash/show';
const res = got.post(url, {
headers: {
'User-Agent': this.config.userAgent,
'Content-Type': 'application/json; charset=utf-8',
},
timeout: {
lookup: timeout,
connect: timeout,
secureConnect: timeout,
socket: timeout, // read timeout
response: timeout,
send: timeout,
request: operationTimeout, // whole operation timeout
},
agent: {
http: this.httpRequestService.httpAgent,
https: this.httpRequestService.httpsAgent,
},
http2: true,
retry: {
limit: 1,
},
enableUnixSockets: false,
body: JSON.stringify({
flashId,
}),
});
remote_json = await res.text();
const redisPipeline = this.redisForRemoteApis.pipeline();
redisPipeline.set(cache_key, remote_json);
redisPipeline.expire(cache_key, 10 * 60);
await redisPipeline.exec();
} else {
remote_json = cache_value;
}
const remote = JSON.parse(remote_json);
if (remote.user == null || remote.user.username == null) {
throw new FlashService.FailedToResolveRemoteUserError();
}
const user = await this.remoteUserResolveService.resolveUser(remote.user.username, host).catch(err => {
throw new FlashService.FailedToResolveRemoteUserError();
});
return await awaitAll({
id: flashId + '@' + host,
createdAt: remote.createdAt ? new Date(remote.createdAt).toISOString() : new Date(0).toISOString(),
updatedAt: remote.updatedAt ? new Date(remote.updatedAt).toISOString() : new Date(0).toISOString(),
userId: user.id,
user: this.userEntityService.pack(user),
title: String(remote.title),
summary: String(remote.summary),
script: String(remote.script),
favoritedCount: remote.favoritedCount,
visibility: remote.visibility ?? 'public',
likedCount: remote.likedCount ?? 0,
isLiked: false, //後でLike対応する
emojis: (remote.summary && fetch_emoji) ? emojis(this.config, this.httpRequestService, this.redisForRemoteApis, host, remote.summary) : {},
});
}
}
1 change: 1 addition & 0 deletions packages/backend/src/di-symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const DI = {
roleAssignmentsRepository: Symbol('roleAssignmentsRepository'),
flashsRepository: Symbol('flashsRepository'),
flashLikesRepository: Symbol('flashLikesRepository'),
flashLikesRemoteRepository: Symbol('flashLikesRemoteRepository'),
userMemosRepository: Symbol('userMemosRepository'),
bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
reversiGamesRepository: Symbol('reversiGamesRepository'),
Expand Down
43 changes: 43 additions & 0 deletions packages/backend/src/models/FlashLikeRemote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project, yojo-art team
* SPDX-License-Identifier: AGPL-3.0-only
*/

import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { id } from './util/id.js';
import { MiUser } from './User.js';

@Entity('flash_like_remote')
@Index(['userId', 'flashId', 'host'], { unique: true })
export class MiFlashLikeRemote {
@PrimaryColumn(id())
public id: string;

@Index()
@Column(id())
public userId: MiUser['id'];

@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user: MiUser | null;

@Column(id())
public authorId: MiUser['id'];
@ManyToOne(type => MiUser, {
onDelete: 'CASCADE',
})
@JoinColumn()
public author: MiUser | null;

@Column('varchar', {
length: 32,
})
public flashId: string;

@Column('varchar', {
length: 128,
})
public host: string;
}
9 changes: 9 additions & 0 deletions packages/backend/src/models/RepositoryModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
MiEvent,
MiFlash,
MiFlashLike,
MiFlashLikeRemote,
MiFollowing,
MiFollowRequest,
MiGalleryLike,
Expand Down Expand Up @@ -509,6 +510,12 @@ const $flashLikesRepository: Provider = {
inject: [DI.db],
};

const $flashLikesRemoteRepository: Provider = {
provide: DI.flashLikesRemoteRepository,
useFactory: (db: DataSource) => db.getRepository(MiFlashLikeRemote).extend(miRepository as MiRepository<MiFlashLikeRemote>),
inject: [DI.db],
};

const $rolesRepository: Provider = {
provide: DI.rolesRepository,
useFactory: (db: DataSource) => db.getRepository(MiRole).extend(miRepository as MiRepository<MiRole>),
Expand Down Expand Up @@ -626,6 +633,7 @@ const $officialTagRepository: Provider = {
$roleAssignmentsRepository,
$flashsRepository,
$flashLikesRepository,
$flashLikesRemoteRepository,
$userMemosRepository,
$abuseReportResolversRepository,
$bubbleGameRecordsRepository,
Expand Down Expand Up @@ -705,6 +713,7 @@ const $officialTagRepository: Provider = {
$roleAssignmentsRepository,
$flashsRepository,
$flashLikesRepository,
$flashLikesRemoteRepository,
$userMemosRepository,
$abuseReportResolversRepository,
$bubbleGameRecordsRepository,
Expand Down
3 changes: 3 additions & 0 deletions packages/backend/src/models/_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ import { MiRole } from '@/models/Role.js';
import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiFlashLikeRemote } from '@/models/FlashLikeRemote.js';
import { MiUserListFavorite } from '@/models/UserListFavorite.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
Expand Down Expand Up @@ -207,6 +208,7 @@ export {
MiRoleAssignment,
MiFlash,
MiFlashLike,
MiFlashLikeRemote,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
Expand Down Expand Up @@ -286,6 +288,7 @@ export type RolesRepository = Repository<MiRole> & MiRepository<MiRole>;
export type RoleAssignmentsRepository = Repository<MiRoleAssignment> & MiRepository<MiRoleAssignment>;
export type FlashsRepository = Repository<MiFlash> & MiRepository<MiFlash>;
export type FlashLikesRepository = Repository<MiFlashLike> & MiRepository<MiFlashLike>;
export type FlashLikesRemoteRepository = Repository<MiFlashLikeRemote> & MiRepository<MiFlashLikeRemote>;
export type UserMemoRepository = Repository<MiUserMemo> & MiRepository<MiUserMemo>;
export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord> & MiRepository<MiBubbleGameRecord>;
export type ReversiGamesRepository = Repository<MiReversiGame> & MiRepository<MiReversiGame>;
9 changes: 9 additions & 0 deletions packages/backend/src/models/json-schema/flash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,14 @@ export const packedFlashSchema = {
type: 'boolean',
optional: true, nullable: false,
},
emojis: {
type: 'object',
optional: true, nullable: false,
additionalProperties: {
anyOf: [{
type: 'string',
}],
},
},
},
} as const;
2 changes: 2 additions & 0 deletions packages/backend/src/postgres.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { MiRole } from '@/models/Role.js';
import { MiRoleAssignment } from '@/models/RoleAssignment.js';
import { MiFlash } from '@/models/Flash.js';
import { MiFlashLike } from '@/models/FlashLike.js';
import { MiFlashLikeRemote } from '@/models/FlashLikeRemote.js';
import { MiUserMemo } from '@/models/UserMemo.js';
import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
import { MiReversiGame } from '@/models/ReversiGame.js';
Expand Down Expand Up @@ -211,6 +212,7 @@ export const entities = [
MiRoleAssignment,
MiFlash,
MiFlashLike,
MiFlashLikeRemote,
MiUserMemo,
MiBubbleGameRecord,
MiReversiGame,
Expand Down
Loading

0 comments on commit 67e077c

Please sign in to comment.