Merge branch 'develop' into feature/default-post-target-detect-from-path
This commit is contained in:
		
						commit
						986b17a3ae
					
				|  | @ -9595,6 +9595,26 @@ export interface Locale extends ILocale { | |||
|          * 対局がキャンセルされました | ||||
|          */ | ||||
|         "gameCanceled": string; | ||||
|         /** | ||||
|          * 開始時に対局をタイムラインに投稿 | ||||
|          */ | ||||
|         "shareToTlTheGameWhenStart": string; | ||||
|         /** | ||||
|          * 対局を開始しました! #MisskeyReversi | ||||
|          */ | ||||
|         "iStartedAGame": string; | ||||
|         /** | ||||
|          * 相手が設定を変更しました | ||||
|          */ | ||||
|         "opponentHasSettingsChanged": string; | ||||
|         /** | ||||
|          * 変則許可 (完全フリー) | ||||
|          */ | ||||
|         "allowIrregularRules": string; | ||||
|         /** | ||||
|          * 変則なし | ||||
|          */ | ||||
|         "disallowIrregularRules": string; | ||||
|     }; | ||||
|     "_offlineScreen": { | ||||
|         /** | ||||
|  |  | |||
|  | @ -2556,6 +2556,11 @@ _reversi: | |||
|   freeMatch: "フリーマッチ" | ||||
|   lookingForPlayer: "対戦相手を探しています" | ||||
|   gameCanceled: "対局がキャンセルされました" | ||||
|   shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿" | ||||
|   iStartedAGame: "対局を開始しました! #MisskeyReversi" | ||||
|   opponentHasSettingsChanged: "相手が設定を変更しました" | ||||
|   allowIrregularRules: "変則許可 (完全フリー)" | ||||
|   disallowIrregularRules: "変則なし" | ||||
| 
 | ||||
| _offlineScreen: | ||||
|   title: "オフライン - サーバーに接続できません" | ||||
|  |  | |||
|  | @ -380,8 +380,11 @@ hcaptcha: "hCaptcha(キャプチャ)" | |||
| enableHcaptcha: "hCaptcha(キャプチャ)をつけとく" | ||||
| hcaptchaSiteKey: "サイトキー" | ||||
| hcaptchaSecretKey: "シークレットキー" | ||||
| mcaptcha: "mCaptcha" | ||||
| enableMcaptcha: "hCaptcha(キャプチャ)をつけとく" | ||||
| mcaptchaSiteKey: "サイトキー" | ||||
| mcaptchaSecretKey: "シークレットキー" | ||||
| mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL" | ||||
| recaptcha: "reCAPTCHA" | ||||
| enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする" | ||||
| recaptchaSiteKey: "サイトキー" | ||||
|  | @ -629,6 +632,7 @@ medium: "中" | |||
| small: "小" | ||||
| generateAccessToken: "アクセストークンの発行" | ||||
| permission: "権限" | ||||
| adminPermission: "管理者権限" | ||||
| enableAll: "全部使えるようにする" | ||||
| disableAll: "全部使えへんようにする" | ||||
| tokenRequested: "アカウントへのアクセス許してやったらどうや" | ||||
|  | @ -1055,6 +1059,8 @@ limitWidthOfReaction: "ツッコミの最大横幅を制限して、ちっさく | |||
| noteIdOrUrl: "ノートIDかURL" | ||||
| video: "動画" | ||||
| videos: "動画" | ||||
| audio: "音声" | ||||
| audioFiles: "音声" | ||||
| dataSaver: "データケチケチ" | ||||
| accountMigration: "アカウントのお引っ越し" | ||||
| accountMoved: "このユーザーはさらのアカウントに引っ越したで:" | ||||
|  | @ -1187,7 +1193,25 @@ seasonalScreenEffect: "季節にあった画面の動き" | |||
| decorate: "デコる" | ||||
| addMfmFunction: "装飾つける" | ||||
| enableQuickAddMfmFunction: "ややこしいMFMのピッカーを出す" | ||||
| bubbleGame: "バブルゲーム" | ||||
| sfx: "効果音" | ||||
| soundWillBePlayed: "サウンドが再生されるで" | ||||
| showReplay: "リプレイ見る" | ||||
| replay: "リプレイ" | ||||
| replaying: "リプレイ中" | ||||
| ranking: "ランキング" | ||||
| lastNDays: "直近{n}日" | ||||
| backToTitle: "タイトルへ" | ||||
| hemisphere: "住んでる地域" | ||||
| withSensitive: "センシティブなファイルを含むノートを表示" | ||||
| userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿" | ||||
| enableHorizontalSwipe: "スワイプしてタブを切り替える" | ||||
| _bubbleGame: | ||||
|   howToPlay: "遊び方" | ||||
|   _howToPlay: | ||||
|     section1: "位置を調整してハコにモノを落とすで。" | ||||
|     section2: "同じもんがくっついたら別のやつになって、スコアがもらえるで。" | ||||
|     section3: "モノがハコからあふれたらゲームオーバーや。ハコからあふれんようにしながらモノを融合させてハイスコアを目指しいや!" | ||||
| _announcement: | ||||
|   forExistingUsers: "もうおるユーザーのみ" | ||||
|   forExistingUsersDescription: "オンにしたらこのお知らせができた時点でおる人らにだけお知らせが行くで。切ったらこの知らせが行ったあとにアカウント作った人にもちゃんとお知らせが行くで。" | ||||
|  | @ -1558,6 +1582,13 @@ _achievements: | |||
|     _tutorialCompleted: | ||||
|       title: "Misskeyひよっこ講座 修了証" | ||||
|       description: "チュートリアル全部やった" | ||||
|     _bubbleGameExplodingHead: | ||||
|       title: "🤯" | ||||
|       description: "バブルゲームで最も大きいモノを出した" | ||||
|     _bubbleGameDoubleExplodingHead: | ||||
|       title: "ダブル🤯" | ||||
|       description: "バブルゲームで最も大きいモノを2つ同時に出した" | ||||
|       flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて" | ||||
| _role: | ||||
|   new: "ロールの作成" | ||||
|   edit: "ロールの編集" | ||||
|  | @ -2410,6 +2441,51 @@ _dataSaver: | |||
|   _code: | ||||
|     title: "コードハイライト" | ||||
|     description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。" | ||||
| _hemisphere: | ||||
|   N: "北半球" | ||||
|   S: "南半球" | ||||
|   caption: "一部のクライアント設定で、季節を判定するのに使用するで。" | ||||
| _reversi: | ||||
|   reversi: "リバーシ" | ||||
|   gameSettings: "対局の設定" | ||||
|   chooseBoard: "ボードを選択" | ||||
|   blackOrWhite: "先行/後攻" | ||||
|   blackIs: "{name}が黒(先行)" | ||||
|   rules: "ルール" | ||||
|   thisGameIsStartedSoon: "対局、そろそろ開始されるで。" | ||||
|   waitingForOther: "相手の準備が完了するのを待ってんで。" | ||||
|   waitingForMe: "あんさんの準備が完了すんのを待ってんで" | ||||
|   waitingBoth: "準備してなー" | ||||
|   ready: "準備完了" | ||||
|   cancelReady: "準備を再開" | ||||
|   opponentTurn: "相手のターンやで" | ||||
|   myTurn: "あんさんのターンや" | ||||
|   turnOf: "{name}のターンやで" | ||||
|   pastTurnOf: "{name}のターン" | ||||
|   surrender: "投了" | ||||
|   surrendered: "投了により" | ||||
|   timeout: "時間切れ" | ||||
|   drawn: "引き分け" | ||||
|   won: "{name}の勝ち" | ||||
|   black: "黒" | ||||
|   white: "白" | ||||
|   total: "合計" | ||||
|   turnCount: "{count}ターン目" | ||||
|   myGames: "自分の対局" | ||||
|   allGames: "みんなの対局" | ||||
|   ended: "終了" | ||||
|   playing: "対局中" | ||||
|   isLlotheo: "石の少ない方が勝ち(ロセオ)" | ||||
|   loopedMap: "ループマップ" | ||||
|   canPutEverywhere: "どこでも置けるモード" | ||||
|   timeLimitForEachTurn: "1ターンの時間制限" | ||||
|   freeMatch: "フリーマッチ" | ||||
|   lookingForPlayer: "対戦相手を探してるで" | ||||
|   gameCanceled: "対局がキャンセルされたわ" | ||||
|   shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで" | ||||
|   iStartedAGame: "対局し始めたで! #MisskeyReversi" | ||||
|   opponentHasSettingsChanged: "相手が設定変えたで" | ||||
| _offlineScreen: | ||||
|   title: "オフライン - サーバーに接続できひんで" | ||||
|   header: "サーバーに接続できへんわ" | ||||
| 
 | ||||
|  |  | |||
|  | @ -380,9 +380,11 @@ hcaptcha: "hCaptcha" | |||
| enableHcaptcha: "hCaptcha 활성화" | ||||
| hcaptchaSiteKey: "사이트 키" | ||||
| hcaptchaSecretKey: "시크릿 키" | ||||
| mcaptcha: "mCaptcha" | ||||
| enableMcaptcha: "mCaptcha 활성화" | ||||
| mcaptchaSiteKey: "사이트 키" | ||||
| mcaptchaSecretKey: "시크릿 키" | ||||
| mcaptchaInstanceUrl: "mCaptcha 인스턴스 URL" | ||||
| recaptcha: "reCAPTCHA" | ||||
| enableRecaptcha: "reCAPTCHA 활성화" | ||||
| recaptchaSiteKey: "사이트 키" | ||||
|  | @ -630,6 +632,7 @@ medium: "보통" | |||
| small: "작게" | ||||
| generateAccessToken: "액세스 토큰 생성" | ||||
| permission: "권한" | ||||
| adminPermission: "관리자 권한" | ||||
| enableAll: "전체 선택" | ||||
| disableAll: "전체 해제" | ||||
| tokenRequested: "계정 접근 허용" | ||||
|  | @ -673,6 +676,7 @@ useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용됩니 | |||
| other: "기타" | ||||
| regenerateLoginToken: "로그인 토큰을 재생성" | ||||
| regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다." | ||||
| theKeywordWhenSearchingForCustomEmoji: "맞춤 이모티콘을 검색할 때 키워드가 됩니다." | ||||
| setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다." | ||||
| fileIdOrUrl: "파일 ID 또는 URL" | ||||
| behavior: "동작" | ||||
|  | @ -1055,6 +1059,8 @@ limitWidthOfReaction: "리액션의 최대 폭을 제한하고 작게 표시하 | |||
| noteIdOrUrl: "노트 ID 및 URL" | ||||
| video: "동영상" | ||||
| videos: "동영상" | ||||
| audio: "소리" | ||||
| audioFiles: "소리" | ||||
| dataSaver: "데이터 절약 모드" | ||||
| accountMigration: "계정 이동" | ||||
| accountMoved: "이 사용자는 다음 계정으로 이사했습니다:" | ||||
|  | @ -1187,10 +1193,25 @@ seasonalScreenEffect: "계절에 따른 효과 보이기" | |||
| decorate: "장식하기" | ||||
| addMfmFunction: "장식 추가하기" | ||||
| enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기" | ||||
| bubbleGame: "버블 게임" | ||||
| sfx: "효과음" | ||||
| soundWillBePlayed: "소리가 재생됩니다" | ||||
| showReplay: "리플레이 보기" | ||||
| replay: "리플레이" | ||||
| replaying: "리플레이 중" | ||||
| ranking: "랭킹" | ||||
| lastNDays: "최근 {n}일" | ||||
| backToTitle: "타이틀로 가기" | ||||
| hemisphere: "거주 지역" | ||||
| withSensitive: "민감한 파일이 포함된 노트 보기" | ||||
| userSaysSomethingSensitive: "{name}의 민감한 파일이 포함된 게시물" | ||||
| enableHorizontalSwipe: "스와이프하여 탭 전환" | ||||
| _bubbleGame: | ||||
|   howToPlay: "설명" | ||||
|   _howToPlay: | ||||
|     section1: "위치를 조정하여 상자에 물건을 떨어뜨립니다." | ||||
|     section2: "같은 종류의 물건이 붙으면 다른 물건으로 바뀌면서 점수를 얻게 됩니다." | ||||
|     section3: "상자에서 물건이 넘치면 게임 오버입니다. 상자에서 물건이 넘치지 않도록 하면서 물건을 융합하여 높은 점수를 획득하세요!" | ||||
| _announcement: | ||||
|   forExistingUsers: "기존 유저에게만 알림" | ||||
|   forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다." | ||||
|  | @ -1561,6 +1582,13 @@ _achievements: | |||
|     _tutorialCompleted: | ||||
|       title: "Misskey 입문자 과정 수료증" | ||||
|       description: "튜토리얼을 완료했습니다" | ||||
|     _bubbleGameExplodingHead: | ||||
|       title: "🤯" | ||||
|       description: "버블 게임에서 가장 큰 물건을 내놓았다" | ||||
|     _bubbleGameDoubleExplodingHead: | ||||
|       title: "더블 🤯" | ||||
|       description: "버블게임에서 가장 큰 물건 2개를 동시에 내놓았다." | ||||
|       flavor: "이 정도만 도시락통에 🤯 🤯 조금만 더" | ||||
| _role: | ||||
|   new: "새 역할 생성" | ||||
|   edit: "역할 수정" | ||||
|  | @ -2413,6 +2441,48 @@ _dataSaver: | |||
|   _code: | ||||
|     title: "문자열 강조" | ||||
|     description: "MFM 등으로 문자열 강조 기법을 사용할 때 누르기 전에는 불러오지 않습니다. 문자열 강조에서는 강조할 언어마다 그 정의 파일을 불러와야 하지만 이를 자동으로 불러오지 않으므로 데이터 사용량을 줄일 수 있습니다." | ||||
| _hemisphere: | ||||
|   N: "북반구" | ||||
|   S: "남반구" | ||||
|   caption: "일부 클라이언트 설정에서 계절을 판단하기 위해 사용합니다." | ||||
| _reversi: | ||||
|   reversi: "리버시" | ||||
|   gameSettings: "대국 설정" | ||||
|   chooseBoard: "보드 선택" | ||||
|   blackOrWhite: "선공/후공" | ||||
|   blackIs: "{name}님이 흑(선공)" | ||||
|   rules: "규칙" | ||||
|   thisGameIsStartedSoon: "대국이 곧 시작됩니다" | ||||
|   waitingForOther: "상대방의 준비가 완료되기를 기다리고 있습니다." | ||||
|   waitingForMe: "당신의 준비가 완료되기를 기다리고 있습니다." | ||||
|   waitingBoth: "준비하세요" | ||||
|   ready: "준비 완료" | ||||
|   cancelReady: "준비 다시 시작" | ||||
|   opponentTurn: "상대의 차례입니다" | ||||
|   myTurn: "당신의 차례입니다" | ||||
|   turnOf: "{name}의 차례입니다" | ||||
|   pastTurnOf: "{name}의 차례" | ||||
|   surrender: "기권" | ||||
|   surrendered: "기권에 의해" | ||||
|   timeout: "시간 초과" | ||||
|   drawn: "무승부" | ||||
|   won: "{name}의 승리" | ||||
|   black: "흑" | ||||
|   white: "백" | ||||
|   total: "합계" | ||||
|   turnCount: "{count}턴 째" | ||||
|   myGames: "내 대국" | ||||
|   allGames: "모두의 대국" | ||||
|   ended: "종료" | ||||
|   playing: "대국 중" | ||||
|   isLlotheo: "돌이 적은 사람이 승리 (로세오)" | ||||
|   loopedMap: "루프 지도" | ||||
|   canPutEverywhere: "어디에도 둘 수 있는 모드" | ||||
|   timeLimitForEachTurn: "1턴의 시간 제한" | ||||
|   freeMatch: "프리매치" | ||||
|   lookingForPlayer: "상대를 찾고 있습니다" | ||||
|   gameCanceled: "대국이 취소되었습니다" | ||||
| _offlineScreen: | ||||
|   title: "오프라인 - 서버에 접속할 수 없습니다" | ||||
|   header: "서버에 접속할 수 없습니다" | ||||
| 
 | ||||
|  |  | |||
|  | @ -2473,7 +2473,7 @@ _reversi: | |||
|   turnCount: "{count} 回合" | ||||
|   myGames: "我的對弈" | ||||
|   allGames: "所有對弈" | ||||
|   ended: "" | ||||
|   ended: "已結束" | ||||
|   playing: "正在對弈" | ||||
|   isLlotheo: "子較少的一方為勝(顛倒規則)" | ||||
|   loopedMap: "循環棋盤" | ||||
|  | @ -2482,6 +2482,11 @@ _reversi: | |||
|   freeMatch: "自由對戰" | ||||
|   lookingForPlayer: "正在搜尋對手" | ||||
|   gameCanceled: "對弈已被取消" | ||||
|   shareToTlTheGameWhenStart: "在遊戲開始時將對弈資訊發布到時間軸" | ||||
|   iStartedAGame: "對弈開始了! #MisskeyReversi" | ||||
|   opponentHasSettingsChanged: "對手更改了設定" | ||||
|   allowIrregularRules: "允許異常規則(完全自由)" | ||||
|   disallowIrregularRules: "不允許異常規則" | ||||
| _offlineScreen: | ||||
|   title: "離線-無法連接伺服器" | ||||
|   header: "無法連接伺服器" | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| { | ||||
| 	"name": "misskey", | ||||
| 	"version": "2024.2.0-beta.3", | ||||
| 	"version": "2024.2.0-beta.7", | ||||
| 	"codename": "nasubi", | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
|  |  | |||
|  | @ -0,0 +1,16 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| export class Reversi61706081514499 { | ||||
|     name = 'Reversi61706081514499' | ||||
| 
 | ||||
|     async up(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "reversi_game" ADD "noIrregularRules" boolean NOT NULL DEFAULT false`); | ||||
|     } | ||||
| 
 | ||||
|     async down(queryRunner) { | ||||
|         await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "noIrregularRules"`); | ||||
|     } | ||||
| } | ||||
|  | @ -107,7 +107,6 @@ | |||
| 		"cli-highlight": "2.1.11", | ||||
| 		"color-convert": "2.0.1", | ||||
| 		"content-disposition": "0.5.4", | ||||
| 		"crc-32": "^1.2.2", | ||||
| 		"date-fns": "2.30.0", | ||||
| 		"deep-email-validator": "0.1.21", | ||||
| 		"fastify": "4.25.2", | ||||
|  |  | |||
|  | @ -5,10 +5,9 @@ | |||
| 
 | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import * as Redis from 'ioredis'; | ||||
| import CRC32 from 'crc-32'; | ||||
| import { ModuleRef } from '@nestjs/core'; | ||||
| import * as Reversi from 'misskey-reversi'; | ||||
| import { IsNull } from 'typeorm'; | ||||
| import { IsNull, LessThan, MoreThan } from 'typeorm'; | ||||
| import type { | ||||
| 	MiReversiGame, | ||||
| 	ReversiGamesRepository, | ||||
|  | @ -25,7 +24,7 @@ import { Serialized } from '@/types.js'; | |||
| import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js'; | ||||
| import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common'; | ||||
| 
 | ||||
| const MATCHING_TIMEOUT_MS = 1000 * 15; // 15sec
 | ||||
| const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
 | ||||
| 
 | ||||
| @Injectable() | ||||
| export class ReversiService implements OnApplicationShutdown, OnModuleInit { | ||||
|  | @ -86,44 +85,82 @@ export class ReversiService implements OnApplicationShutdown, 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'); | ||||
| 
 | ||||
|  | @ -131,7 +168,9 @@ export class ReversiService implements OnApplicationShutdown, 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; | ||||
| 		} | ||||
|  | @ -139,23 +178,35 @@ export class ReversiService implements OnApplicationShutdown, 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; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -167,7 +218,15 @@ export class ReversiService implements OnApplicationShutdown, 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 | ||||
|  | @ -221,7 +280,7 @@ export class ReversiService implements OnApplicationShutdown, 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, | ||||
|  | @ -234,6 +293,7 @@ export class ReversiService implements OnApplicationShutdown, 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'], | ||||
|  | @ -255,7 +315,13 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { | |||
| 			bw = parseInt(game.bw, 10); | ||||
| 		} | ||||
| 
 | ||||
| 		const crc32 = CRC32.str(JSON.stringify(game.logs)).toString(); | ||||
| 		const engine = new Reversi.Game(game.map, { | ||||
| 			isLlotheo: game.isLlotheo, | ||||
| 			canPutEverywhere: game.canPutEverywhere, | ||||
| 			loopedBoard: game.loopedBoard, | ||||
| 		}); | ||||
| 
 | ||||
| 		const crc32 = engine.calcCrc32().toString(); | ||||
| 
 | ||||
| 		const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update() | ||||
| 			.set({ | ||||
|  | @ -276,12 +342,6 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { | |||
| 		this.cacheGame(updatedGame); | ||||
| 
 | ||||
| 		//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
 | ||||
| 		const engine = new Reversi.Game(updatedGame.map, { | ||||
| 			isLlotheo: updatedGame.isLlotheo, | ||||
| 			canPutEverywhere: updatedGame.canPutEverywhere, | ||||
| 			loopedBoard: updatedGame.loopedBoard, | ||||
| 		}); | ||||
| 
 | ||||
| 		if (engine.isEnded) { | ||||
| 			let winnerId; | ||||
| 			if (engine.winner === true) { | ||||
|  | @ -335,7 +395,7 @@ export class ReversiService implements OnApplicationShutdown, 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; | ||||
|  | @ -406,7 +466,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { | |||
| 
 | ||||
| 		const serializeLogs = Reversi.Serializer.serializeLogs(logs); | ||||
| 
 | ||||
| 		const crc32 = CRC32.str(JSON.stringify(serializeLogs)).toString(); | ||||
| 		const crc32 = engine.calcCrc32().toString(); | ||||
| 
 | ||||
| 		const updatedGame = { | ||||
| 			...game, | ||||
|  | @ -536,7 +596,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { | |||
| 		if (game == null) throw new Error('game not found'); | ||||
| 
 | ||||
| 		if (crc32.toString() !== game.crc32) { | ||||
| 			return await this.reversiGameEntityService.packDetail(game); | ||||
| 			return game; | ||||
| 		} else { | ||||
| 			return null; | ||||
| 		} | ||||
|  |  | |||
|  | @ -61,6 +61,7 @@ export class ReversiGameEntityService { | |||
| 			canPutEverywhere: game.canPutEverywhere, | ||||
| 			loopedBoard: game.loopedBoard, | ||||
| 			timeLimitForEachTurn: game.timeLimitForEachTurn, | ||||
| 			noIrregularRules: game.noIrregularRules, | ||||
| 			logs: game.logs, | ||||
| 			map: game.map, | ||||
| 		}); | ||||
|  | @ -105,6 +106,7 @@ export class ReversiGameEntityService { | |||
| 			canPutEverywhere: game.canPutEverywhere, | ||||
| 			loopedBoard: game.loopedBoard, | ||||
| 			timeLimitForEachTurn: game.timeLimitForEachTurn, | ||||
| 			noIrregularRules: game.noIrregularRules, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
|  |  | |||
|  | @ -106,6 +106,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.'); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -372,6 +372,7 @@ import * as ep___reversi_match from './endpoints/reversi/match.js'; | |||
| import * as ep___reversi_invitations from './endpoints/reversi/invitations.js'; | ||||
| import * as ep___reversi_showGame from './endpoints/reversi/show-game.js'; | ||||
| import * as ep___reversi_surrender from './endpoints/reversi/surrender.js'; | ||||
| import * as ep___reversi_verify from './endpoints/reversi/verify.js'; | ||||
| import { GetterService } from './GetterService.js'; | ||||
| import { ApiLoggerService } from './ApiLoggerService.js'; | ||||
| import type { Provider } from '@nestjs/common'; | ||||
|  | @ -742,6 +743,7 @@ const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___r | |||
| const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default }; | ||||
| const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default }; | ||||
| const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default }; | ||||
| const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default }; | ||||
| 
 | ||||
| @Module({ | ||||
| 	imports: [ | ||||
|  | @ -1116,6 +1118,7 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass | |||
| 		$reversi_invitations, | ||||
| 		$reversi_showGame, | ||||
| 		$reversi_surrender, | ||||
| 		$reversi_verify, | ||||
| 	], | ||||
| 	exports: [ | ||||
| 		$admin_meta, | ||||
|  | @ -1481,6 +1484,7 @@ const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass | |||
| 		$reversi_invitations, | ||||
| 		$reversi_showGame, | ||||
| 		$reversi_surrender, | ||||
| 		$reversi_verify, | ||||
| 	], | ||||
| }) | ||||
| export class EndpointsModule {} | ||||
|  |  | |||
|  | @ -373,6 +373,7 @@ import * as ep___reversi_match from './endpoints/reversi/match.js'; | |||
| import * as ep___reversi_invitations from './endpoints/reversi/invitations.js'; | ||||
| import * as ep___reversi_showGame from './endpoints/reversi/show-game.js'; | ||||
| import * as ep___reversi_surrender from './endpoints/reversi/surrender.js'; | ||||
| import * as ep___reversi_verify from './endpoints/reversi/verify.js'; | ||||
| 
 | ||||
| const eps = [ | ||||
| 	['admin/meta', ep___admin_meta], | ||||
|  | @ -741,6 +742,7 @@ const eps = [ | |||
| 	['reversi/invitations', ep___reversi_invitations], | ||||
| 	['reversi/show-game', ep___reversi_showGame], | ||||
| 	['reversi/surrender', ep___reversi_surrender], | ||||
| 	['reversi/verify', ep___reversi_verify], | ||||
| ]; | ||||
| 
 | ||||
| interface IEndpointMetaBase { | ||||
|  |  | |||
|  | @ -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; | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,64 @@ | |||
| /* | ||||
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors | ||||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import { Injectable } from '@nestjs/common'; | ||||
| import { Endpoint } from '@/server/api/endpoint-base.js'; | ||||
| import { ReversiService } from '@/core/ReversiService.js'; | ||||
| import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; | ||||
| import { ApiError } from '../../error.js'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	errors: { | ||||
| 		noSuchGame: { | ||||
| 			message: 'No such game.', | ||||
| 			code: 'NO_SUCH_GAME', | ||||
| 			id: '8fb05624-b525-43dd-90f7-511852bdfeee', | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	res: { | ||||
| 		type: 'object', | ||||
| 		optional: false, nullable: false, | ||||
| 		properties: { | ||||
| 			desynced: { type: 'boolean' }, | ||||
| 			game: { | ||||
| 				type: 'object', | ||||
| 				optional: true, nullable: true, | ||||
| 				ref: 'ReversiGameDetailed', | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| } as const; | ||||
| 
 | ||||
| export const paramDef = { | ||||
| 	type: 'object', | ||||
| 	properties: { | ||||
| 		gameId: { type: 'string', format: 'misskey:id' }, | ||||
| 		crc32: { type: 'string' }, | ||||
| 	}, | ||||
| 	required: ['gameId', 'crc32'], | ||||
| } as const; | ||||
| 
 | ||||
| @Injectable() | ||||
| export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 | ||||
| 	constructor( | ||||
| 		private reversiService: ReversiService, | ||||
| 		private reversiGameEntityService: ReversiGameEntityService, | ||||
| 	) { | ||||
| 		super(meta, paramDef, async (ps, me) => { | ||||
| 			const game = await this.reversiService.checkCrc(ps.gameId, ps.crc32); | ||||
| 			if (game) { | ||||
| 				return { | ||||
| 					desynced: true, | ||||
| 					game: await this.reversiGameEntityService.packDetail(game), | ||||
| 				}; | ||||
| 			} else { | ||||
| 				return { | ||||
| 					desynced: false, | ||||
| 				}; | ||||
| 			} | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | @ -4,7 +4,7 @@ | |||
|  */ | ||||
| 
 | ||||
| import { Inject, Injectable } from '@nestjs/common'; | ||||
| import type { MiReversiGame, ReversiGamesRepository } from '@/models/_.js'; | ||||
| import type { MiReversiGame } from '@/models/_.js'; | ||||
| import { DI } from '@/di-symbols.js'; | ||||
| import { bindThis } from '@/decorators.js'; | ||||
| import { ReversiService } from '@/core/ReversiService.js'; | ||||
|  | @ -19,7 +19,6 @@ class ReversiGameChannel extends Channel { | |||
| 
 | ||||
| 	constructor( | ||||
| 		private reversiService: ReversiService, | ||||
| 		private reversiGamesRepository: ReversiGamesRepository, | ||||
| 		private reversiGameEntityService: ReversiGameEntityService, | ||||
| 
 | ||||
| 		id: string, | ||||
|  | @ -42,7 +41,6 @@ class ReversiGameChannel extends Channel { | |||
| 			case 'updateSettings': this.updateSettings(body.key, body.value); break; | ||||
| 			case 'cancel': this.cancelGame(); break; | ||||
| 			case 'putStone': this.putStone(body.pos, body.id); break; | ||||
| 			case 'resync': this.resync(body.crc32); break; | ||||
| 			case 'claimTimeIsUp': this.claimTimeIsUp(); break; | ||||
| 		} | ||||
| 	} | ||||
|  | @ -75,14 +73,6 @@ class ReversiGameChannel extends Channel { | |||
| 		this.reversiService.putStoneToGame(this.gameId!, this.user, pos, id); | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	private async resync(crc32: string | number) { | ||||
| 		const game = await this.reversiService.checkCrc(this.gameId!, crc32); | ||||
| 		if (game) { | ||||
| 			this.send('resynced', game); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@bindThis | ||||
| 	private async claimTimeIsUp() { | ||||
| 		if (this.user == null) return; | ||||
|  | @ -104,9 +94,6 @@ export class ReversiGameChannelService implements MiChannelService<false> { | |||
| 	public readonly kind = ReversiGameChannel.kind; | ||||
| 
 | ||||
| 	constructor( | ||||
| 		@Inject(DI.reversiGamesRepository) | ||||
| 		private reversiGamesRepository: ReversiGamesRepository, | ||||
| 
 | ||||
| 		private reversiService: ReversiService, | ||||
| 		private reversiGameEntityService: ReversiGameEntityService, | ||||
| 	) { | ||||
|  | @ -116,7 +103,6 @@ export class ReversiGameChannelService implements MiChannelService<false> { | |||
| 	public create(id: string, connection: Channel['connection']): ReversiGameChannel { | ||||
| 		return new ReversiGameChannel( | ||||
| 			this.reversiService, | ||||
| 			this.reversiGamesRepository, | ||||
| 			this.reversiGameEntityService, | ||||
| 			id, | ||||
| 			connection, | ||||
|  |  | |||
										
											Binary file not shown.
										
									
								
							| Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 181 KiB | 
|  | @ -41,10 +41,8 @@ | |||
| 		"chartjs-plugin-zoom": "2.0.1", | ||||
| 		"chromatic": "10.3.1", | ||||
| 		"compare-versions": "6.1.0", | ||||
| 		"crc-32": "^1.2.2", | ||||
| 		"cropperjs": "2.0.0-beta.4", | ||||
| 		"date-fns": "2.30.0", | ||||
| 		"defu": "^6.1.4", | ||||
| 		"escape-regexp": "0.0.1", | ||||
| 		"estree-walker": "3.0.3", | ||||
| 		"eventemitter3": "5.0.1", | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| <template> | ||||
| <div | ||||
| 	ref="rootEl" | ||||
| 	:class="[$style.transitionRoot]" | ||||
| 	:class="[$style.transitionRoot, { [$style.enableAnimation]: shouldAnimate }]" | ||||
| 	@touchstart.passive="touchStart" | ||||
| 	@touchmove.passive="touchMove" | ||||
| 	@touchend.passive="touchEnd" | ||||
|  | @ -44,6 +44,8 @@ const emit = defineEmits<{ | |||
| 	(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void; | ||||
| }>(); | ||||
| 
 | ||||
| const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontalSwipe.value || defaultStore.reactiveState.animation.value); | ||||
| 
 | ||||
| // ▼ しきい値 ▼ // | ||||
| 
 | ||||
| // スワイプと判定される最小の距離 | ||||
|  | @ -188,7 +190,9 @@ watch(tabModel, (newTab, oldTab) => { | |||
| .transitionChildren { | ||||
| 	grid-area: 1 / 1 / 2 / 2; | ||||
| 	transform: translateX(var(--swipe)); | ||||
| } | ||||
| 
 | ||||
| .enableAnimation .transitionChildren { | ||||
| 	&.swipeAnimation_enterActive, | ||||
| 	&.swipeAnimation_leaveActive { | ||||
| 		transition: transform .3s cubic-bezier(0.65, 0.05, 0.36, 1); | ||||
|  |  | |||
|  | @ -143,7 +143,6 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <script lang="ts" setup> | ||||
| import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue'; | ||||
| import * as CRC32 from 'crc-32'; | ||||
| import * as Misskey from 'misskey-js'; | ||||
| import * as Reversi from 'misskey-reversi'; | ||||
| import MkButton from '@/components/MkButton.vue'; | ||||
|  | @ -240,11 +239,17 @@ watch(logPos, (v) => { | |||
| 
 | ||||
| if (game.value.isStarted && !game.value.isEnded) { | ||||
| 	useInterval(() => { | ||||
| 		if (game.value.isEnded || props.connection == null) return; | ||||
| 		const crc32 = CRC32.str(JSON.stringify(game.value.logs)).toString(); | ||||
| 		if (game.value.isEnded) return; | ||||
| 		const crc32 = engine.value.calcCrc32(); | ||||
| 		if (_DEV_) console.log('crc32', crc32); | ||||
| 		props.connection.send('resync', { | ||||
| 			crc32: crc32, | ||||
| 		misskeyApi('reversi/verify', { | ||||
| 			gameId: game.value.id, | ||||
| 			crc32: crc32.toString(), | ||||
| 		}).then((res) => { | ||||
| 			if (res.desynced) { | ||||
| 				console.log('resynced'); | ||||
| 				restoreGame(res.game!); | ||||
| 			} | ||||
| 		}); | ||||
| 	}, 10000, { immediate: false, afterMounted: true }); | ||||
| } | ||||
|  | @ -392,12 +397,6 @@ function restoreGame(_game) { | |||
| 	checkEnd(); | ||||
| } | ||||
| 
 | ||||
| function onStreamResynced(_game) { | ||||
| 	console.log('resynced'); | ||||
| 
 | ||||
| 	restoreGame(_game); | ||||
| } | ||||
| 
 | ||||
| async function surrender() { | ||||
| 	const { canceled } = await os.confirm({ | ||||
| 		type: 'warning', | ||||
|  | @ -450,7 +449,6 @@ function share() { | |||
| onMounted(() => { | ||||
| 	if (props.connection != null) { | ||||
| 		props.connection.on('log', onStreamLog); | ||||
| 		props.connection.on('resynced', onStreamResynced); | ||||
| 		props.connection.on('ended', onStreamEnded); | ||||
| 	} | ||||
| }); | ||||
|  | @ -458,7 +456,6 @@ onMounted(() => { | |||
| onActivated(() => { | ||||
| 	if (props.connection != null) { | ||||
| 		props.connection.on('log', onStreamLog); | ||||
| 		props.connection.on('resynced', onStreamResynced); | ||||
| 		props.connection.on('ended', onStreamEnded); | ||||
| 	} | ||||
| }); | ||||
|  | @ -466,7 +463,6 @@ onActivated(() => { | |||
| onDeactivated(() => { | ||||
| 	if (props.connection != null) { | ||||
| 		props.connection.off('log', onStreamLog); | ||||
| 		props.connection.off('resynced', onStreamResynced); | ||||
| 		props.connection.off('ended', onStreamEnded); | ||||
| 	} | ||||
| }); | ||||
|  | @ -474,7 +470,6 @@ onDeactivated(() => { | |||
| onUnmounted(() => { | ||||
| 	if (props.connection != null) { | ||||
| 		props.connection.off('log', onStreamLog); | ||||
| 		props.connection.off('resynced', onStreamResynced); | ||||
| 		props.connection.off('ended', onStreamEnded); | ||||
| 	} | ||||
| }); | ||||
|  |  | |||
|  | @ -12,85 +12,96 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			<div class="_gaps" :class="{ [$style.disallowInner]: isReady }"> | ||||
| 				<div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div> | ||||
| 
 | ||||
| 				<div class="_panel"> | ||||
| 					<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);"> | ||||
| 						<div>{{ mapName }}</div> | ||||
| 						<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton> | ||||
| 					</div> | ||||
| 				<template v-if="game.noIrregularRules"> | ||||
| 					<div>{{ i18n.ts._reversi.disallowIrregularRules }}</div> | ||||
| 				</template> | ||||
| 				<template v-else> | ||||
| 					<div class="_panel"> | ||||
| 						<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);"> | ||||
| 							<div>{{ mapName }}</div> | ||||
| 							<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton> | ||||
| 						</div> | ||||
| 
 | ||||
| 					<div style="padding: 16px;"> | ||||
| 						<div v-if="game.map == null"><i class="ti ti-dice"></i></div> | ||||
| 						<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> | ||||
| 							<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)"> | ||||
| 								<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i> | ||||
| 						<div style="padding: 16px;"> | ||||
| 							<div v-if="game.map == null"><i class="ti ti-dice"></i></div> | ||||
| 							<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }"> | ||||
| 								<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)"> | ||||
| 									<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i> | ||||
| 								</div> | ||||
| 							</div> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 
 | ||||
| 				<MkFolder :defaultOpen="true"> | ||||
| 					<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template> | ||||
| 					<MkFolder :defaultOpen="true"> | ||||
| 						<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template> | ||||
| 
 | ||||
| 					<MkRadios v-model="game.bw"> | ||||
| 						<option value="random">{{ i18n.ts.random }}</option> | ||||
| 						<option :value="'1'"> | ||||
| 							<I18n :src="i18n.ts._reversi.blackIs" tag="span"> | ||||
| 								<template #name> | ||||
| 									<b><MkUserName :user="game.user1"/></b> | ||||
| 								</template> | ||||
| 							</I18n> | ||||
| 						</option> | ||||
| 						<option :value="'2'"> | ||||
| 							<I18n :src="i18n.ts._reversi.blackIs" tag="span"> | ||||
| 								<template #name> | ||||
| 									<b><MkUserName :user="game.user2"/></b> | ||||
| 								</template> | ||||
| 							</I18n> | ||||
| 						</option> | ||||
| 					</MkRadios> | ||||
| 				</MkFolder> | ||||
| 						<MkRadios v-model="game.bw"> | ||||
| 							<option value="random">{{ i18n.ts.random }}</option> | ||||
| 							<option :value="'1'"> | ||||
| 								<I18n :src="i18n.ts._reversi.blackIs" tag="span"> | ||||
| 									<template #name> | ||||
| 										<b><MkUserName :user="game.user1"/></b> | ||||
| 									</template> | ||||
| 								</I18n> | ||||
| 							</option> | ||||
| 							<option :value="'2'"> | ||||
| 								<I18n :src="i18n.ts._reversi.blackIs" tag="span"> | ||||
| 									<template #name> | ||||
| 										<b><MkUserName :user="game.user2"/></b> | ||||
| 									</template> | ||||
| 								</I18n> | ||||
| 							</option> | ||||
| 						</MkRadios> | ||||
| 					</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder :defaultOpen="true"> | ||||
| 					<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template> | ||||
| 					<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template> | ||||
| 					<MkFolder :defaultOpen="true"> | ||||
| 						<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template> | ||||
| 						<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template> | ||||
| 
 | ||||
| 					<MkRadios v-model="game.timeLimitForEachTurn"> | ||||
| 						<option :value="5">5{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="10">10{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="30">30{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="60">60{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="90">90{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="120">120{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="180">180{{ i18n.ts._time.second }}</option> | ||||
| 						<option :value="3600">3600{{ i18n.ts._time.second }}</option> | ||||
| 					</MkRadios> | ||||
| 				</MkFolder> | ||||
| 						<MkRadios v-model="game.timeLimitForEachTurn"> | ||||
| 							<option :value="5">5{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="10">10{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="30">30{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="60">60{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="90">90{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="120">120{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="180">180{{ i18n.ts._time.second }}</option> | ||||
| 							<option :value="3600">3600{{ i18n.ts._time.second }}</option> | ||||
| 						</MkRadios> | ||||
| 					</MkFolder> | ||||
| 
 | ||||
| 				<MkFolder :defaultOpen="true"> | ||||
| 					<template #label>{{ i18n.ts._reversi.rules }}</template> | ||||
| 					<MkFolder :defaultOpen="true"> | ||||
| 						<template #label>{{ i18n.ts._reversi.rules }}</template> | ||||
| 
 | ||||
| 					<div class="_gaps_s"> | ||||
| 						<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch> | ||||
| 						<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch> | ||||
| 						<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch> | ||||
| 					</div> | ||||
| 				</MkFolder> | ||||
| 						<div class="_gaps_s"> | ||||
| 							<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch> | ||||
| 							<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch> | ||||
| 							<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch> | ||||
| 						</div> | ||||
| 					</MkFolder> | ||||
| 				</template> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</MkSpacer> | ||||
| 	<template #footer> | ||||
| 		<div :class="$style.footer"> | ||||
| 			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> | ||||
| 				<div style="text-align: center; margin-bottom: 10px;"> | ||||
| 					<template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> | ||||
| 					<template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template> | ||||
| 					<template v-if="!isReady && isOpReady">{{ i18n.ts._reversi.waitingForMe }}</template> | ||||
| 					<template v-if="!isReady && !isOpReady">{{ i18n.ts._reversi.waitingBoth }}<MkEllipsis/></template> | ||||
| 				</div> | ||||
| 				<div class="_buttonsCenter"> | ||||
| 					<MkButton rounded danger @click="cancel">{{ i18n.ts.cancel }}</MkButton> | ||||
| 					<MkButton v-if="!isReady" rounded primary @click="ready">{{ i18n.ts._reversi.ready }}</MkButton> | ||||
| 					<MkButton v-if="isReady" rounded @click="unready">{{ i18n.ts._reversi.cancelReady }}</MkButton> | ||||
| 				<div style="text-align: center;" class="_gaps_s"> | ||||
| 					<div v-if="opponentHasSettingsChanged" style="color: var(--warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div> | ||||
| 					<div> | ||||
| 						<template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> | ||||
| 						<template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template> | ||||
| 						<template v-if="!isReady && isOpReady">{{ i18n.ts._reversi.waitingForMe }}</template> | ||||
| 						<template v-if="!isReady && !isOpReady">{{ i18n.ts._reversi.waitingBoth }}<MkEllipsis/></template> | ||||
| 					</div> | ||||
| 					<div class="_buttonsCenter"> | ||||
| 						<MkButton rounded danger @click="cancel">{{ i18n.ts.cancel }}</MkButton> | ||||
| 						<MkButton v-if="!isReady" rounded primary @click="ready">{{ i18n.ts._reversi.ready }}</MkButton> | ||||
| 						<MkButton v-if="isReady" rounded @click="unready">{{ i18n.ts._reversi.cancelReady }}</MkButton> | ||||
| 					</div> | ||||
| 					<div> | ||||
| 						<MkSwitch v-model="shareWhenStart">{{ i18n.ts._reversi.shareToTlTheGameWhenStart }}</MkSwitch> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</MkSpacer> | ||||
| 		</div> | ||||
|  | @ -124,6 +135,8 @@ const props = defineProps<{ | |||
| 	connection: Misskey.ChannelConnection; | ||||
| }>(); | ||||
| 
 | ||||
| const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false }); | ||||
| 
 | ||||
| const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game)); | ||||
| 
 | ||||
| const mapName = computed(() => { | ||||
|  | @ -142,6 +155,8 @@ const isOpReady = computed(() => { | |||
| 	return false; | ||||
| }); | ||||
| 
 | ||||
| const opponentHasSettingsChanged = ref(false); | ||||
| 
 | ||||
| watch(() => game.value.bw, () => { | ||||
| 	updateSettings('bw'); | ||||
| }); | ||||
|  | @ -190,6 +205,7 @@ async function cancel() { | |||
| 
 | ||||
| function ready() { | ||||
| 	props.connection.send('ready', true); | ||||
| 	opponentHasSettingsChanged.value = false; | ||||
| } | ||||
| 
 | ||||
| function unready() { | ||||
|  | @ -212,6 +228,10 @@ function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof M | |||
| 	if (userId === $i.id) return; | ||||
| 	if (game.value[key] === value) return; | ||||
| 	game.value[key] = value; | ||||
| 	if (isReady.value) { | ||||
| 		opponentHasSettingsChanged.value = true; | ||||
| 		unready(); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| function onMapCellClick(pos: number, pixel: string) { | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 
 | ||||
| <template> | ||||
| <div v-if="game == null || (!game.isEnded && connection == null)"><MkLoading/></div> | ||||
| <GameSetting v-else-if="!game.isStarted" :game="game" :connection="connection!"/> | ||||
| <GameSetting v-else-if="!game.isStarted" v-model:shareWhenStart="shareWhenStart" :game="game" :connection="connection!"/> | ||||
| <GameBoard v-else :game="game" :connection="connection"/> | ||||
| </template> | ||||
| 
 | ||||
|  | @ -21,6 +21,7 @@ import { signinRequired } from '@/account.js'; | |||
| import { useRouter } from '@/global/router/supplier.js'; | ||||
| import * as os from '@/os.js'; | ||||
| import { i18n } from '@/i18n.js'; | ||||
| import { useInterval } from '@/scripts/use-interval.js'; | ||||
| 
 | ||||
| const $i = signinRequired(); | ||||
| 
 | ||||
|  | @ -32,17 +33,32 @@ const props = defineProps<{ | |||
| 
 | ||||
| const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null); | ||||
| const connection = shallowRef<Misskey.ChannelConnection | null>(null); | ||||
| const shareWhenStart = ref(false); | ||||
| 
 | ||||
| watch(() => props.gameId, () => { | ||||
| 	fetchGame(); | ||||
| }); | ||||
| 
 | ||||
| function start(_game: Misskey.entities.ReversiGameDetailed) { | ||||
| 	if (game.value?.isStarted) return; | ||||
| 
 | ||||
| 	if (shareWhenStart.value) { | ||||
| 		misskeyApi('notes/create', { | ||||
| 			text: i18n.ts._reversi.iStartedAGame + '\n' + location.href, | ||||
| 			visibility: 'home', | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	game.value = _game; | ||||
| } | ||||
| 
 | ||||
| async function fetchGame() { | ||||
| 	const _game = await misskeyApi('reversi/show-game', { | ||||
| 		gameId: props.gameId, | ||||
| 	}); | ||||
| 
 | ||||
| 	game.value = _game; | ||||
| 	shareWhenStart.value = false; | ||||
| 
 | ||||
| 	if (connection.value) { | ||||
| 		connection.value.dispose(); | ||||
|  | @ -52,7 +68,7 @@ async function fetchGame() { | |||
| 			gameId: game.value.id, | ||||
| 		}); | ||||
| 		connection.value.on('started', x => { | ||||
| 			game.value = x.game; | ||||
| 			start(x.game); | ||||
| 		}); | ||||
| 		connection.value.on('canceled', x => { | ||||
| 			connection.value?.dispose(); | ||||
|  | @ -68,6 +84,25 @@ async function fetchGame() { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| // 通信を取りこぼした場合の救済 | ||||
| useInterval(async () => { | ||||
| 	if (game.value == null) return; | ||||
| 	if (game.value.isStarted) return; | ||||
| 
 | ||||
| 	const _game = await misskeyApi('reversi/show-game', { | ||||
| 		gameId: props.gameId, | ||||
| 	}); | ||||
| 
 | ||||
| 	if (_game.isStarted) { | ||||
| 		start(_game); | ||||
| 	} else { | ||||
| 		game.value = _game; | ||||
| 	} | ||||
| }, 1000 * 10, { | ||||
| 	immediate: false, | ||||
| 	afterMounted: true, | ||||
| }); | ||||
| 
 | ||||
| onMounted(() => { | ||||
| 	fetchGame(); | ||||
| }); | ||||
|  | @ -78,10 +113,6 @@ onUnmounted(() => { | |||
| 	} | ||||
| }); | ||||
| 
 | ||||
| const headerActions = computed(() => []); | ||||
| 
 | ||||
| const headerTabs = computed(() => []); | ||||
| 
 | ||||
| definePageMetadata(computed(() => ({ | ||||
| 	title: 'Reversi', | ||||
| 	icon: 'ti ti-device-gamepad', | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			<MkPagination :pagination="myGamesPagination" :disableAutoLoad="true"> | ||||
| 				<template #default="{ items }"> | ||||
| 					<div :class="$style.gamePreviews"> | ||||
| 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> | ||||
| 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> | ||||
| 							<div :class="$style.gamePreviewPlayers"> | ||||
| 								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> | ||||
| 								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> | ||||
|  | @ -45,7 +45,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> | ||||
| 							</div> | ||||
| 							<div :class="$style.gamePreviewFooter"> | ||||
| 								<span v-if="!g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> | ||||
| 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> | ||||
| 								<span v-else-if="!g.isEnded" :class="$style.gamePreviewStatusWaiting"><MkEllipsis/></span> | ||||
| 								<span v-else>{{ i18n.ts._reversi.ended }}</span> | ||||
| 								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/> | ||||
| 							</div> | ||||
|  | @ -60,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 			<MkPagination :pagination="gamesPagination" :disableAutoLoad="true"> | ||||
| 				<template #default="{ items }"> | ||||
| 					<div :class="$style.gamePreviews"> | ||||
| 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> | ||||
| 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> | ||||
| 							<div :class="$style.gamePreviewPlayers"> | ||||
| 								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> | ||||
| 								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> | ||||
|  | @ -71,7 +72,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||
| 								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> | ||||
| 							</div> | ||||
| 							<div :class="$style.gamePreviewFooter"> | ||||
| 								<span v-if="!g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> | ||||
| 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> | ||||
| 								<span v-else-if="!g.isEnded" :class="$style.gamePreviewStatusWaiting"><MkEllipsis/></span> | ||||
| 								<span v-else>{{ i18n.ts._reversi.ended }}</span> | ||||
| 								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/> | ||||
| 							</div> | ||||
|  | @ -137,7 +139,9 @@ if ($i) { | |||
| 	const connection = useStream().useChannel('reversi'); | ||||
| 
 | ||||
| 	connection.on('matched', x => { | ||||
| 		startGame(x.game); | ||||
| 		if (matchingUser.value != null || matchingAny.value) { | ||||
| 			startGame(x.game); | ||||
| 		} | ||||
| 	}); | ||||
| 
 | ||||
| 	connection.on('invited', invitation => { | ||||
|  | @ -153,6 +157,7 @@ if ($i) { | |||
| const invitations = ref<Misskey.entities.UserLite[]>([]); | ||||
| const matchingUser = ref<Misskey.entities.UserLite | null>(null); | ||||
| const matchingAny = ref<boolean>(false); | ||||
| const noIrregularRules = ref<boolean>(false); | ||||
| 
 | ||||
| function startGame(game: Misskey.entities.ReversiGameDetailed) { | ||||
| 	matchingUser.value = null; | ||||
|  | @ -178,6 +183,7 @@ async function matchHeatbeat() { | |||
| 	} else if (matchingAny.value) { | ||||
| 		const res = await misskeyApi('reversi/match', { | ||||
| 			userId: null, | ||||
| 			noIrregularRules: noIrregularRules.value, | ||||
| 		}); | ||||
| 
 | ||||
| 		if (res != null) { | ||||
|  | @ -195,10 +201,22 @@ async function matchUser() { | |||
| 	matchHeatbeat(); | ||||
| } | ||||
| 
 | ||||
| async function matchAny() { | ||||
| 	matchingAny.value = true; | ||||
| 
 | ||||
| 	matchHeatbeat(); | ||||
| function matchAny(ev: MouseEvent) { | ||||
| 	os.popupMenu([{ | ||||
| 		text: i18n.ts._reversi.allowIrregularRules, | ||||
| 		action: () => { | ||||
| 			noIrregularRules.value = false; | ||||
| 			matchingAny.value = true; | ||||
| 			matchHeatbeat(); | ||||
| 		}, | ||||
| 	}, { | ||||
| 		text: i18n.ts._reversi.disallowIrregularRules, | ||||
| 		action: () => { | ||||
| 			noIrregularRules.value = true; | ||||
| 			matchingAny.value = true; | ||||
| 			matchHeatbeat(); | ||||
| 		}, | ||||
| 	}], ev.currentTarget ?? ev.target); | ||||
| } | ||||
| 
 | ||||
| function cancelMatching() { | ||||
|  | @ -220,12 +238,14 @@ async function accept(user) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| useInterval(matchHeatbeat, 1000 * 10, { immediate: false, afterMounted: true }); | ||||
| useInterval(matchHeatbeat, 1000 * 5, { immediate: false, afterMounted: true }); | ||||
| 
 | ||||
| onMounted(() => { | ||||
| 	misskeyApi('reversi/invitations').then(_invitations => { | ||||
| 		invitations.value = _invitations; | ||||
| 	}); | ||||
| 
 | ||||
| 	window.addEventListener('beforeunload', cancelMatching); | ||||
| }); | ||||
| 
 | ||||
| onDeactivated(() => { | ||||
|  | @ -273,6 +293,10 @@ definePageMetadata(computed(() => ({ | |||
| 	box-shadow: inset 0 0 8px 0px var(--accent); | ||||
| } | ||||
| 
 | ||||
| .gamePreviewWaiting { | ||||
| 	box-shadow: inset 0 0 8px 0px var(--warn); | ||||
| } | ||||
| 
 | ||||
| .gamePreviewPlayers { | ||||
| 	text-align: center; | ||||
| 	padding: 16px; | ||||
|  | @ -306,6 +330,12 @@ definePageMetadata(computed(() => ({ | |||
| 	animation: blink 2s infinite; | ||||
| } | ||||
| 
 | ||||
| .gamePreviewStatusWaiting { | ||||
| 	color: var(--warn); | ||||
| 	font-weight: bold; | ||||
| 	animation: blink 2s infinite; | ||||
| } | ||||
| 
 | ||||
| .waitingScreen { | ||||
| 	text-align: center; | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ | |||
| 
 | ||||
| import { onUnmounted, Ref, ref, watch } from 'vue'; | ||||
| import { BroadcastChannel } from 'broadcast-channel'; | ||||
| import { defu } from 'defu'; | ||||
| import { $i } from '@/account.js'; | ||||
| import { misskeyApi } from '@/scripts/misskey-api.js'; | ||||
| import { get, set } from '@/scripts/idb-proxy.js'; | ||||
|  | @ -81,14 +80,37 @@ export class Storage<T extends StateDef> { | |||
| 		this.loaded = this.ready.then(() => this.load()); | ||||
| 	} | ||||
| 
 | ||||
| 	private isPureObject(value: unknown): value is Record<string, unknown> { | ||||
| 	private isPureObject(value: unknown): value is Record<string | number | symbol, unknown> { | ||||
| 		return typeof value === 'object' && value !== null && !Array.isArray(value); | ||||
| 	} | ||||
| 
 | ||||
| 	private mergeState<T>(value: T, def: T): T { | ||||
| 	/** | ||||
| 	 * valueにないキーをdefからもらう(再帰的)\ | ||||
| 	 * nullはそのまま、undefinedはdefの値 | ||||
| 	 **/ | ||||
| 	private mergeObject<X>(value: X, def: X): X { | ||||
| 		if (this.isPureObject(value) && this.isPureObject(def)) { | ||||
| 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def); | ||||
| 			return defu(value, def) as T; | ||||
| 			const result = structuredClone(value) as X; | ||||
| 			for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) { | ||||
| 				if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) { | ||||
| 					result[k] = v; | ||||
| 				} else if (this.isPureObject(v) && this.isPureObject(result[k])) { | ||||
| 					const child = structuredClone(result[k]) as X[keyof X] & Record<string | number | symbol, unknown>; | ||||
| 					result[k] = this.mergeObject<typeof v>(child, v); | ||||
| 				} | ||||
| 			} | ||||
| 			return result; | ||||
| 		} | ||||
| 		return value; | ||||
| 	} | ||||
| 
 | ||||
| 	private mergeState<X>(value: X, def: X): X { | ||||
| 		if (this.isPureObject(value) && this.isPureObject(def)) { | ||||
| 			const merged = this.mergeObject(value, def); | ||||
| 
 | ||||
| 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); | ||||
| 
 | ||||
| 			return merged as X; | ||||
| 		} | ||||
| 		return value; | ||||
| 	} | ||||
|  |  | |||
|  | @ -1633,6 +1633,8 @@ declare namespace entities { | |||
|         ReversiShowGameRequest, | ||||
|         ReversiShowGameResponse, | ||||
|         ReversiSurrenderRequest, | ||||
|         ReversiVerifyRequest, | ||||
|         ReversiVerifyResponse, | ||||
|         Error_2 as Error, | ||||
|         UserLite, | ||||
|         UserDetailedNotMeOnly, | ||||
|  | @ -2644,6 +2646,12 @@ type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200 | |||
| // @public (undocumented) | ||||
| type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json']; | ||||
| 
 | ||||
| // @public (undocumented) | ||||
| type Role = components['schemas']['Role']; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| /* | ||||
|  * version: 2024.2.0-beta.2 | ||||
|  * generatedAt: 2024-01-22T07:11:08.412Z | ||||
|  * version: 2024.2.0-beta.6 | ||||
|  * generatedAt: 2024-01-24T07:32:10.455Z | ||||
|  */ | ||||
| 
 | ||||
| import type { SwitchCaseResponseType } from '../api.js'; | ||||
|  | @ -4073,5 +4073,16 @@ declare module '../api.js' { | |||
|       params: P, | ||||
|       credential?: string | null, | ||||
|     ): Promise<SwitchCaseResponseType<E, P>>; | ||||
| 
 | ||||
|     /** | ||||
|      * No description provided. | ||||
|      *  | ||||
|      * **Credential required**: *No* | ||||
|      */ | ||||
|     request<E extends 'reversi/verify', P extends Endpoints[E]['req']>( | ||||
|       endpoint: E, | ||||
|       params: P, | ||||
|       credential?: string | null, | ||||
|     ): Promise<SwitchCaseResponseType<E, P>>; | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| /* | ||||
|  * version: 2024.2.0-beta.2 | ||||
|  * generatedAt: 2024-01-22T07:11:08.410Z | ||||
|  * version: 2024.2.0-beta.6 | ||||
|  * generatedAt: 2024-01-24T07:32:10.453Z | ||||
|  */ | ||||
| 
 | ||||
| import type { | ||||
|  | @ -554,6 +554,8 @@ import type { | |||
| 	ReversiShowGameRequest, | ||||
| 	ReversiShowGameResponse, | ||||
| 	ReversiSurrenderRequest, | ||||
| 	ReversiVerifyRequest, | ||||
| 	ReversiVerifyResponse, | ||||
| } from './entities.js'; | ||||
| 
 | ||||
| export type Endpoints = { | ||||
|  | @ -923,4 +925,5 @@ export type Endpoints = { | |||
| 	'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse }; | ||||
| 	'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse }; | ||||
| 	'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse }; | ||||
| 	'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse }; | ||||
| } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| /* | ||||
|  * version: 2024.2.0-beta.2 | ||||
|  * generatedAt: 2024-01-22T07:11:08.408Z | ||||
|  * version: 2024.2.0-beta.6 | ||||
|  * generatedAt: 2024-01-24T07:32:10.452Z | ||||
|  */ | ||||
| 
 | ||||
| import { operations } from './types.js'; | ||||
|  | @ -556,3 +556,5 @@ export type ReversiInvitationsResponse = operations['reversi/invitations']['resp | |||
| export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json']; | ||||
| export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json']; | ||||
| export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json']; | ||||
| export type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json']; | ||||
| export type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json']; | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| /* | ||||
|  * version: 2024.2.0-beta.2 | ||||
|  * generatedAt: 2024-01-22T07:11:08.408Z | ||||
|  * version: 2024.2.0-beta.6 | ||||
|  * generatedAt: 2024-01-24T07:32:10.450Z | ||||
|  */ | ||||
| 
 | ||||
| import { components } from './types.js'; | ||||
|  |  | |||
|  | @ -2,8 +2,8 @@ | |||
| /* eslint @typescript-eslint/no-explicit-any: 0 */ | ||||
| 
 | ||||
| /* | ||||
|  * version: 2024.2.0-beta.2 | ||||
|  * generatedAt: 2024-01-22T07:11:08.327Z | ||||
|  * version: 2024.2.0-beta.6 | ||||
|  * generatedAt: 2024-01-24T07:32:10.370Z | ||||
|  */ | ||||
| 
 | ||||
| /** | ||||
|  | @ -3526,6 +3526,15 @@ export type paths = { | |||
|      */ | ||||
|     post: operations['reversi/surrender']; | ||||
|   }; | ||||
|   '/reversi/verify': { | ||||
|     /** | ||||
|      * reversi/verify | ||||
|      * @description No description provided. | ||||
|      * | ||||
|      * **Credential required**: *No* | ||||
|      */ | ||||
|     post: operations['reversi/verify']; | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export type webhooks = Record<string, never>; | ||||
|  | @ -4484,6 +4493,7 @@ export type components = { | |||
|       timeoutUserId: string | null; | ||||
|       black: number | null; | ||||
|       bw: string; | ||||
|       noIrregularRules: boolean; | ||||
|       isLlotheo: boolean; | ||||
|       canPutEverywhere: boolean; | ||||
|       loopedBoard: boolean; | ||||
|  | @ -4519,6 +4529,7 @@ export type components = { | |||
|       timeoutUserId: string | null; | ||||
|       black: number | null; | ||||
|       bw: string; | ||||
|       noIrregularRules: boolean; | ||||
|       isLlotheo: boolean; | ||||
|       canPutEverywhere: boolean; | ||||
|       loopedBoard: boolean; | ||||
|  | @ -25790,6 +25801,10 @@ export type operations = { | |||
|         'application/json': { | ||||
|           /** Format: misskey:id */ | ||||
|           userId?: string | null; | ||||
|           /** @default false */ | ||||
|           noIrregularRules?: boolean; | ||||
|           /** @default false */ | ||||
|           multiple?: boolean; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|  | @ -25984,5 +25999,63 @@ export type operations = { | |||
|       }; | ||||
|     }; | ||||
|   }; | ||||
|   /** | ||||
|    * reversi/verify | ||||
|    * @description No description provided. | ||||
|    * | ||||
|    * **Credential required**: *No* | ||||
|    */ | ||||
|   'reversi/verify': { | ||||
|     requestBody: { | ||||
|       content: { | ||||
|         'application/json': { | ||||
|           /** Format: misskey:id */ | ||||
|           gameId: string; | ||||
|           crc32: string; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|     responses: { | ||||
|       /** @description OK (with results) */ | ||||
|       200: { | ||||
|         content: { | ||||
|           'application/json': { | ||||
|             desynced: boolean; | ||||
|             game?: components['schemas']['ReversiGameDetailed'] | null; | ||||
|           }; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Client error */ | ||||
|       400: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['Error']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Authentication error */ | ||||
|       401: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['Error']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Forbidden error */ | ||||
|       403: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['Error']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description I'm Ai */ | ||||
|       418: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['Error']; | ||||
|         }; | ||||
|       }; | ||||
|       /** @description Internal server error */ | ||||
|       500: { | ||||
|         content: { | ||||
|           'application/json': components['schemas']['Error']; | ||||
|         }; | ||||
|       }; | ||||
|     }; | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -36,7 +36,8 @@ | |||
| 		"built" | ||||
| 	], | ||||
| 	"dependencies": { | ||||
| 		"crc-32": "1.2.2", | ||||
| 		"esbuild": "0.19.11", | ||||
| 		"glob": "^10.3.10" | ||||
| 		"glob": "10.3.10" | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
|  * SPDX-License-Identifier: AGPL-3.0-only | ||||
|  */ | ||||
| 
 | ||||
| import CRC32 from 'crc-32'; | ||||
| 
 | ||||
| /** | ||||
|  * true ... 黒 | ||||
|  * false ... 白 | ||||
|  | @ -204,6 +206,13 @@ export class Game { | |||
| 		return ([] as number[]).concat(...diffVectors.map(effectsInLine)); | ||||
| 	} | ||||
| 
 | ||||
| 	public calcCrc32(): number { | ||||
| 		return CRC32.str(JSON.stringify({ | ||||
| 			board: this.board, | ||||
| 			turn: this.turn, | ||||
| 		})); | ||||
| 	} | ||||
| 
 | ||||
| 	public get isEnded(): boolean { | ||||
| 		return this.turn === null; | ||||
| 	} | ||||
|  |  | |||
							
								
								
									
										154
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										154
									
								
								pnpm-lock.yaml
								
								
								
								
							|  | @ -185,9 +185,6 @@ importers: | |||
|       content-disposition: | ||||
|         specifier: 0.5.4 | ||||
|         version: 0.5.4 | ||||
|       crc-32: | ||||
|         specifier: ^1.2.2 | ||||
|         version: 1.2.2 | ||||
|       date-fns: | ||||
|         specifier: 2.30.0 | ||||
|         version: 2.30.0 | ||||
|  | @ -742,18 +739,12 @@ importers: | |||
|       compare-versions: | ||||
|         specifier: 6.1.0 | ||||
|         version: 6.1.0 | ||||
|       crc-32: | ||||
|         specifier: ^1.2.2 | ||||
|         version: 1.2.2 | ||||
|       cropperjs: | ||||
|         specifier: 2.0.0-beta.4 | ||||
|         version: 2.0.0-beta.4 | ||||
|       date-fns: | ||||
|         specifier: 2.30.0 | ||||
|         version: 2.30.0 | ||||
|       defu: | ||||
|         specifier: ^6.1.4 | ||||
|         version: 6.1.4 | ||||
|       escape-regexp: | ||||
|         specifier: 0.0.1 | ||||
|         version: 0.0.1 | ||||
|  | @ -840,7 +831,7 @@ importers: | |||
|         version: 1.7.2(vue@3.4.15) | ||||
|       vite: | ||||
|         specifier: 5.0.12 | ||||
|         version: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|         version: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|       vue: | ||||
|         specifier: 3.4.15 | ||||
|         version: 3.4.15(typescript@5.3.3) | ||||
|  | @ -1018,7 +1009,7 @@ importers: | |||
|         version: 1.0.3 | ||||
|       vitest: | ||||
|         specifier: 0.34.6 | ||||
|         version: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0) | ||||
|         version: 0.34.6(happy-dom@10.0.3)(sass@1.70.0) | ||||
|       vitest-fetch-mock: | ||||
|         specifier: 0.2.2 | ||||
|         version: 0.2.2(vitest@0.34.6) | ||||
|  | @ -1177,11 +1168,14 @@ importers: | |||
| 
 | ||||
|   packages/misskey-reversi: | ||||
|     dependencies: | ||||
|       crc-32: | ||||
|         specifier: 1.2.2 | ||||
|         version: 1.2.2 | ||||
|       esbuild: | ||||
|         specifier: 0.19.11 | ||||
|         version: 0.19.11 | ||||
|       glob: | ||||
|         specifier: ^10.3.10 | ||||
|         specifier: 10.3.10 | ||||
|         version: 10.3.10 | ||||
|     devDependencies: | ||||
|       '@misskey-dev/eslint-plugin': | ||||
|  | @ -1912,7 +1906,7 @@ packages: | |||
|       '@babel/traverse': 7.22.11 | ||||
|       '@babel/types': 7.22.17 | ||||
|       convert-source-map: 1.9.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       gensync: 1.0.0-beta.2 | ||||
|       json5: 2.2.3 | ||||
|       semver: 6.3.1 | ||||
|  | @ -1935,7 +1929,7 @@ packages: | |||
|       '@babel/traverse': 7.23.5 | ||||
|       '@babel/types': 7.23.5 | ||||
|       convert-source-map: 2.0.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       gensync: 1.0.0-beta.2 | ||||
|       json5: 2.2.3 | ||||
|       semver: 6.3.1 | ||||
|  | @ -2037,7 +2031,7 @@ packages: | |||
|       '@babel/core': 7.23.5 | ||||
|       '@babel/helper-compilation-targets': 7.22.15 | ||||
|       '@babel/helper-plugin-utils': 7.22.5 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       lodash.debounce: 4.0.8 | ||||
|       resolve: 1.22.8 | ||||
|     transitivePeerDependencies: | ||||
|  | @ -3436,7 +3430,7 @@ packages: | |||
|       '@babel/helper-split-export-declaration': 7.22.6 | ||||
|       '@babel/parser': 7.23.5 | ||||
|       '@babel/types': 7.22.17 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       globals: 11.12.0 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -3454,7 +3448,7 @@ packages: | |||
|       '@babel/helper-split-export-declaration': 7.22.6 | ||||
|       '@babel/parser': 7.23.6 | ||||
|       '@babel/types': 7.23.5 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       globals: 11.12.0 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -4161,7 +4155,7 @@ packages: | |||
|     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | ||||
|     dependencies: | ||||
|       ajv: 6.12.6 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       espree: 9.6.1 | ||||
|       globals: 13.19.0 | ||||
|       ignore: 5.2.4 | ||||
|  | @ -4178,7 +4172,7 @@ packages: | |||
|     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} | ||||
|     dependencies: | ||||
|       ajv: 6.12.6 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       espree: 9.6.1 | ||||
|       globals: 13.19.0 | ||||
|       ignore: 5.2.4 | ||||
|  | @ -4413,7 +4407,7 @@ packages: | |||
|     engines: {node: '>=10.10.0'} | ||||
|     dependencies: | ||||
|       '@humanwhocodes/object-schema': 2.0.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       minimatch: 3.1.2 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -4717,7 +4711,7 @@ packages: | |||
|       magic-string: 0.27.0 | ||||
|       react-docgen-typescript: 2.2.2(typescript@5.3.3) | ||||
|       typescript: 5.3.3 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|     dev: true | ||||
| 
 | ||||
|   /@jridgewell/gen-mapping@0.3.2: | ||||
|  | @ -4741,6 +4735,7 @@ packages: | |||
|     dependencies: | ||||
|       '@jridgewell/gen-mapping': 0.3.2 | ||||
|       '@jridgewell/trace-mapping': 0.3.18 | ||||
|     dev: false | ||||
| 
 | ||||
|   /@jridgewell/sourcemap-codec@1.4.14: | ||||
|     resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} | ||||
|  | @ -6777,7 +6772,7 @@ packages: | |||
|       magic-string: 0.30.5 | ||||
|       rollup: 3.29.4 | ||||
|       typescript: 5.3.3 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - encoding | ||||
|       - supports-color | ||||
|  | @ -6982,7 +6977,7 @@ packages: | |||
|       util: 0.12.5 | ||||
|       util-deprecate: 1.0.2 | ||||
|       watchpack: 2.4.0 | ||||
|       ws: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) | ||||
|       ws: 8.16.0 | ||||
|     transitivePeerDependencies: | ||||
|       - bufferutil | ||||
|       - encoding | ||||
|  | @ -7151,7 +7146,7 @@ packages: | |||
|       react: 18.2.0 | ||||
|       react-docgen: 7.0.1 | ||||
|       react-dom: 18.2.0(react@18.2.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - '@preact/preset-vite' | ||||
|       - encoding | ||||
|  | @ -7277,7 +7272,7 @@ packages: | |||
|       '@storybook/vue3': 7.6.10(vue@3.4.15) | ||||
|       '@vitejs/plugin-vue': 4.5.2(vite@5.0.12)(vue@3.4.15) | ||||
|       magic-string: 0.30.5 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|       vue-docgen-api: 4.64.1(vue@3.4.15) | ||||
|     transitivePeerDependencies: | ||||
|       - '@preact/preset-vite' | ||||
|  | @ -7777,7 +7772,7 @@ packages: | |||
|       dom-accessibility-api: 0.5.16 | ||||
|       lodash: 4.17.21 | ||||
|       redent: 3.0.0 | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0) | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0) | ||||
|     dev: true | ||||
| 
 | ||||
|   /@testing-library/user-event@14.4.3(@testing-library/dom@9.2.0): | ||||
|  | @ -8451,7 +8446,7 @@ packages: | |||
|       '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) | ||||
|       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) | ||||
|       '@typescript-eslint/visitor-keys': 6.11.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.53.0 | ||||
|       graphemer: 1.4.0 | ||||
|       ignore: 5.2.4 | ||||
|  | @ -8480,7 +8475,7 @@ packages: | |||
|       '@typescript-eslint/type-utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) | ||||
|       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) | ||||
|       '@typescript-eslint/visitor-keys': 6.18.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.56.0 | ||||
|       graphemer: 1.4.0 | ||||
|       ignore: 5.2.4 | ||||
|  | @ -8506,7 +8501,7 @@ packages: | |||
|       '@typescript-eslint/types': 6.11.0 | ||||
|       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) | ||||
|       '@typescript-eslint/visitor-keys': 6.11.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.53.0 | ||||
|       typescript: 5.3.3 | ||||
|     transitivePeerDependencies: | ||||
|  | @ -8527,7 +8522,7 @@ packages: | |||
|       '@typescript-eslint/types': 6.18.1 | ||||
|       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) | ||||
|       '@typescript-eslint/visitor-keys': 6.18.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.56.0 | ||||
|       typescript: 5.3.3 | ||||
|     transitivePeerDependencies: | ||||
|  | @ -8562,7 +8557,7 @@ packages: | |||
|     dependencies: | ||||
|       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3) | ||||
|       '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.3.3) | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.53.0 | ||||
|       ts-api-utils: 1.0.1(typescript@5.3.3) | ||||
|       typescript: 5.3.3 | ||||
|  | @ -8582,7 +8577,7 @@ packages: | |||
|     dependencies: | ||||
|       '@typescript-eslint/typescript-estree': 6.18.1(typescript@5.3.3) | ||||
|       '@typescript-eslint/utils': 6.18.1(eslint@8.56.0)(typescript@5.3.3) | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.56.0 | ||||
|       ts-api-utils: 1.0.1(typescript@5.3.3) | ||||
|       typescript: 5.3.3 | ||||
|  | @ -8611,7 +8606,7 @@ packages: | |||
|     dependencies: | ||||
|       '@typescript-eslint/types': 6.11.0 | ||||
|       '@typescript-eslint/visitor-keys': 6.11.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       globby: 11.1.0 | ||||
|       is-glob: 4.0.3 | ||||
|       semver: 7.5.4 | ||||
|  | @ -8632,7 +8627,7 @@ packages: | |||
|     dependencies: | ||||
|       '@typescript-eslint/types': 6.18.1 | ||||
|       '@typescript-eslint/visitor-keys': 6.18.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       globby: 11.1.0 | ||||
|       is-glob: 4.0.3 | ||||
|       minimatch: 9.0.3 | ||||
|  | @ -8712,7 +8707,7 @@ packages: | |||
|       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.5) | ||||
|       magic-string: 0.27.0 | ||||
|       react-refresh: 0.14.0 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: true | ||||
|  | @ -8724,7 +8719,7 @@ packages: | |||
|       vite: ^4.0.0 || ^5.0.0 | ||||
|       vue: ^3.2.25 | ||||
|     dependencies: | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|       vue: 3.4.15(typescript@5.3.3) | ||||
|     dev: true | ||||
| 
 | ||||
|  | @ -8735,7 +8730,7 @@ packages: | |||
|       vite: ^5.0.0 | ||||
|       vue: ^3.2.25 | ||||
|     dependencies: | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|       vue: 3.4.15(typescript@5.3.3) | ||||
|     dev: false | ||||
| 
 | ||||
|  | @ -8755,7 +8750,7 @@ packages: | |||
|       std-env: 3.7.0 | ||||
|       test-exclude: 6.0.0 | ||||
|       v8-to-istanbul: 9.2.0 | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0) | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: true | ||||
|  | @ -9096,7 +9091,7 @@ packages: | |||
|     engines: {node: '>= 6.0.0'} | ||||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
| 
 | ||||
|  | @ -9104,7 +9099,7 @@ packages: | |||
|     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} | ||||
|     engines: {node: '>= 14'} | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: false | ||||
|  | @ -9490,7 +9485,7 @@ packages: | |||
|     resolution: {integrity: sha512-TAlMYvOuwGyLK3PfBb5WKBXZmXz2fVCgv23d6zZFdle/q3gPjmxBaeuC0pY0Dzs5PWMSgfqqEZkrye19GlDTgw==} | ||||
|     dependencies: | ||||
|       archy: 1.0.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       fastq: 1.15.0 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -9894,6 +9889,7 @@ packages: | |||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       node-gyp-build: 4.6.0 | ||||
|     dev: false | ||||
| 
 | ||||
|   /bullmq@5.1.4: | ||||
|     resolution: {integrity: sha512-j/AjaPc8BhyrH7b2MyZpi4cUtGH8TJTxonZUmXEefmKU8z5DcldzmlXPief0P4+qvN0A7qwWZH3n0F+GsWgQkg==} | ||||
|  | @ -10438,6 +10434,7 @@ packages: | |||
| 
 | ||||
|   /commander@2.20.3: | ||||
|     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} | ||||
|     dev: false | ||||
| 
 | ||||
|   /commander@6.2.1: | ||||
|     resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} | ||||
|  | @ -10934,7 +10931,6 @@ packages: | |||
|     dependencies: | ||||
|       ms: 2.1.2 | ||||
|       supports-color: 5.5.0 | ||||
|     dev: true | ||||
| 
 | ||||
|   /debug@4.3.4(supports-color@8.1.1): | ||||
|     resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} | ||||
|  | @ -10947,6 +10943,7 @@ packages: | |||
|     dependencies: | ||||
|       ms: 2.1.2 | ||||
|       supports-color: 8.1.1 | ||||
|     dev: true | ||||
| 
 | ||||
|   /decamelize-keys@1.1.1: | ||||
|     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} | ||||
|  | @ -11089,6 +11086,7 @@ packages: | |||
| 
 | ||||
|   /defu@6.1.4: | ||||
|     resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /del@6.1.1: | ||||
|     resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} | ||||
|  | @ -11162,7 +11160,7 @@ packages: | |||
|     hasBin: true | ||||
|     dependencies: | ||||
|       address: 1.2.2 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: true | ||||
|  | @ -11486,7 +11484,7 @@ packages: | |||
|     peerDependencies: | ||||
|       esbuild: '>=0.12 <1' | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       esbuild: 0.18.20 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -11795,7 +11793,7 @@ packages: | |||
|       ajv: 6.12.6 | ||||
|       chalk: 4.1.2 | ||||
|       cross-spawn: 7.0.3 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       doctrine: 3.0.0 | ||||
|       escape-string-regexp: 4.0.0 | ||||
|       eslint-scope: 7.2.2 | ||||
|  | @ -11842,7 +11840,7 @@ packages: | |||
|       ajv: 6.12.6 | ||||
|       chalk: 4.1.2 | ||||
|       cross-spawn: 7.0.3 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       doctrine: 3.0.0 | ||||
|       escape-string-regexp: 4.0.0 | ||||
|       eslint-scope: 7.2.2 | ||||
|  | @ -12473,7 +12471,7 @@ packages: | |||
|       debug: | ||||
|         optional: true | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
| 
 | ||||
|   /for-each@0.3.3: | ||||
|     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} | ||||
|  | @ -13029,7 +13027,6 @@ packages: | |||
|   /has-flag@3.0.0: | ||||
|     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} | ||||
|     engines: {node: '>=4'} | ||||
|     dev: true | ||||
| 
 | ||||
|   /has-flag@4.0.0: | ||||
|     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} | ||||
|  | @ -13167,7 +13164,7 @@ packages: | |||
|     engines: {node: '>= 14'} | ||||
|     dependencies: | ||||
|       agent-base: 7.1.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: false | ||||
|  | @ -13227,7 +13224,7 @@ packages: | |||
|     engines: {node: '>= 6.0.0'} | ||||
|     dependencies: | ||||
|       agent-base: 5.1.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: true | ||||
|  | @ -13237,7 +13234,7 @@ packages: | |||
|     engines: {node: '>= 6'} | ||||
|     dependencies: | ||||
|       agent-base: 6.0.2 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
| 
 | ||||
|  | @ -13246,7 +13243,7 @@ packages: | |||
|     engines: {node: '>= 14'} | ||||
|     dependencies: | ||||
|       agent-base: 7.1.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|     dev: false | ||||
|  | @ -13406,7 +13403,7 @@ packages: | |||
|     dependencies: | ||||
|       '@ioredis/commands': 1.2.0 | ||||
|       cluster-key-slot: 1.1.2 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       denque: 2.1.0 | ||||
|       lodash.defaults: 4.2.0 | ||||
|       lodash.isarguments: 3.1.0 | ||||
|  | @ -13852,7 +13849,7 @@ packages: | |||
|     resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} | ||||
|     engines: {node: '>=10'} | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       istanbul-lib-coverage: 3.2.2 | ||||
|       source-map: 0.6.1 | ||||
|     transitivePeerDependencies: | ||||
|  | @ -15627,6 +15624,7 @@ packages: | |||
|     resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} | ||||
|     hasBin: true | ||||
|     requiresBuild: true | ||||
|     dev: false | ||||
| 
 | ||||
|   /node-gyp@10.0.1: | ||||
|     resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==} | ||||
|  | @ -17161,7 +17159,7 @@ packages: | |||
|     engines: {node: '>=8.16.0'} | ||||
|     dependencies: | ||||
|       '@types/mime-types': 2.1.4 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       extract-zip: 1.7.0 | ||||
|       https-proxy-agent: 4.0.0 | ||||
|       mime: 2.6.0 | ||||
|  | @ -18161,7 +18159,7 @@ packages: | |||
|     dependencies: | ||||
|       '@hapi/hoek': 10.0.1 | ||||
|       '@hapi/wreck': 18.0.1 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       joi: 17.7.0 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -18361,7 +18359,7 @@ packages: | |||
|     engines: {node: '>= 14'} | ||||
|     dependencies: | ||||
|       agent-base: 7.1.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       socks: 2.7.1 | ||||
|     transitivePeerDependencies: | ||||
|       - supports-color | ||||
|  | @ -18514,7 +18512,7 @@ packages: | |||
|       arg: 5.0.2 | ||||
|       bluebird: 3.7.2 | ||||
|       check-more-types: 2.24.0 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       execa: 5.1.1 | ||||
|       lazy-ass: 1.6.0 | ||||
|       ps-tree: 1.2.0 | ||||
|  | @ -18772,7 +18770,6 @@ packages: | |||
|     engines: {node: '>=4'} | ||||
|     dependencies: | ||||
|       has-flag: 3.0.0 | ||||
|     dev: true | ||||
| 
 | ||||
|   /supports-color@7.2.0: | ||||
|     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} | ||||
|  | @ -18935,6 +18932,7 @@ packages: | |||
|       acorn: 8.11.3 | ||||
|       commander: 2.20.3 | ||||
|       source-map-support: 0.5.21 | ||||
|     dev: false | ||||
| 
 | ||||
|   /test-exclude@6.0.0: | ||||
|     resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} | ||||
|  | @ -19393,7 +19391,7 @@ packages: | |||
|       chalk: 4.1.2 | ||||
|       cli-highlight: 2.1.11 | ||||
|       dayjs: 1.11.10 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       dotenv: 16.0.3 | ||||
|       glob: 10.3.10 | ||||
|       ioredis: 5.3.2 | ||||
|  | @ -19656,6 +19654,7 @@ packages: | |||
|     requiresBuild: true | ||||
|     dependencies: | ||||
|       node-gyp-build: 4.6.0 | ||||
|     dev: false | ||||
| 
 | ||||
|   /util-deprecate@1.0.2: | ||||
|     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} | ||||
|  | @ -19747,17 +19746,17 @@ packages: | |||
|       core-util-is: 1.0.2 | ||||
|       extsprintf: 1.3.0 | ||||
| 
 | ||||
|   /vite-node@0.34.6(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0): | ||||
|   /vite-node@0.34.6(@types/node@20.11.5)(sass@1.70.0): | ||||
|     resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} | ||||
|     engines: {node: '>=v14.18.0'} | ||||
|     hasBin: true | ||||
|     dependencies: | ||||
|       cac: 6.7.14 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       mlly: 1.5.0 | ||||
|       pathe: 1.1.2 | ||||
|       picocolors: 1.0.0 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - '@types/node' | ||||
|       - less | ||||
|  | @ -19773,7 +19772,7 @@ packages: | |||
|     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} | ||||
|     dev: true | ||||
| 
 | ||||
|   /vite@5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0): | ||||
|   /vite@5.0.12(@types/node@20.11.5)(sass@1.70.0): | ||||
|     resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} | ||||
|     engines: {node: ^18.0.0 || >=20.0.0} | ||||
|     hasBin: true | ||||
|  | @ -19806,7 +19805,6 @@ packages: | |||
|       postcss: 8.4.33 | ||||
|       rollup: 4.9.6 | ||||
|       sass: 1.70.0 | ||||
|       terser: 5.27.0 | ||||
|     optionalDependencies: | ||||
|       fsevents: 2.3.3 | ||||
| 
 | ||||
|  | @ -19817,12 +19815,12 @@ packages: | |||
|       vitest: '>=0.16.0' | ||||
|     dependencies: | ||||
|       cross-fetch: 3.1.5 | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0) | ||||
|       vitest: 0.34.6(happy-dom@10.0.3)(sass@1.70.0) | ||||
|     transitivePeerDependencies: | ||||
|       - encoding | ||||
|     dev: true | ||||
| 
 | ||||
|   /vitest@0.34.6(happy-dom@10.0.3)(sass@1.70.0)(terser@5.27.0): | ||||
|   /vitest@0.34.6(happy-dom@10.0.3)(sass@1.70.0): | ||||
|     resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} | ||||
|     engines: {node: '>=v14.18.0'} | ||||
|     hasBin: true | ||||
|  | @ -19865,7 +19863,7 @@ packages: | |||
|       acorn-walk: 8.3.2 | ||||
|       cac: 6.7.14 | ||||
|       chai: 4.3.10 | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       happy-dom: 10.0.3 | ||||
|       local-pkg: 0.4.3 | ||||
|       magic-string: 0.30.5 | ||||
|  | @ -19875,8 +19873,8 @@ packages: | |||
|       strip-literal: 1.3.0 | ||||
|       tinybench: 2.6.0 | ||||
|       tinypool: 0.7.0 | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite-node: 0.34.6(@types/node@20.11.5)(sass@1.70.0)(terser@5.27.0) | ||||
|       vite: 5.0.12(@types/node@20.11.5)(sass@1.70.0) | ||||
|       vite-node: 0.34.6(@types/node@20.11.5)(sass@1.70.0) | ||||
|       why-is-node-running: 2.2.2 | ||||
|     transitivePeerDependencies: | ||||
|       - less | ||||
|  | @ -19947,7 +19945,7 @@ packages: | |||
|     peerDependencies: | ||||
|       eslint: '>=6.0.0' | ||||
|     dependencies: | ||||
|       debug: 4.3.4(supports-color@8.1.1) | ||||
|       debug: 4.3.4(supports-color@5.5.0) | ||||
|       eslint: 8.56.0 | ||||
|       eslint-scope: 7.2.2 | ||||
|       eslint-visitor-keys: 3.4.3 | ||||
|  | @ -20277,6 +20275,19 @@ packages: | |||
|       async-limiter: 1.0.1 | ||||
|     dev: true | ||||
| 
 | ||||
|   /ws@8.16.0: | ||||
|     resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} | ||||
|     engines: {node: '>=10.0.0'} | ||||
|     peerDependencies: | ||||
|       bufferutil: ^4.0.1 | ||||
|       utf-8-validate: '>=5.0.2' | ||||
|     peerDependenciesMeta: | ||||
|       bufferutil: | ||||
|         optional: true | ||||
|       utf-8-validate: | ||||
|         optional: true | ||||
|     dev: true | ||||
| 
 | ||||
|   /ws@8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3): | ||||
|     resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} | ||||
|     engines: {node: '>=10.0.0'} | ||||
|  | @ -20291,6 +20302,7 @@ packages: | |||
|     dependencies: | ||||
|       bufferutil: 4.0.7 | ||||
|       utf-8-validate: 6.0.3 | ||||
|     dev: false | ||||
| 
 | ||||
|   /xev@3.0.2: | ||||
|     resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue