Merge remote-tracking branch 'misskey-dev/develop' into io
This commit is contained in:
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import * as Reversi from 'misskey-reversi';
|
||||
import { LessThan, MoreThan } from 'typeorm';
|
||||
import type {
|
||||
MiReversiGame,
|
||||
ReversiGamesRepository,
|
||||
@@ -23,7 +24,7 @@ import { Serialized } from '@/types.js';
|
||||
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
||||
const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
|
||||
const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
|
||||
|
||||
@Injectable()
|
||||
export class ReversiService implements OnModuleInit {
|
||||
@@ -84,44 +85,82 @@ export class ReversiService implements OnModuleInit {
|
||||
map: game.map,
|
||||
bw: game.bw,
|
||||
crc32: game.crc32,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
} satisfies Partial<MiReversiGame>;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async matchSpecificUser(me: MiUser, targetUser: MiUser): Promise<MiReversiGame | null> {
|
||||
public async matchSpecificUser(me: MiUser, targetUser: MiUser, multiple = false): Promise<MiReversiGame | null> {
|
||||
if (targetUser.id === me.id) {
|
||||
throw new Error('You cannot match yourself.');
|
||||
}
|
||||
|
||||
if (!multiple) {
|
||||
// 既にマッチしている対局が無いか探す(3分以内)
|
||||
const games = await this.reversiGamesRepository.find({
|
||||
where: [
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false },
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false },
|
||||
],
|
||||
relations: ['user1', 'user2'],
|
||||
order: { id: 'DESC' },
|
||||
});
|
||||
if (games.length > 0) {
|
||||
return games[0];
|
||||
}
|
||||
}
|
||||
|
||||
//#region 相手から既に招待されてないか確認
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${me.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
|
||||
if (invitations.includes(targetUser.id)) {
|
||||
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
|
||||
|
||||
const game = await this.matched(targetUser.id, me.id);
|
||||
|
||||
return game;
|
||||
} else {
|
||||
this.redisClient.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
|
||||
|
||||
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
|
||||
user: await this.userEntityService.pack(me, targetUser),
|
||||
const game = await this.matched(targetUser.id, me.id, {
|
||||
noIrregularRules: false,
|
||||
});
|
||||
|
||||
return null;
|
||||
return game;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
|
||||
redisPipeline.expire(`reversi:matchSpecific:${targetUser.id}`, 120, 'NX');
|
||||
await redisPipeline.exec();
|
||||
|
||||
this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
|
||||
user: await this.userEntityService.pack(me, targetUser),
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async matchAnyUser(me: MiUser): Promise<MiReversiGame | null> {
|
||||
public async matchAnyUser(me: MiUser, options: { noIrregularRules: boolean }, multiple = false): Promise<MiReversiGame | null> {
|
||||
if (!multiple) {
|
||||
// 既にマッチしている対局が無いか探す(3分以内)
|
||||
const games = await this.reversiGamesRepository.find({
|
||||
where: [
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false },
|
||||
{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false },
|
||||
],
|
||||
relations: ['user1', 'user2'],
|
||||
order: { id: 'DESC' },
|
||||
});
|
||||
if (games.length > 0) {
|
||||
return games[0];
|
||||
}
|
||||
}
|
||||
|
||||
//#region まず自分宛ての招待を探す
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${me.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
|
||||
@@ -129,7 +168,9 @@ export class ReversiService implements OnModuleInit {
|
||||
const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
|
||||
await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
|
||||
|
||||
const game = await this.matched(invitorId, me.id);
|
||||
const game = await this.matched(invitorId, me.id, {
|
||||
noIrregularRules: false,
|
||||
});
|
||||
|
||||
return game;
|
||||
}
|
||||
@@ -137,23 +178,35 @@ export class ReversiService implements OnModuleInit {
|
||||
|
||||
const matchings = await this.redisClient.zrange(
|
||||
'reversi:matchAny',
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
0,
|
||||
2, // 自分自身のIDが入っている場合もあるので2つ取得
|
||||
'REV');
|
||||
|
||||
const userIds = matchings.filter(id => id !== me.id);
|
||||
const items = matchings.filter(id => !id.startsWith(me.id));
|
||||
|
||||
if (userIds.length > 0) {
|
||||
// pick random
|
||||
const matchedUserId = userIds[Math.floor(Math.random() * userIds.length)];
|
||||
if (items.length > 0) {
|
||||
const [matchedUserId, option] = items[0].split(':');
|
||||
|
||||
await this.redisClient.zrem('reversi:matchAny', me.id, matchedUserId);
|
||||
await this.redisClient.zrem('reversi:matchAny',
|
||||
me.id,
|
||||
matchedUserId,
|
||||
me.id + ':noIrregularRules',
|
||||
matchedUserId + ':noIrregularRules');
|
||||
|
||||
const game = await this.matched(matchedUserId, me.id);
|
||||
const game = await this.matched(matchedUserId, me.id, {
|
||||
noIrregularRules: options.noIrregularRules || option === 'noIrregularRules',
|
||||
});
|
||||
|
||||
return game;
|
||||
} else {
|
||||
await this.redisClient.zadd('reversi:matchAny', Date.now(), me.id);
|
||||
const redisPipeline = this.redisClient.pipeline();
|
||||
if (options.noIrregularRules) {
|
||||
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules');
|
||||
} else {
|
||||
redisPipeline.zadd('reversi:matchAny', Date.now(), me.id);
|
||||
}
|
||||
redisPipeline.expire('reversi:matchAny', 15, 'NX');
|
||||
await redisPipeline.exec();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -165,7 +218,15 @@ export class ReversiService implements OnModuleInit {
|
||||
|
||||
@bindThis
|
||||
public async matchAnyUserCancel(user: MiUser) {
|
||||
await this.redisClient.zrem('reversi:matchAny', user.id);
|
||||
await this.redisClient.zrem('reversi:matchAny', user.id, user.id + ':noIrregularRules');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async cleanOutdatedGames() {
|
||||
await this.reversiGamesRepository.delete({
|
||||
id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)),
|
||||
isStarted: false,
|
||||
});
|
||||
}
|
||||
|
||||
@bindThis
|
||||
@@ -219,7 +280,7 @@ export class ReversiService implements OnModuleInit {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async matched(parentId: MiUser['id'], childId: MiUser['id']): Promise<MiReversiGame> {
|
||||
private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
|
||||
const game = await this.reversiGamesRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
user1Id: parentId,
|
||||
@@ -232,6 +293,7 @@ export class ReversiService implements OnModuleInit {
|
||||
map: Reversi.maps.eighteight.data,
|
||||
bw: 'random',
|
||||
isLlotheo: false,
|
||||
noIrregularRules: options.noIrregularRules,
|
||||
}).then(x => this.reversiGamesRepository.findOneOrFail({
|
||||
where: { id: x.identifiers[0].id },
|
||||
relations: ['user1', 'user2'],
|
||||
@@ -333,7 +395,7 @@ export class ReversiService implements OnModuleInit {
|
||||
public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
|
||||
const invitations = await this.redisClient.zrange(
|
||||
`reversi:matchSpecific:${user.id}`,
|
||||
Date.now() - MATCHING_TIMEOUT_MS,
|
||||
Date.now() - INVITATION_TIMEOUT_MS,
|
||||
'+inf',
|
||||
'BYSCORE');
|
||||
return invitations;
|
||||
|
@@ -60,6 +60,7 @@ export class ReversiGameEntityService {
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
timeLimitForEachTurn: game.timeLimitForEachTurn,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
logs: game.logs,
|
||||
map: game.map,
|
||||
});
|
||||
@@ -108,6 +109,7 @@ export class ReversiGameEntityService {
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
timeLimitForEachTurn: game.timeLimitForEachTurn,
|
||||
noIrregularRules: game.noIrregularRules,
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -113,6 +113,11 @@ export class MiReversiGame {
|
||||
})
|
||||
public bw: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public noIrregularRules: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
@@ -82,6 +82,10 @@ export const packedReversiGameLiteSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noIrregularRules: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLlotheo: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
@@ -196,6 +200,10 @@ export const packedReversiGameDetailedSchema = {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
noIrregularRules: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
isLlotheo: {
|
||||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
|
@@ -11,6 +11,7 @@ import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { ReversiService } from '@/core/ReversiService.js';
|
||||
import { QueueLoggerService } from '../QueueLoggerService.js';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
||||
@@ -32,6 +33,7 @@ export class CleanProcessorService {
|
||||
private roleAssignmentsRepository: RoleAssignmentsRepository,
|
||||
|
||||
private queueLoggerService: QueueLoggerService,
|
||||
private reversiService: ReversiService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
this.logger = this.queueLoggerService.logger.createSubLogger('clean');
|
||||
@@ -65,6 +67,8 @@ export class CleanProcessorService {
|
||||
});
|
||||
}
|
||||
|
||||
this.reversiService.cleanOutdatedGames();
|
||||
|
||||
this.logger.succ('Cleaned.');
|
||||
}
|
||||
}
|
||||
|
@@ -43,7 +43,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
|
||||
.andWhere('game.isStarted = TRUE')
|
||||
.innerJoinAndSelect('game.user1', 'user1')
|
||||
.innerJoinAndSelect('game.user2', 'user2');
|
||||
|
||||
@@ -53,6 +52,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
.where('game.user1Id = :userId', { userId: me.id })
|
||||
.orWhere('game.user2Id = :userId', { userId: me.id });
|
||||
}));
|
||||
} else {
|
||||
query.andWhere('game.isStarted = TRUE');
|
||||
}
|
||||
|
||||
const games = await query.take(ps.limit).getMany();
|
||||
|
@@ -37,6 +37,8 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
noIrregularRules: { type: 'boolean', default: false },
|
||||
multiple: { type: 'boolean', default: false },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
@@ -56,7 +58,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw err;
|
||||
}) : null;
|
||||
|
||||
const game = target ? await this.reversiService.matchSpecificUser(me, target) : await this.reversiService.matchAnyUser(me);
|
||||
const game = target
|
||||
? await this.reversiService.matchSpecificUser(me, target, ps.multiple)
|
||||
: await this.reversiService.matchAnyUser(me, { noIrregularRules: ps.noIrregularRules }, ps.multiple);
|
||||
|
||||
if (game == null) return;
|
||||
|
||||
|
Reference in New Issue
Block a user