Merge branch 'misskey-dev:develop' into develop

This commit is contained in:
こけっち 2022-01-26 20:09:42 +09:00 committed by GitHub
commit 72058b30c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
935 changed files with 16205 additions and 32948 deletions

View File

@ -17,14 +17,14 @@ jobs:
services: services:
postgres: postgres:
image: postgres:12.2-alpine image: postgres:13
ports: ports:
- 54312:5432 - 54312:5432
env: env:
POSTGRES_DB: test-misskey POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
redis: redis:
image: redis:4.0-alpine image: redis:6
ports: ports:
- 56312:6379 - 56312:6379
@ -51,19 +51,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false
matrix: matrix:
node-version: [16.x] node-version: [16.x]
browser: [chrome]
services: services:
postgres: postgres:
image: postgres:12.2-alpine image: postgres:13
ports: ports:
- 54312:5432 - 54312:5432
env: env:
POSTGRES_DB: test-misskey POSTGRES_DB: test-misskey
POSTGRES_HOST_AUTH_METHOD: trust POSTGRES_HOST_AUTH_METHOD: trust
redis: redis:
image: redis:4.0-alpine image: redis:6
ports: ports:
- 56312:6379 - 56312:6379
@ -71,6 +73,12 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
with: with:
submodules: true submodules: true
# https://github.com/cypress-io/cypress-docker-images/issues/150
#- name: Install mplayer for FireFox
# run: sudo apt install mplayer -y
# if: ${{ matrix.browser == 'firefox' }}
#- uses: browser-actions/setup-firefox@latest
# if: ${{ matrix.browser == 'firefox' }}
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
@ -87,5 +95,24 @@ jobs:
run: cp .github/misskey/test.yml .config run: cp .github/misskey/test.yml .config
- name: Build - name: Build
run: yarn build run: yarn build
- name: Test # https://github.com/cypress-io/cypress/issues/4351#issuecomment-559489091
run: yarn e2e - name: ALSA Env
run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
- name: Cypress run
uses: cypress-io/github-action@v2
with:
install: false
start: npm run start:test
wait-on: 'http://localhost:61812'
headless: false
browser: ${{ matrix.browser }}
- uses: actions/upload-artifact@v2
if: failure()
with:
name: ${{ matrix.browser }}-cypress-screenshots
path: cypress/screenshots
- uses: actions/upload-artifact@v2
if: always()
with:
name: ${{ matrix.browser }}-cypress-videos
path: cypress/videos

View File

@ -4,6 +4,5 @@
"eg2.vscode-npm-script", "eg2.vscode-npm-script",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"johnsoncodehk.volar", "johnsoncodehk.volar",
"sysoev.language-stylus"
] ]
} }

View File

@ -7,6 +7,27 @@
--> -->
## 12.x.x (unreleased)
### Changes
- Room機能が削除されました
- 後日別リポジトリとして復活予定です
- リバーシ機能が削除されました
- 後日別リポジトリとして復活予定です
- Chat UIが削除されました
- ートに添付できるファイルの数が16に増えました
- カスタム絵文字にSVGを指定した場合、PNGに変換されて表示されるようになりました
### Improvements
- カスタム絵文字一括編集機能
- カスタム絵文字一括インポート
- 投稿フォームで一時的に投稿するアカウントを切り替えられるように
- Unifying Misskey-specific IRIs in JSON-LD `@context`
- セキュリティの向上
### Bugfixes
- アップロードエラー時の処理を修正
## 12.101.1 (2021/12/29) ## 12.101.1 (2021/12/29)
### Bugfixes ### Bugfixes

View File

@ -87,25 +87,18 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows).
## Vue ## Vue
Misskey uses Vue(v3) as its front-end framework. Misskey uses Vue(v3) as its front-end framework.
**When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.** - Use TypeScript.
Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. - **When creating a new component, please use the Composition API (with [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html) and [ref sugar](https://github.com/vuejs/rfcs/discussions/369)) instead of the Options API.**
- Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
## Adding MisskeyRoom items
* Use English for material, object and texture names.
* Use meter for unit of length.
* Your PR should include all source files (e.g. `.png`, `.blend`) of your models (for later editing).
* Your PR must include the glTF binary files (`.glb`) of your models.
* Add a locale key `room.furnitures.YOUR_ITEM` at [`/locales/ja-JP.yml`](/locales/ja-JP.yml).
* Add a furniture definition at [`src/client/scripts/room/furnitures.json5`](src/client/scripts/room/furnitures.json5).
If you have no experience on 3D modeling, we suggest to use the free 3DCG software [Blender](https://www.blender.org/).
You can find information on glTF 2.0 at [glTF 2.0 — Blender Manual]( https://docs.blender.org/manual/en/dev/addons/io_scene_gltf2.html).
## Notes ## Notes
### How to resolve conflictions occurred at yarn.lock? ### How to resolve conflictions occurred at yarn.lock?
Just execute `yarn` to fix it. Just execute `yarn` to fix it.
### INSERTするときにはsaveではなくinsertを使用する
#6441
### placeholder ### placeholder
SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない
例えば 例えば

View File

@ -41,8 +41,6 @@ describe('After setup instance', () => {
username: 'admin', username: 'admin',
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin');
}); });
afterEach(() => { afterEach(() => {
@ -82,15 +80,11 @@ describe('After user signup', () => {
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin').then(() => { // ユーザー作成
// ユーザー作成 cy.request('POST', '/api/signup', {
cy.request('POST', '/api/signup', { username: 'alice',
username: 'alice', password: 'alice1234',
password: 'alice1234', }).its('body').as('alice');
}).its('body').as('alice');
});
cy.get('@alice');
}); });
afterEach(() => { afterEach(() => {
@ -145,27 +139,21 @@ describe('After user singed in', () => {
password: 'pass', password: 'pass',
}).its('body').as('admin'); }).its('body').as('admin');
cy.get('@admin').then(() => { // ユーザー作成
// ユーザー作成 cy.request('POST', '/api/signup', {
cy.request('POST', '/api/signup', { username: 'alice',
username: 'alice', password: 'alice1234',
password: 'alice1234', }).its('body').as('alice');
}).its('body').as('alice');
});
cy.get('@alice').then(() => { cy.visit('/');
cy.visit('/');
cy.intercept('POST', '/api/signin').as('signin'); cy.intercept('POST', '/api/signin').as('signin');
cy.get('[data-cy-signin]').click(); cy.get('[data-cy-signin]').click();
cy.get('[data-cy-signin-username] input').type('alice'); cy.get('[data-cy-signin-username] input').type('alice');
cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
cy.wait('@signin').as('signedIn'); cy.wait('@signin').as('signedIn');
});
cy.get('@signedIn');
}); });
afterEach(() => { afterEach(() => {

View File

@ -20,7 +20,13 @@ import './commands'
// require('./commands') // require('./commands')
Cypress.on('uncaught:exception', (err, runnable) => { Cypress.on('uncaught:exception', (err, runnable) => {
if (err.message.includes('ResizeObserver loop limit exceeded')) { if ([
return false // Chrome
} 'ResizeObserver loop limit exceeded',
// Firefox
'ResizeObserver loop completed with undelivered notifications',
].some(msg => err.message.includes(msg))) {
return false;
}
}); });

View File

@ -40,6 +40,7 @@ services:
# image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2 # image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
# environment: # environment:
# - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
# - "TAKE_FILE_OWNERSHIP=111"
# networks: # networks:
# - internal_network # - internal_network
# volumes: # volumes:

View File

@ -242,7 +242,6 @@ uploadFromUrlDescription: "アップロードしたいファイルのURL"
uploadFromUrlRequested: "アップロードをリクエストしました" uploadFromUrlRequested: "アップロードをリクエストしました"
uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。" uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がかかる場合があります。"
explore: "みつける" explore: "みつける"
games: "Misskey Games"
messageRead: "既読" messageRead: "既読"
noMoreHistory: "これより過去の履歴はありません" noMoreHistory: "これより過去の履歴はありません"
startMessaging: "チャットを開始" startMessaging: "チャットを開始"
@ -537,7 +536,6 @@ yourAccountSuspendedDescription: "このアカウントは、サーバーの利
menu: "メニュー" menu: "メニュー"
divider: "分割線" divider: "分割線"
addItem: "項目を追加" addItem: "項目を追加"
rooms: "ルーム"
relays: "リレー" relays: "リレー"
addRelay: "リレーの追加" addRelay: "リレーの追加"
inboxUrl: "inboxのURL" inboxUrl: "inboxのURL"
@ -621,8 +619,11 @@ reportAbuse: "通報"
reportAbuseOf: "{name}を通報する" reportAbuseOf: "{name}を通報する"
fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のートがある場合はそのURLも記入してください。" fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のートがある場合はそのURLも記入してください。"
abuseReported: "内容が送信されました。ご報告ありがとうございました。" abuseReported: "内容が送信されました。ご報告ありがとうございました。"
reporter: "通報者"
reporteeOrigin: "通報先" reporteeOrigin: "通報先"
reporterOrigin: "通報元" reporterOrigin: "通報元"
forwardReport: "リモートインスタンスに通報を転送する"
forwardReportIsAnonymous: "リモートインスタンスからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。"
send: "送信" send: "送信"
abuseMarkAsResolved: "対応済みにする" abuseMarkAsResolved: "対応済みにする"
openInNewTab: "新しいタブで開く" openInNewTab: "新しいタブで開く"
@ -670,7 +671,6 @@ emailVerified: "メールアドレスが確認されました"
noteFavoritesCount: "お気に入りノートの数" noteFavoritesCount: "お気に入りノートの数"
pageLikesCount: "Pageにいいねした数" pageLikesCount: "Pageにいいねした数"
pageLikedCount: "Pageにいいねされた数" pageLikedCount: "Pageにいいねされた数"
reversiCount: "リバーシの対局数"
contact: "連絡先" contact: "連絡先"
useSystemFont: "システムのデフォルトのフォントを使う" useSystemFont: "システムのデフォルトのフォントを使う"
clips: "クリップ" clips: "クリップ"
@ -743,9 +743,10 @@ online: "オンライン"
active: "アクティブ" active: "アクティブ"
offline: "オフライン" offline: "オフライン"
notRecommended: "非推奨" notRecommended: "非推奨"
botProtection: "Bot防御" botProtection: "Botプロテクション"
instanceBlocking: "インスタンスブロック" instanceBlocking: "インスタンスブロック"
selectAccount: "アカウントを選択" selectAccount: "アカウントを選択"
switchAccount: "アカウントを切り替え"
enabled: "有効" enabled: "有効"
disabled: "無効" disabled: "無効"
quickAction: "クイックアクション" quickAction: "クイックアクション"
@ -754,7 +755,7 @@ administration: "管理"
accounts: "アカウント" accounts: "アカウント"
switch: "切り替え" switch: "切り替え"
noMaintainerInformationWarning: "管理者情報が設定されていません。" noMaintainerInformationWarning: "管理者情報が設定されていません。"
noBotProtectionWarning: "Bot防御が設定されていません。" noBotProtectionWarning: "Botプロテクションが設定されていません。"
configure: "設定する" configure: "設定する"
postToGallery: "ギャラリーへ投稿" postToGallery: "ギャラリーへ投稿"
gallery: "ギャラリー" gallery: "ギャラリー"
@ -958,40 +959,6 @@ _mfm:
rotate: "回転" rotate: "回転"
rotateDescription: "指定した角度で回転させます。" rotateDescription: "指定した角度で回転させます。"
_reversi:
reversi: "リバーシ"
gameSettings: "対局の設定"
chooseBoard: "ボードを選択"
blackOrWhite: "先行/後攻"
blackIs: "{name}が黒(先行)"
rules: "ルール"
botSettings: "Botのオプション"
thisGameIsStartedSoon: "対局は数秒後に開始されます"
waitingForOther: "相手の準備が完了するのを待っています"
waitingForMe: "あなたの準備が完了するのを待っています"
waitingBoth: "準備してください"
ready: "準備完了"
cancelReady: "準備を再開"
opponentTurn: "相手のターンです"
myTurn: "あなたのターンです"
turnOf: "{name}のターンです"
pastTurnOf: "{name}のターン"
surrender: "投了"
surrendered: "投了により"
drawn: "引き分け"
won: "{name}の勝ち"
black: "黒"
white: "白"
total: "合計"
turnCount: "{count}ターン目"
myGames: "自分の対局"
allGames: "みんなの対局"
ended: "終了"
playing: "対局中"
isLlotheo: "石の少ない方が勝ち(ロセオ)"
loopedMap: "ループマップ"
canPutEverywhere: "どこでも置けるモード"
_instanceTicker: _instanceTicker:
none: "表示しない" none: "表示しない"
remote: "リモートユーザーに表示" remote: "リモートユーザーに表示"
@ -1119,8 +1086,6 @@ _sfx:
chatBg: "チャット(バックグラウンド)" chatBg: "チャット(バックグラウンド)"
antenna: "アンテナ受信" antenna: "アンテナ受信"
channel: "チャンネル通知" channel: "チャンネル通知"
reversiPutBlack: "リバーシ: 黒が打ったとき"
reversiPutWhite: "リバーシ: 白が打ったとき"
_ago: _ago:
unknown: "謎" unknown: "謎"
@ -1362,69 +1327,6 @@ _timelines:
social: "ソーシャル" social: "ソーシャル"
global: "グローバル" global: "グローバル"
_rooms:
roomOf: "{user}のルーム"
addFurniture: "家具を置く"
translate: "移動"
rotate: "回転"
exit: "戻る"
remove: "しまう"
clear: "片付け"
clearConfirm: "全ての家具をしまいますか?"
leaveConfirm: "未保存の変更があります、移動しますか?"
chooseImage: "画像を選択"
roomType: "部屋のタイプ"
carpetColor: "床の色"
_roomType:
default: "デフォルト"
washitsu: "和室"
_furnitures:
milk: "牛乳パック"
bed: "ベッド"
low-table: "ローテーブル"
desk: "デスク"
chair: "チェア"
chair2: "チェア2"
fan: "換気扇"
pc: "パソコン"
plant: "観葉植物"
plant2: "観葉植物2"
eraser: "消しゴム"
pencil: "鉛筆"
pudding: "プリン"
cardboard-box: "段ボール箱"
cardboard-box2: "段ボール箱2"
cardboard-box3: "段ボール箱3"
book: "本"
book2: "本2"
piano: "ピアノ"
facial-tissue: "ティッシュボックス"
server: "サーバー"
moon: "月"
corkboard: "コルクボード"
mousepad: "マウスパッド"
monitor: "モニター"
keyboard: "キーボード"
carpet-stripe: "カーペット(縞)"
mat: "マット"
color-box: "カラーボックス"
wall-clock: "壁掛け時計"
photoframe: "額縁"
cube: "キューブ"
tv: "テレビ"
pinguin: "ピンギン"
rubik-cube: "ルービックキューブ"
poster-h: "ポスター(横長)"
poster-v: "ポスター(縦長)"
sofa: "ソファ"
spiral: "螺旋階段"
bin: "ゴミ箱"
cup-noodle: "カップ麺"
holo-display: "ホログラフィックディスプレイ"
energy-drink: "エナジードリンク"
doll-ai: "藍ちゃん人形"
banknote: "札束"
_pages: _pages:
newPage: "ページの作成" newPage: "ページの作成"
editPage: "ページの編集" editPage: "ページの編集"

View File

@ -42,12 +42,12 @@
"js-yaml": "4.1.0" "js-yaml": "4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.54", "@redocly/openapi-core": "1.0.0-beta.79",
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.20",
"@typescript-eslint/parser": "5.4.0", "@typescript-eslint/parser": "5.10.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "9.1.0", "cypress": "9.3.1",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.14.0",
"typescript": "4.5.2" "typescript": "4.5.5"
} }
} }

View File

@ -0,0 +1,13 @@
const { QueryRunner } = require('typeorm');
module.exports = class forwardedReport1637320813000 {
name = 'forwardedReport1637320813000';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "forwarded" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "forwarded"`);
}
};

View File

@ -0,0 +1,15 @@
const { MigrationInterface, QueryRunner } = require("typeorm");
module.exports = class emojiUrl1642611822809 {
name = 'emojiUrl1642611822809'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "url" TO "originalUrl"`);
await queryRunner.query(`ALTER TABLE "emoji" ADD "publicUrl" character varying(512) NOT NULL DEFAULT ''`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "publicUrl"`);
await queryRunner.query(`ALTER TABLE "emoji" RENAME COLUMN "originalUrl" TO "url"`);
}
}

View File

@ -0,0 +1,13 @@
const { MigrationInterface, QueryRunner } = require("typeorm");
module.exports = class driveFileWebpublicType1642613870898 {
name = 'driveFileWebpublicType1642613870898'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" ADD "webpublicType" character varying(128)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "drive_file" DROP COLUMN "webpublicType"`);
}
}

View File

@ -22,87 +22,78 @@
"@sinonjs/fake-timers": "7.1.2", "@sinonjs/fake-timers": "7.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.5", "@types/bull": "3.15.7",
"@types/cbor": "6.0.0", "@types/cbor": "6.0.0",
"@types/dateformat": "3.0.1", "@types/dateformat": "3.0.1",
"@types/escape-regexp": "0.0.0", "@types/escape-regexp": "0.0.1",
"@types/glob": "7.2.0", "@types/glob": "7.2.0",
"@types/is-url": "1.2.30", "@types/is-url": "1.2.30",
"@types/js-yaml": "4.0.4", "@types/js-yaml": "4.0.5",
"@types/jsdom": "16.2.13", "@types/jsdom": "16.2.14",
"@types/jsonld": "1.5.6", "@types/jsonld": "1.5.6",
"@types/koa": "2.13.4", "@types/koa": "2.13.4",
"@types/koa-bodyparser": "4.3.3", "@types/koa-bodyparser": "4.3.5",
"@types/koa-cors": "0.0.2", "@types/koa-cors": "0.0.2",
"@types/koa-favicon": "2.0.21", "@types/koa-favicon": "2.0.21",
"@types/koa-logger": "3.1.2", "@types/koa-logger": "3.1.2",
"@types/koa-mount": "4.0.1", "@types/koa-mount": "4.0.1",
"@types/koa-send": "4.1.3", "@types/koa-send": "4.1.3",
"@types/koa-views": "7.0.0", "@types/koa-views": "7.0.0",
"@types/koa__cors": "3.0.3", "@types/koa__cors": "3.1.1",
"@types/koa__multer": "2.0.4", "@types/koa__multer": "2.0.4",
"@types/koa__router": "8.0.8", "@types/koa__router": "8.0.11",
"@types/mocha": "8.2.3", "@types/mocha": "8.2.3",
"@types/node": "16.11.7", "@types/node": "17.0.10",
"@types/node-fetch": "2.5.12", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/parse5": "6.0.3", "@types/parse5": "6.0.3",
"@types/portscanner": "2.1.1", "@types/portscanner": "2.1.1",
"@types/pug": "2.0.5", "@types/pug": "2.0.6",
"@types/punycode": "2.1.0", "@types/punycode": "2.1.0",
"@types/qrcode": "1.4.1", "@types/qrcode": "1.4.2",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.2", "@types/ratelimiter": "3.4.3",
"@types/redis": "2.8.32", "@types/redis": "4.0.11",
"@types/rename": "1.0.4", "@types/rename": "1.0.4",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/sanitize-html": "2.5.0", "@types/sanitize-html": "2.6.2",
"@types/seedrandom": "2.4.28", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.29.3", "@types/sharp": "0.29.5",
"@types/sinonjs__fake-timers": "6.0.4", "@types/sinonjs__fake-timers": "6.0.4",
"@types/speakeasy": "2.0.6", "@types/speakeasy": "2.0.7",
"@types/throttle-debounce": "2.1.0", "@types/throttle-debounce": "2.1.0",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.2", "@types/tmp": "0.2.3",
"@types/uuid": "8.3.1", "@types/uuid": "8.3.4",
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/webpack": "5.28.0", "@types/webpack": "5.28.0",
"@types/webpack-stream": "3.2.12", "@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.4", "@types/websocket": "1.0.4",
"@types/ws": "8.2.0", "@types/ws": "8.2.2",
"@typescript-eslint/eslint-plugin": "5.3.1", "@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.1.0", "@typescript-eslint/parser": "5.10.0",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"archiver": "5.3.0", "archiver": "5.3.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.4",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1013.0", "aws-sdk": "2.1061.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.4", "blurhash": "1.1.4",
"broadcast-channel": "4.5.0", "broadcast-channel": "4.9.0",
"bull": "4.1.0", "bull": "4.2.1",
"cacheable-lookup": "6.0.4", "cacheable-lookup": "6.0.4",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "4.1.2", "chalk": "4.1.2",
"chart.js": "3.6.0",
"chartjs-adapter-date-fns": "2.0.0",
"chartjs-plugin-zoom": "1.1.1",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"compare-versions": "3.6.0", "content-disposition": "0.5.4",
"content-disposition": "0.5.3",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "6.5.1",
"cssnano": "5.0.10",
"date-fns": "2.25.0",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"deep-email-validator": "0.1.18", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "8.2.0", "eslint": "8.7.0",
"eslint-plugin-import": "2.25.3", "eslint-plugin-import": "2.25.4",
"eslint-plugin-vue": "8.0.3",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "16.5.3", "file-type": "16.5.3",
@ -110,11 +101,9 @@
"glob": "7.2.0", "glob": "7.2.0",
"got": "11.8.2", "got": "11.8.2",
"hpagent": "0.1.2", "hpagent": "0.1.2",
"http-signature": "1.3.5", "http-signature": "1.3.6",
"idb-keyval": "5.1.3",
"insert-text-at-cursor": "0.3.0",
"ip-cidr": "3.0.4", "ip-cidr": "3.0.4",
"is-svg": "4.3.1", "is-svg": "4.3.2",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "16.7.0", "jsdom": "16.7.0",
"json5": "2.2.0", "json5": "2.2.0",
@ -131,30 +120,29 @@
"koa-slow": "2.1.0", "koa-slow": "2.1.0",
"koa-views": "7.0.2", "koa-views": "7.0.2",
"langmap": "0.0.16", "langmap": "0.0.16",
"mfm-js": "0.20.0", "mfm-js": "0.21.0",
"mime-types": "2.1.34", "mime-types": "2.1.34",
"misskey-js": "0.0.8", "misskey-js": "0.0.13",
"mocha": "8.4.0", "mocha": "8.4.0",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"multer": "1.4.3", "multer": "1.4.4",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"nodemailer": "6.7.0", "nodemailer": "6.7.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.7.1", "pg": "8.7.1",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"prismjs": "1.25.0",
"private-ip": "2.3.3", "private-ip": "2.3.3",
"probe-image-size": "7.2.1", "probe-image-size": "7.2.2",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"pug": "3.0.2", "pug": "3.0.2",
"punycode": "2.1.1", "punycode": "2.1.1",
"pureimage": "0.3.5", "pureimage": "0.3.8",
"qrcode": "1.4.4", "qrcode": "1.5.0",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "1.16.0", "re2": "1.17.3",
"redis": "3.1.2", "redis": "3.1.2",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@ -163,9 +151,9 @@
"require-all": "3.0.0", "require-all": "3.0.0",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "2.5.3", "sanitize-html": "2.6.1",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.29.2", "sharp": "0.29.3",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
@ -179,20 +167,21 @@
"ts-loader": "9.2.6", "ts-loader": "9.2.6",
"ts-node": "10.4.0", "ts-node": "10.4.0",
"tsc-alias": "1.4.1", "tsc-alias": "1.4.1",
"tsconfig-paths": "3.11.0", "tsconfig-paths": "3.12.0",
"twemoji-parser": "13.1.0", "twemoji-parser": "13.1.0",
"typeorm": "0.2.39", "typeorm": "0.2.41",
"typescript": "4.4.4", "typescript": "4.5.5",
"ulid": "2.3.0", "ulid": "2.3.0",
"unzipper": "0.10.11",
"uuid": "8.3.2", "uuid": "8.3.2",
"web-push": "3.4.5", "web-push": "3.4.5",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "8.2.3", "ws": "8.4.2",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@redocly/openapi-core": "1.0.0-beta.54", "@redocly/openapi-core": "1.0.0-beta.79",
"@types/fluent-ffmpeg": "2.1.17", "@types/fluent-ffmpeg": "2.1.20",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"execa": "6.0.0" "execa": "6.0.0"
} }

View File

@ -1,2 +1,47 @@
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
// ブラウザで直接表示することを許可するファイルの種類のリスト
// ここに含まれないものは application/octet-stream としてレスポンスされる
// SVGはXSSを生むので許可しない
export const FILE_TYPE_BROWSERSAFE = [
// Images
'image/png',
'image/gif',
'image/jpeg',
'image/webp',
'image/apng',
'image/bmp',
'image/tiff',
'image/x-icon',
// OggS
'audio/opus',
'video/ogg',
'audio/ogg',
'application/ogg',
// ISO/IEC base media file format
'video/quicktime',
'video/mp4',
'audio/mp4',
'video/x-m4v',
'audio/x-m4a',
'video/3gpp',
'video/3gpp2',
'video/mpeg',
'audio/mpeg',
'video/webm',
'audio/webm',
'audio/aac',
'audio/x-flac',
'audio/vnd.wave',
];
/*
https://github.com/sindresorhus/file-type/blob/main/supported.js
https://github.com/sindresorhus/file-type/blob/main/core.js
https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
*/

View File

@ -40,8 +40,6 @@ import { Signin } from '@/models/entities/signin';
import { AuthSession } from '@/models/entities/auth-session'; import { AuthSession } from '@/models/entities/auth-session';
import { FollowRequest } from '@/models/entities/follow-request'; import { FollowRequest } from '@/models/entities/follow-request';
import { Emoji } from '@/models/entities/emoji'; import { Emoji } from '@/models/entities/emoji';
import { ReversiGame } from '@/models/entities/games/reversi/game';
import { ReversiMatching } from '@/models/entities/games/reversi/matching';
import { UserNotePining } from '@/models/entities/user-note-pining'; import { UserNotePining } from '@/models/entities/user-note-pining';
import { Poll } from '@/models/entities/poll'; import { Poll } from '@/models/entities/poll';
import { UserKeypair } from '@/models/entities/user-keypair'; import { UserKeypair } from '@/models/entities/user-keypair';
@ -166,8 +164,6 @@ export const entities = [
AntennaNote, AntennaNote,
PromoNote, PromoNote,
PromoRead, PromoRead,
ReversiGame,
ReversiMatching,
Relay, Relay,
MutedNote, MutedNote,
Channel, Channel,
@ -224,7 +220,9 @@ export async function resetDb() {
WHERE nspname NOT IN ('pg_catalog', 'information_schema') WHERE nspname NOT IN ('pg_catalog', 'information_schema')
AND C.relkind = 'r' AND C.relkind = 'r'
AND nspname !~ '^pg_toast';`); AND nspname !~ '^pg_toast';`);
await Promise.all(tables.map(t => t.table).map(x => conn.query(`DELETE FROM "${x}" CASCADE`))); for (const table of tables) {
await conn.query(`DELETE FROM "${table.table}" CASCADE`);
}
}; };
for (let i = 1; i <= 3; i++) { for (let i = 1; i <= 3; i++) {

View File

@ -1,263 +0,0 @@
import { count, concat } from '@/prelude/array';
// MISSKEY REVERSI ENGINE
/**
* true ...
* false ...
*/
export type Color = boolean;
const BLACK = true;
const WHITE = false;
export type MapPixel = 'null' | 'empty';
export type Options = {
isLlotheo: boolean;
canPutEverywhere: boolean;
loopedBoard: boolean;
};
export type Undo = {
/**
*
*/
color: Color;
/**
*
*/
pos: number;
/**
*
*/
effects: number[];
/**
*
*/
turn: Color | null;
};
/**
*
*/
export default class Reversi {
public map: MapPixel[];
public mapWidth: number;
public mapHeight: number;
public board: (Color | null | undefined)[];
public turn: Color | null = BLACK;
public opts: Options;
public prevPos = -1;
public prevColor: Color | null = null;
private logs: Undo[] = [];
/**
*
*/
constructor(map: string[], opts: Options) {
//#region binds
this.put = this.put.bind(this);
//#endregion
//#region Options
this.opts = opts;
if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
//#endregion
//#region Parse map data
this.mapWidth = map[0].length;
this.mapHeight = map.length;
const mapData = map.join('');
this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
//#endregion
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
}
/**
*
*/
public get blackCount() {
return count(BLACK, this.board);
}
/**
*
*/
public get whiteCount() {
return count(WHITE, this.board);
}
public transformPosToXy(pos: number): number[] {
const x = pos % this.mapWidth;
const y = Math.floor(pos / this.mapWidth);
return [x, y];
}
public transformXyToPos(x: number, y: number): number {
return x + (y * this.mapWidth);
}
/**
*
* @param color
* @param pos
*/
public put(color: Color, pos: number) {
this.prevPos = pos;
this.prevColor = color;
this.board[pos] = color;
// 反転させられる石を取得
const effects = this.effects(color, pos);
// 反転させる
for (const pos of effects) {
this.board[pos] = color;
}
const turn = this.turn;
this.logs.push({
color,
pos,
effects,
turn,
});
this.calcTurn();
}
private calcTurn() {
// ターン計算
this.turn =
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
this.canPutSomewhere(this.prevColor!) ? this.prevColor :
null;
}
public undo() {
const undo = this.logs.pop()!;
this.prevColor = undo.color;
this.prevPos = undo.pos;
this.board[undo.pos] = null;
for (const pos of undo.effects) {
const color = this.board[pos];
this.board[pos] = !color;
}
this.turn = undo.turn;
}
/**
*
* @param pos
*/
public mapDataGet(pos: number): MapPixel {
const [x, y] = this.transformPosToXy(pos);
return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
}
/**
*
*/
public puttablePlaces(color: Color): number[] {
return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
}
/**
*
*/
public canPutSomewhere(color: Color): boolean {
return this.puttablePlaces(color).length > 0;
}
/**
*
* @param color
* @param pos
*/
public canPut(color: Color, pos: number): boolean {
return (
this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
}
/**
*
* @param color
* @param initPos
*/
public effects(color: Color, initPos: number): number[] {
const enemyColor = !color;
const diffVectors: [number, number][] = [
[ 0, -1], // 上
[ +1, -1], // 右上
[ +1, 0], // 右
[ +1, +1], // 右下
[ 0, +1], // 下
[ -1, +1], // 左下
[ -1, 0], // 左
[ -1, -1], // 左上
];
const effectsInLine = ([dx, dy]: [number, number]): number[] => {
const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
let [x, y] = this.transformPosToXy(initPos);
while (true) {
[x, y] = nextPos(x, y);
// 座標が指し示す位置がボード外に出たとき
if (this.opts.loopedBoard && this.transformXyToPos(
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) {
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
return found;
} else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) {
return []; // 挟めないことが確定 (盤面外に到達)
}
const pos = this.transformXyToPos(x, y);
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
const stone = this.board[pos];
if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
}
};
return concat(diffVectors.map(effectsInLine));
}
/**
*
*/
public get isEnded(): boolean {
return this.turn === null;
}
/**
* (null = )
*/
public get winner(): Color | null {
return this.isEnded ?
this.blackCount == this.whiteCount ? null :
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
undefined as never;
}
}

View File

@ -1,896 +0,0 @@
/**
*
*
* :
* () ...
* - ...
* b ...
* w ...
*/
export type Map = {
name?: string;
category?: string;
author?: string;
data: string[];
};
export const fourfour: Map = {
name: '4x4',
category: '4x4',
data: [
'----',
'-wb-',
'-bw-',
'----',
],
};
export const sixsix: Map = {
name: '6x6',
category: '6x6',
data: [
'------',
'------',
'--wb--',
'--bw--',
'------',
'------',
],
};
export const roundedSixsix: Map = {
name: '6x6 rounded',
category: '6x6',
author: 'syuilo',
data: [
' ---- ',
'------',
'--wb--',
'--bw--',
'------',
' ---- ',
],
};
export const roundedSixsix2: Map = {
name: '6x6 rounded 2',
category: '6x6',
author: 'syuilo',
data: [
' -- ',
' ---- ',
'--wb--',
'--bw--',
' ---- ',
' -- ',
],
};
export const eighteight: Map = {
name: '8x8',
category: '8x8',
data: [
'--------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const eighteightH1: Map = {
name: '8x8 handicap 1',
category: '8x8',
data: [
'b-------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const eighteightH2: Map = {
name: '8x8 handicap 2',
category: '8x8',
data: [
'b-------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'-------b',
],
};
export const eighteightH3: Map = {
name: '8x8 handicap 3',
category: '8x8',
data: [
'b------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'-------b',
],
};
export const eighteightH4: Map = {
name: '8x8 handicap 4',
category: '8x8',
data: [
'b------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'b------b',
],
};
export const eighteightH28: Map = {
name: '8x8 handicap 28',
category: '8x8',
data: [
'bbbbbbbb',
'b------b',
'b------b',
'b--wb--b',
'b--bw--b',
'b------b',
'b------b',
'bbbbbbbb',
],
};
export const roundedEighteight: Map = {
name: '8x8 rounded',
category: '8x8',
author: 'syuilo',
data: [
' ------ ',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
' ------ ',
],
};
export const roundedEighteight2: Map = {
name: '8x8 rounded 2',
category: '8x8',
author: 'syuilo',
data: [
' ---- ',
' ------ ',
'--------',
'---wb---',
'---bw---',
'--------',
' ------ ',
' ---- ',
],
};
export const roundedEighteight3: Map = {
name: '8x8 rounded 3',
category: '8x8',
author: 'syuilo',
data: [
' -- ',
' ---- ',
' ------ ',
'---wb---',
'---bw---',
' ------ ',
' ---- ',
' -- ',
],
};
export const eighteightWithNotch: Map = {
name: '8x8 with notch',
category: '8x8',
author: 'syuilo',
data: [
'--- ---',
'--------',
'--------',
' --wb-- ',
' --bw-- ',
'--------',
'--------',
'--- ---',
],
};
export const eighteightWithSomeHoles: Map = {
name: '8x8 with some holes',
category: '8x8',
author: 'syuilo',
data: [
'--- ----',
'----- --',
'-- -----',
'---wb---',
'---bw- -',
' -------',
'--- ----',
'--------',
],
};
export const circle: Map = {
name: 'Circle',
category: '8x8',
author: 'syuilo',
data: [
' -- ',
' ------ ',
' ------ ',
'---wb---',
'---bw---',
' ------ ',
' ------ ',
' -- ',
],
};
export const smile: Map = {
name: 'Smile',
category: '8x8',
author: 'syuilo',
data: [
' ------ ',
'--------',
'-- -- --',
'---wb---',
'-- bw --',
'--- ---',
'--------',
' ------ ',
],
};
export const window: Map = {
name: 'Window',
category: '8x8',
author: 'syuilo',
data: [
'--------',
'- -- -',
'- -- -',
'---wb---',
'---bw---',
'- -- -',
'- -- -',
'--------',
],
};
export const reserved: Map = {
name: 'Reserved',
category: '8x8',
author: 'Aya',
data: [
'w------b',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'b------w',
],
};
export const x: Map = {
name: 'X',
category: '8x8',
author: 'Aya',
data: [
'w------b',
'-w----b-',
'--w--b--',
'---wb---',
'---bw---',
'--b--w--',
'-b----w-',
'b------w',
],
};
export const parallel: Map = {
name: 'Parallel',
category: '8x8',
author: 'Aya',
data: [
'--------',
'--------',
'--------',
'---bb---',
'---ww---',
'--------',
'--------',
'--------',
],
};
export const lackOfBlack: Map = {
name: 'Lack of Black',
category: '8x8',
data: [
'--------',
'--------',
'--------',
'---w----',
'---bw---',
'--------',
'--------',
'--------',
],
};
export const squareParty: Map = {
name: 'Square Party',
category: '8x8',
author: 'syuilo',
data: [
'--------',
'-wwwbbb-',
'-w-wb-b-',
'-wwwbbb-',
'-bbbwww-',
'-b-bw-w-',
'-bbbwww-',
'--------',
],
};
export const minesweeper: Map = {
name: 'Minesweeper',
category: '8x8',
author: 'syuilo',
data: [
'b-b--w-w',
'-w-wb-b-',
'w-b--w-b',
'-b-wb-w-',
'-w-bw-b-',
'b-w--b-w',
'-b-bw-w-',
'w-w--b-b',
],
};
export const tenthtenth: Map = {
name: '10x10',
category: '10x10',
data: [
'----------',
'----------',
'----------',
'----------',
'----wb----',
'----bw----',
'----------',
'----------',
'----------',
'----------',
],
};
export const hole: Map = {
name: 'The Hole',
category: '10x10',
author: 'syuilo',
data: [
'----------',
'----------',
'--wb--wb--',
'--bw--bw--',
'---- ----',
'---- ----',
'--wb--wb--',
'--bw--bw--',
'----------',
'----------',
],
};
export const grid: Map = {
name: 'Grid',
category: '10x10',
author: 'syuilo',
data: [
'----------',
'- - -- - -',
'----------',
'- - -- - -',
'----wb----',
'----bw----',
'- - -- - -',
'----------',
'- - -- - -',
'----------',
],
};
export const cross: Map = {
name: 'Cross',
category: '10x10',
author: 'Aya',
data: [
' ---- ',
' ---- ',
' ---- ',
'----------',
'----wb----',
'----bw----',
'----------',
' ---- ',
' ---- ',
' ---- ',
],
};
export const charX: Map = {
name: 'Char X',
category: '10x10',
author: 'syuilo',
data: [
'--- ---',
'---- ----',
'----------',
' -------- ',
' --wb-- ',
' --bw-- ',
' -------- ',
'----------',
'---- ----',
'--- ---',
],
};
export const charY: Map = {
name: 'Char Y',
category: '10x10',
author: 'syuilo',
data: [
'--- ---',
'---- ----',
'----------',
' -------- ',
' --wb-- ',
' --bw-- ',
' ------ ',
' ------ ',
' ------ ',
' ------ ',
],
};
export const walls: Map = {
name: 'Walls',
category: '10x10',
author: 'Aya',
data: [
' bbbbbbbb ',
'w--------w',
'w--------w',
'w--------w',
'w---wb---w',
'w---bw---w',
'w--------w',
'w--------w',
'w--------w',
' bbbbbbbb ',
],
};
export const cpu: Map = {
name: 'CPU',
category: '10x10',
author: 'syuilo',
data: [
' b b b b ',
'w--------w',
' -------- ',
'w--------w',
' ---wb--- ',
' ---bw--- ',
'w--------w',
' -------- ',
'w--------w',
' b b b b ',
],
};
export const checker: Map = {
name: 'Checker',
category: '10x10',
author: 'Aya',
data: [
'----------',
'----------',
'----------',
'---wbwb---',
'---bwbw---',
'---wbwb---',
'---bwbw---',
'----------',
'----------',
'----------',
],
};
export const japaneseCurry: Map = {
name: 'Japanese curry',
category: '10x10',
author: 'syuilo',
data: [
'w-b-b-b-b-',
'-w-b-b-b-b',
'w-w-b-b-b-',
'-w-w-b-b-b',
'w-w-wwb-b-',
'-w-wbb-b-b',
'w-w-w-b-b-',
'-w-w-w-b-b',
'w-w-w-w-b-',
'-w-w-w-w-b',
],
};
export const mosaic: Map = {
name: 'Mosaic',
category: '10x10',
author: 'syuilo',
data: [
'- - - - - ',
' - - - - -',
'- - - - - ',
' - w w - -',
'- - b b - ',
' - w w - -',
'- - b b - ',
' - - - - -',
'- - - - - ',
' - - - - -',
],
};
export const arena: Map = {
name: 'Arena',
category: '10x10',
author: 'syuilo',
data: [
'- - -- - -',
' - - - - ',
'- ------ -',
' -------- ',
'- --wb-- -',
'- --bw-- -',
' -------- ',
'- ------ -',
' - - - - ',
'- - -- - -',
],
};
export const reactor: Map = {
name: 'Reactor',
category: '10x10',
author: 'syuilo',
data: [
'-w------b-',
'b- - - -w',
'- --wb-- -',
'---b w---',
'- b wb w -',
'- w bw b -',
'---w b---',
'- --bw-- -',
'w- - - -b',
'-b------w-',
],
};
export const sixeight: Map = {
name: '6x8',
category: 'Special',
data: [
'------',
'------',
'------',
'--wb--',
'--bw--',
'------',
'------',
'------',
],
};
export const spark: Map = {
name: 'Spark',
category: 'Special',
author: 'syuilo',
data: [
' - - ',
'----------',
' -------- ',
' -------- ',
' ---wb--- ',
' ---bw--- ',
' -------- ',
' -------- ',
'----------',
' - - ',
],
};
export const islands: Map = {
name: 'Islands',
category: 'Special',
author: 'syuilo',
data: [
'-------- ',
'---wb--- ',
'---bw--- ',
'-------- ',
' - - ',
' - - ',
' --------',
' --------',
' --------',
' --------',
],
};
export const galaxy: Map = {
name: 'Galaxy',
category: 'Special',
author: 'syuilo',
data: [
' ------ ',
' --www--- ',
' ------w--- ',
'---bbb--w---',
'--b---b-w-b-',
'-b--wwb-w-b-',
'-b-w-bww--b-',
'-b-w-b---b--',
'---w--bbb---',
' ---w------ ',
' ---www-- ',
' ------ ',
],
};
export const triangle: Map = {
name: 'Triangle',
category: 'Special',
author: 'syuilo',
data: [
' -- ',
' -- ',
' ---- ',
' ---- ',
' --wb-- ',
' --bw-- ',
' -------- ',
' -------- ',
'----------',
'----------',
],
};
export const iphonex: Map = {
name: 'iPhone X',
category: 'Special',
author: 'syuilo',
data: [
' -- -- ',
'--------',
'--------',
'--------',
'--------',
'---wb---',
'---bw---',
'--------',
'--------',
'--------',
'--------',
' ------ ',
],
};
export const dealWithIt: Map = {
name: 'Deal with it!',
category: 'Special',
author: 'syuilo',
data: [
'------------',
'--w-b-------',
' --b-w------',
' --w-b---- ',
' ------- ',
],
};
export const experiment: Map = {
name: 'Let\'s experiment',
category: 'Special',
author: 'syuilo',
data: [
' ------------ ',
'------wb------',
'------bw------',
'--------------',
' - - ',
'------ ------',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'bbbbbb wwwwww',
'wwwwww bbbbbb',
],
};
export const bigBoard: Map = {
name: 'Big board',
category: 'Special',
data: [
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'-------wb-------',
'-------bw-------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
'----------------',
],
};
export const twoBoard: Map = {
name: 'Two board',
category: 'Special',
author: 'Aya',
data: [
'-------- --------',
'-------- --------',
'-------- --------',
'---wb--- ---wb---',
'---bw--- ---bw---',
'-------- --------',
'-------- --------',
'-------- --------',
],
};
export const test1: Map = {
name: 'Test1',
category: 'Test',
data: [
'--------',
'---wb---',
'---bw---',
'--------',
],
};
export const test2: Map = {
name: 'Test2',
category: 'Test',
data: [
'------',
'------',
'-b--w-',
'-w--b-',
'-w--b-',
],
};
export const test3: Map = {
name: 'Test3',
category: 'Test',
data: [
'-w-',
'--w',
'w--',
'-w-',
'--w',
'w--',
'-w-',
'--w',
'w--',
'-w-',
'---',
'b--',
],
};
export const test4: Map = {
name: 'Test4',
category: 'Test',
data: [
'-w--b-',
'-w--b-',
'------',
'-w--b-',
'-w--b-',
],
};
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう
export const test6: Map = {
name: 'Test6',
category: 'Test',
data: [
'--wwwww-',
'wwwwwwww',
'wbbbwbwb',
'wbbbbwbb',
'wbwbbwbb',
'wwbwbbbb',
'--wbbbbb',
'-wwwww--',
],
};
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう
export const test7: Map = {
name: 'Test7',
category: 'Test',
data: [
'b--w----',
'b-wwww--',
'bwbwwwbb',
'wbwwwwb-',
'wwwwwww-',
'-wwbbwwb',
'--wwww--',
'--wwww--',
],
};
// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう
export const test8: Map = {
name: 'Test8',
category: 'Test',
data: [
'--------',
'-----w--',
'w--www--',
'wwwwww--',
'bbbbwww-',
'wwwwww--',
'--www---',
'--ww----',
],
};

View File

@ -1,18 +0,0 @@
{
"name": "misskey-reversi",
"version": "0.0.5",
"description": "Misskey reversi engine",
"keywords": [
"misskey"
],
"author": "syuilo <i@syuilo.com>",
"license": "MIT",
"repository": "https://github.com/misskey-dev/misskey.git",
"bugs": "https://github.com/misskey-dev/misskey/issues",
"main": "./built/core.js",
"types": "./built/core.d.ts",
"scripts": {
"build": "tsc"
},
"dependencies": {}
}

View File

@ -1,21 +0,0 @@
{
"compilerOptions": {
"noEmitOnError": false,
"noImplicitAny": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"declaration": true,
"sourceMap": false,
"target": "es2017",
"module": "commonjs",
"removeComments": false,
"noLib": false,
"outDir": "./built",
"rootDir": "./"
},
"compileOnSave": false,
"include": [
"./core.ts"
]
}

View File

@ -1,5 +1,6 @@
/** /**
* Random avatar generator * Identicon generator
* https://en.wikipedia.org/wiki/Identicon
*/ */
import * as p from 'pureimage'; import * as p from 'pureimage';
@ -34,9 +35,9 @@ const cellSize = actualSize / n;
const sideN = Math.floor(n / 2); const sideN = Math.floor(n / 2);
/** /**
* Generate buffer of random avatar by seed * Generate buffer of an identicon by seed
*/ */
export function genAvatar(seed: string, stream: WriteStream): Promise<void> { export function genIdenticon(seed: string, stream: WriteStream): Promise<void> {
const rand = gen.create(seed); const rand = gen.create(seed);
const canvas = p.make(size, size); const canvas = p.make(size, size);
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');

View File

@ -62,7 +62,8 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
if (emoji == null) return null; if (emoji == null) return null;
const isLocal = emoji.host == null; const isLocal = emoji.host == null;
const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({ url: emoji.url })}`; const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため
const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`;
return { return {
name: emojiName, name: emojiName,
@ -116,7 +117,7 @@ export async function prefetchEmojis(emojis: { name: string; host: string | null
} }
const _emojis = emojisQuery.length > 0 ? await Emojis.find({ const _emojis = emojisQuery.length > 0 ? await Emojis.find({
where: emojisQuery, where: emojisQuery,
select: ['name', 'host', 'url'], select: ['name', 'host', 'originalUrl', 'publicUrl'],
}) : []; }) : [];
for (const emoji of _emojis) { for (const emoji of _emojis) {
cache.set(`${emoji.name} ${emoji.host}`, emoji); cache.set(`${emoji.name} ${emoji.host}`, emoji);

View File

@ -1,32 +1,44 @@
import { SimpleObj, SimpleSchema } from './simple-schema'; import {
import { packedUserSchema } from '@/models/repositories/user'; packedUserLiteSchema,
import { packedNoteSchema } from '@/models/repositories/note'; packedUserDetailedNotMeOnlySchema,
import { packedUserListSchema } from '@/models/repositories/user-list'; packedMeDetailedOnlySchema,
import { packedAppSchema } from '@/models/repositories/app'; packedUserDetailedNotMeSchema,
import { packedMessagingMessageSchema } from '@/models/repositories/messaging-message'; packedMeDetailedSchema,
import { packedNotificationSchema } from '@/models/repositories/notification'; packedUserDetailedSchema,
import { packedDriveFileSchema } from '@/models/repositories/drive-file'; packedUserSchema,
import { packedDriveFolderSchema } from '@/models/repositories/drive-folder'; } from '@/models/schema/user';
import { packedFollowingSchema } from '@/models/repositories/following'; import { packedNoteSchema } from '@/models/schema/note';
import { packedMutingSchema } from '@/models/repositories/muting'; import { packedUserListSchema } from '@/models/schema/user-list';
import { packedBlockingSchema } from '@/models/repositories/blocking'; import { packedAppSchema } from '@/models/schema/app';
import { packedNoteReactionSchema } from '@/models/repositories/note-reaction'; import { packedMessagingMessageSchema } from '@/models/schema/messaging-message';
import { packedHashtagSchema } from '@/models/repositories/hashtag'; import { packedNotificationSchema } from '@/models/schema/notification';
import { packedPageSchema } from '@/models/repositories/page'; import { packedDriveFileSchema } from '@/models/schema/drive-file';
import { packedUserGroupSchema } from '@/models/repositories/user-group'; import { packedDriveFolderSchema } from '@/models/schema/drive-folder';
import { packedNoteFavoriteSchema } from '@/models/repositories/note-favorite'; import { packedFollowingSchema } from '@/models/schema/following';
import { packedChannelSchema } from '@/models/repositories/channel'; import { packedMutingSchema } from '@/models/schema/muting';
import { packedAntennaSchema } from '@/models/repositories/antenna'; import { packedBlockingSchema } from '@/models/schema/blocking';
import { packedClipSchema } from '@/models/repositories/clip'; import { packedNoteReactionSchema } from '@/models/schema/note-reaction';
import { packedFederationInstanceSchema } from '@/models/repositories/federation-instance'; import { packedHashtagSchema } from '@/models/schema/hashtag';
import { packedQueueCountSchema } from '@/models/repositories/queue'; import { packedPageSchema } from '@/models/schema/page';
import { packedGalleryPostSchema } from '@/models/repositories/gallery-post'; import { packedUserGroupSchema } from '@/models/schema/user-group';
import { packedEmojiSchema } from '@/models/repositories/emoji'; import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite';
import { packedReversiGameSchema } from '@/models/repositories/games/reversi/game'; import { packedChannelSchema } from '@/models/schema/channel';
import { packedReversiMatchingSchema } from '@/models/repositories/games/reversi/matching'; import { packedAntennaSchema } from '@/models/schema/antenna';
import { packedClipSchema } from '@/models/schema/clip';
import { packedFederationInstanceSchema } from '@/models/schema/federation-instance';
import { packedQueueCountSchema } from '@/models/schema/queue';
import { packedGalleryPostSchema } from '@/models/schema/gallery-post';
import { packedEmojiSchema } from '@/models/schema/emoji';
export const refs = { export const refs = {
UserLite: packedUserLiteSchema,
UserDetailedNotMeOnly: packedUserDetailedNotMeOnlySchema,
MeDetailedOnly: packedMeDetailedOnlySchema,
UserDetailedNotMe: packedUserDetailedNotMeSchema,
MeDetailed: packedMeDetailedSchema,
UserDetailed: packedUserDetailedSchema,
User: packedUserSchema, User: packedUserSchema,
UserList: packedUserListSchema, UserList: packedUserListSchema,
UserGroup: packedUserGroupSchema, UserGroup: packedUserGroupSchema,
App: packedAppSchema, App: packedAppSchema,
@ -49,16 +61,52 @@ export const refs = {
FederationInstance: packedFederationInstanceSchema, FederationInstance: packedFederationInstanceSchema,
GalleryPost: packedGalleryPostSchema, GalleryPost: packedGalleryPostSchema,
Emoji: packedEmojiSchema, Emoji: packedEmojiSchema,
ReversiGame: packedReversiGameSchema,
ReversiMatching: packedReversiMatchingSchema,
}; };
export type Packed<x extends keyof typeof refs> = ObjType<(typeof refs[x])['properties']>; // Packed = SchemaTypeDef<typeof refs[x]>; とすると展開されてマウスホバー時に型表示が使い物にならなくなる
// ObjType<r['properties']>を指定するとなぜか展開されずにPacked<'Hoge'>と表示される
type PackedDef<r extends { properties?: Obj; oneOf?: ReadonlyArray<MinimumSchema>; allOf?: ReadonlyArray<MinimumSchema> }> =
r['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<r['allOf']>> :
r['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<r['oneOf']> :
r['properties'] extends Obj ? ObjType<r['properties']> :
never;
export type Packed<x extends keyof typeof refs> = PackedDef<typeof refs[x]>;
export interface Schema extends SimpleSchema { type TypeStringef = 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
items?: Schema; type StringDefToType<T extends TypeStringef> =
properties?: Obj; T extends 'boolean' ? boolean :
ref?: keyof typeof refs; T extends 'number' ? number :
T extends 'string' ? string | Date :
T extends 'array' ? ReadonlyArray<any> :
T extends 'object' ? Record<string, any> :
any;
// https://swagger.io/specification/?sbsearch=optional#schema-object
type OfSchema = {
readonly anyOf?: ReadonlyArray<MinimumSchema>;
readonly oneOf?: ReadonlyArray<MinimumSchema>;
readonly allOf?: ReadonlyArray<MinimumSchema>;
}
export interface MinimumSchema extends OfSchema {
readonly type?: TypeStringef;
readonly nullable?: boolean;
readonly optional?: boolean;
readonly items?: MinimumSchema;
readonly properties?: Obj;
readonly description?: string;
readonly example?: any;
readonly format?: string;
readonly ref?: keyof typeof refs;
readonly enum?: ReadonlyArray<string>;
readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
readonly maxLength?: number;
readonly minLength?: number;
}
export interface Schema extends MinimumSchema {
readonly nullable: boolean;
readonly optional: boolean;
} }
type NonUndefinedPropertyNames<T extends Obj> = { type NonUndefinedPropertyNames<T extends Obj> = {
@ -69,22 +117,13 @@ type UndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? K : never [K in keyof T]: T[K]['optional'] extends true ? K : never
}[keyof T]; }[keyof T];
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>; export interface Obj { [key: string]: Schema; }
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
export interface Obj extends SimpleObj { [key: string]: Schema; }
export type ObjType<s extends Obj> = export type ObjType<s extends Obj> =
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } & { -readonly [P in UndefinedPropertyNames<s>]?: SchemaType<s[P]> } &
{ [P in keyof OnlyRequired<s>]: SchemaType<s[P]> }; { -readonly [P in NonUndefinedPropertyNames<s>]: SchemaType<s[P]> };
// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2 type NullOrUndefined<p extends MinimumSchema, T> =
type MyType<T extends Schema> = {
0: any;
1: SchemaType<T>;
}[T extends Schema ? 1 : 0];
type NullOrUndefined<p extends Schema, T> =
p['nullable'] extends true p['nullable'] extends true
? p['optional'] extends true ? p['optional'] extends true
? (T | null | undefined) ? (T | null | undefined)
@ -93,15 +132,41 @@ type NullOrUndefined<p extends Schema, T> =
? (T | undefined) ? (T | undefined)
: T; : T;
export type SchemaType<p extends Schema> = // 共用体型を交差型にする型 https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
p['type'] extends 'number' ? NullOrUndefined<p, number> : type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
p['type'] extends 'string' ? NullOrUndefined<p, string> :
p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> : // https://github.com/misskey-dev/misskey/pull/8144#discussion_r785287552
p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> : // 単純にSchemaTypeDef<X>で判定するだけではダメ
p['type'] extends 'object' ? ( type UnionSchemaType<a extends readonly any[], X extends MinimumSchema = a[number]> = X extends any ? SchemaType<X> : never;
p['ref'] extends keyof typeof refs type ArrayUnion<T> = T extends any ? Array<T> : never;
? NullOrUndefined<p, Packed<p['ref']>>
: NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> export type SchemaTypeDef<p extends MinimumSchema> =
p['type'] extends 'number' ? number :
p['type'] extends 'string' ? (
p['enum'] extends readonly string[] ?
p['enum'][number] :
p['format'] extends 'date-time' ? string : // Dateにする
string
) : ) :
p['type'] extends 'any' ? NullOrUndefined<p, any> : p['type'] extends 'boolean' ? boolean :
p['type'] extends 'object' ? (
p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
p['properties'] extends NonNullable<Obj> ? ObjType<p['properties']> :
p['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['anyOf']> & Partial<UnionToIntersection<UnionSchemaType<p['anyOf']>>> :
p['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
any
) :
p['type'] extends 'array' ? (
p['items'] extends OfSchema ? (
p['items']['anyOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<NonNullable<p['items']['anyOf']>>[] :
p['items']['oneOf'] extends ReadonlyArray<MinimumSchema> ? ArrayUnion<UnionSchemaType<NonNullable<p['items']['oneOf']>>> :
p['items']['allOf'] extends ReadonlyArray<MinimumSchema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
never
) :
p['items'] extends NonNullable<MinimumSchema> ? SchemaTypeDef<p['items']>[] :
any[]
) :
p['oneOf'] extends ReadonlyArray<MinimumSchema> ? UnionSchemaType<p['oneOf']> :
any; any;
export type SchemaType<p extends MinimumSchema> = NullOrUndefined<p, SchemaTypeDef<p>>;

View File

@ -1,15 +0,0 @@
export interface SimpleSchema {
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
nullable: boolean;
optional: boolean;
items?: SimpleSchema;
properties?: SimpleObj;
description?: string;
example?: any;
format?: string;
ref?: string;
enum?: string[];
default?: boolean | null;
}
export interface SimpleObj { [key: string]: SimpleSchema; }

View File

@ -51,6 +51,11 @@ export class AbuseUserReport {
}) })
public resolved: boolean; public resolved: boolean;
@Column('boolean', {
default: false
})
public forwarded: boolean;
@Column('varchar', { @Column('varchar', {
length: 2048, length: 2048,
}) })

View File

@ -101,6 +101,11 @@ export class DriveFile {
}) })
public webpublicUrl: string | null; public webpublicUrl: string | null;
@Column('varchar', {
length: 128, nullable: true,
})
public webpublicType: string | null;
@Index({ unique: true }) @Index({ unique: true })
@Column('varchar', { @Column('varchar', {
length: 256, nullable: true, length: 256, nullable: true,

View File

@ -32,13 +32,19 @@ export class Emoji {
@Column('varchar', { @Column('varchar', {
length: 512, length: 512,
}) })
public url: string; public originalUrl: string;
@Column('varchar', {
length: 512,
})
public publicUrl: string;
@Column('varchar', { @Column('varchar', {
length: 512, nullable: true, length: 512, nullable: true,
}) })
public uri: string | null; public uri: string | null;
// publicUrlの方のtypeが入る
@Column('varchar', { @Column('varchar', {
length: 64, nullable: true, length: 64, nullable: true,
}) })

View File

@ -1,133 +0,0 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../../user';
import { id } from '../../../id';
@Entity()
export class ReversiGame {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the ReversiGame.',
})
public createdAt: Date;
@Column('timestamp with time zone', {
nullable: true,
comment: 'The started date of the ReversiGame.',
})
public startedAt: Date | null;
@Column(id())
public user1Id: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user1: User | null;
@Column(id())
public user2Id: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public user2: User | null;
@Column('boolean', {
default: false,
})
public user1Accepted: boolean;
@Column('boolean', {
default: false,
})
public user2Accepted: boolean;
/**
* ()
* 1 ... user1
* 2 ... user2
*/
@Column('integer', {
nullable: true,
})
public black: number | null;
@Column('boolean', {
default: false,
})
public isStarted: boolean;
@Column('boolean', {
default: false,
})
public isEnded: boolean;
@Column({
...id(),
nullable: true,
})
public winnerId: User['id'] | null;
@Column({
...id(),
nullable: true,
})
public surrendered: User['id'] | null;
@Column('jsonb', {
default: [],
})
public logs: {
at: Date;
color: boolean;
pos: number;
}[];
@Column('varchar', {
array: true, length: 64,
})
public map: string[];
@Column('varchar', {
length: 32,
})
public bw: string;
@Column('boolean', {
default: false,
})
public isLlotheo: boolean;
@Column('boolean', {
default: false,
})
public canPutEverywhere: boolean;
@Column('boolean', {
default: false,
})
public loopedBoard: boolean;
@Column('jsonb', {
nullable: true, default: null,
})
public form1: any | null;
@Column('jsonb', {
nullable: true, default: null,
})
public form2: any | null;
/**
* posを文字列としてすべて連結したもののCRC32値
*/
@Column('varchar', {
length: 32, nullable: true,
})
public crc32: string | null;
}

View File

@ -1,35 +0,0 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from '../../user';
import { id } from '../../../id';
@Entity()
export class ReversiMatching {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the ReversiMatching.',
})
public createdAt: Date;
@Index()
@Column(id())
public parentId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public parent: User | null;
@Index()
@Column(id())
public childId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE',
})
@JoinColumn()
public child: User | null;
}

View File

@ -124,6 +124,7 @@ export class UserProfile {
}) })
public clientData: Record<string, any>; public clientData: Record<string, any>;
// TODO: そのうち消す
@Column('jsonb', { @Column('jsonb', {
default: {}, default: {},
comment: 'The room data of the User.', comment: 'The room data of the User.',

View File

@ -18,7 +18,6 @@ import { AccessToken } from './entities/access-token';
import { UserNotePining } from './entities/user-note-pining'; import { UserNotePining } from './entities/user-note-pining';
import { SigninRepository } from './repositories/signin'; import { SigninRepository } from './repositories/signin';
import { MessagingMessageRepository } from './repositories/messaging-message'; import { MessagingMessageRepository } from './repositories/messaging-message';
import { ReversiGameRepository } from './repositories/games/reversi/game';
import { UserListRepository } from './repositories/user-list'; import { UserListRepository } from './repositories/user-list';
import { UserListJoining } from './entities/user-list-joining'; import { UserListJoining } from './entities/user-list-joining';
import { UserGroupRepository } from './repositories/user-group'; import { UserGroupRepository } from './repositories/user-group';
@ -30,7 +29,6 @@ import { BlockingRepository } from './repositories/blocking';
import { NoteReactionRepository } from './repositories/note-reaction'; import { NoteReactionRepository } from './repositories/note-reaction';
import { NotificationRepository } from './repositories/notification'; import { NotificationRepository } from './repositories/notification';
import { NoteFavoriteRepository } from './repositories/note-favorite'; import { NoteFavoriteRepository } from './repositories/note-favorite';
import { ReversiMatchingRepository } from './repositories/games/reversi/matching';
import { UserPublickey } from './entities/user-publickey'; import { UserPublickey } from './entities/user-publickey';
import { UserKeypair } from './entities/user-keypair'; import { UserKeypair } from './entities/user-keypair';
import { AppRepository } from './repositories/app'; import { AppRepository } from './repositories/app';
@ -107,8 +105,6 @@ export const AuthSessions = getCustomRepository(AuthSessionRepository);
export const AccessTokens = getRepository(AccessToken); export const AccessTokens = getRepository(AccessToken);
export const Signins = getCustomRepository(SigninRepository); export const Signins = getCustomRepository(SigninRepository);
export const MessagingMessages = getCustomRepository(MessagingMessageRepository); export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
export const ReversiGames = getCustomRepository(ReversiGameRepository);
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
export const Pages = getCustomRepository(PageRepository); export const Pages = getCustomRepository(PageRepository);
export const PageLikes = getCustomRepository(PageLikeRepository); export const PageLikes = getCustomRepository(PageLikeRepository);
export const GalleryPosts = getCustomRepository(GalleryPostRepository); export const GalleryPosts = getCustomRepository(GalleryPostRepository);

View File

@ -27,6 +27,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, { assignee: report.assigneeId ? Users.pack(report.assignee || report.assigneeId, null, {
detail: true, detail: true,
}) : null, }) : null,
forwarded: report.forwarded,
}); });
} }

View File

@ -31,94 +31,3 @@ export class AntennaRepository extends Repository<Antenna> {
}; };
} }
} }
export const packedAntennaSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
keywords: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
excludeKeywords: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
src: {
type: 'string' as const,
optional: false as const, nullable: false as const,
enum: ['home', 'all', 'users', 'list', 'group'],
},
userListId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
userGroupId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
users: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
caseSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
notify: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
withReplies: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
withFile: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
hasUnreadNote: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
default: false,
},
},
};

View File

@ -38,38 +38,3 @@ export class AppRepository extends Repository<App> {
}; };
} }
} }
export const packedAppSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
callbackUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
permission: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
secret: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
isAuthorized: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
},
};

View File

@ -30,31 +30,3 @@ export class BlockingRepository extends Repository<Blocking> {
return Promise.all(blockings.map(x => this.pack(x, me))); return Promise.all(blockings.map(x => this.pack(x, me)));
} }
} }
export const packedBlockingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
blockeeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
blockee: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -40,56 +40,3 @@ export class ChannelRepository extends Repository<Channel> {
}; };
} }
} }
export const packedChannelSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
lastNotedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
nullable: true as const, optional: false as const,
},
bannerUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: false as const,
},
notesCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
usersCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
isFollowing: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
userId: {
type: 'string' as const,
nullable: true as const, optional: false as const,
format: 'id',
},
},
};

View File

@ -29,42 +29,3 @@ export class ClipRepository extends Repository<Clip> {
} }
} }
export const packedClipSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
isPublic: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -3,7 +3,7 @@ import { DriveFile } from '@/models/entities/drive-file';
import { Users, DriveFolders } from '../index'; import { Users, DriveFolders } from '../index';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import { toPuny } from '@/misc/convert-host'; import { toPuny } from '@/misc/convert-host';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll, Promiseable } from '@/prelude/await-all';
import { Packed } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import config from '@/config/index'; import config from '@/config/index';
import { query, appendQuery } from '@/prelude/url'; import { query, appendQuery } from '@/prelude/url';
@ -126,7 +126,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const meta = await fetchMeta(); const meta = await fetchMeta();
return await awaitAll({ return await awaitAll<Packed<'DriveFile'>>({
id: file.id, id: file.id,
createdAt: file.createdAt.toISOString(), createdAt: file.createdAt.toISOString(),
name: file.name, name: file.name,
@ -156,112 +156,3 @@ export class DriveFileRepository extends Repository<DriveFile> {
return items.filter(x => x != null); return items.filter(x => x != null);
} }
} }
export const packedDriveFileSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'lenna.jpg',
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'image/jpeg',
},
md5: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'md5',
example: '15eca7fba0480996e2245f5185bf39f2',
},
size: {
type: 'number' as const,
optional: false as const, nullable: false as const,
example: 51469,
},
isSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
blurhash: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
properties: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
width: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 1280,
},
height: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 720,
},
orientation: {
type: 'number' as const,
optional: true as const, nullable: false as const,
example: 8,
},
avgColor: {
type: 'string' as const,
optional: true as const, nullable: false as const,
example: 'rgb(40,65,87)',
},
},
},
url: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
thumbnailUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
comment: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
folderId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
folder: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFolder' as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'User' as const,
},
},
};

View File

@ -48,44 +48,3 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
}); });
} }
} }
export const packedDriveFolderSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
foldersCount: {
type: 'number' as const,
optional: true as const, nullable: false as const,
},
filesCount: {
type: 'number' as const,
optional: true as const, nullable: false as const,
},
parentId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFolder' as const,
},
},
};

View File

@ -15,7 +15,8 @@ export class EmojiRepository extends Repository<Emoji> {
name: emoji.name, name: emoji.name,
category: emoji.category, category: emoji.category,
host: emoji.host, host: emoji.host,
url: emoji.url, // || emoji.originalUrl してるのは後方互換性のため
url: emoji.publicUrl || emoji.originalUrl,
}; };
} }
@ -25,41 +26,3 @@ export class EmojiRepository extends Repository<Emoji> {
return Promise.all(emojis.map(x => this.pack(x))); return Promise.all(emojis.map(x => this.pack(x)));
} }
} }
export const packedEmojiSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
category: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
host: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
url: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,106 +1,2 @@
import config from '@/config/index'; import config from '@/config/index';
export const packedFederationInstanceSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
caughtAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
host: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'misskey.example.com',
},
usersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
notesCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
followingCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
followersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
driveUsage: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
driveFiles: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
latestRequestSentAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
lastCommunicatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
isNotResponding: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
isSuspended: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
softwareName: {
type: 'string' as const,
optional: false as const, nullable: true as const,
example: 'misskey',
},
softwareVersion: {
type: 'string' as const,
optional: false as const, nullable: true as const,
example: config.version,
},
openRegistrations: {
type: 'boolean' as const,
optional: false as const, nullable: true as const,
example: true,
},
name: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
maintainerName: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
maintainerEmail: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
iconUrl: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'url',
},
infoUpdatedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
},
};

View File

@ -84,41 +84,3 @@ export class FollowingRepository extends Repository<Following> {
return Promise.all(followings.map(x => this.pack(x, me, opts))); return Promise.all(followings.map(x => this.pack(x, me, opts)));
} }
} }
export const packedFollowingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
followeeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
followee: {
type: 'object' as const,
optional: true as const, nullable: false as const,
ref: 'User' as const,
},
followerId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
follower: {
type: 'object' as const,
optional: true as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -38,74 +38,3 @@ export class GalleryPostRepository extends Repository<GalleryPost> {
return Promise.all(posts.map(x => this.pack(x, me))); return Promise.all(posts.map(x => this.pack(x, me)));
} }
} }
export const packedGalleryPostSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
title: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
description: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
fileIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
files: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'DriveFile' as const,
},
},
tags: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
isSensitive: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,191 +0,0 @@
import { User } from '@/models/entities/user';
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../../../index';
import { ReversiGame } from '@/models/entities/games/reversi/game';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiGame)
export class ReversiGameRepository extends Repository<ReversiGame> {
public async pack(
src: ReversiGame['id'] | ReversiGame,
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean
}
): Promise<Packed<'ReversiGame'>> {
const opts = Object.assign({
detail: true,
}, options);
const game = typeof src === 'object' ? src : await this.findOneOrFail(src);
return {
id: game.id,
createdAt: game.createdAt.toISOString(),
startedAt: game.startedAt && game.startedAt.toISOString(),
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Accepted: game.user1Accepted,
user2Accepted: game.user2Accepted,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: await Users.pack(game.user1Id, me),
user2: await Users.pack(game.user2Id, me),
winnerId: game.winnerId,
winner: game.winnerId ? await Users.pack(game.winnerId, me) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
...(opts.detail ? {
logs: game.logs.map(log => ({
at: log.at.toISOString(),
color: log.color,
pos: log.pos,
})),
map: game.map,
} : {}),
};
}
}
export const packedReversiGameSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
startedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
isStarted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
isEnded: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
form1: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
form2: {
type: 'any' as const,
optional: false as const, nullable: true as const,
},
user1Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user2Accepted: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
user1Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user2Id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
user1: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
user2: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
winnerId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
winner: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
surrendered: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
black: {
type: 'number' as const,
optional: false as const, nullable: true as const,
},
bw: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
isLlotheo: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
canPutEverywhere: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
loopedBoard: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
logs: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: true as const, nullable: false as const,
properties: {
at: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
color: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
pos: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
},
},
map: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
},
};

View File

@ -1,69 +0,0 @@
import { EntityRepository, Repository } from 'typeorm';
import { ReversiMatching } from '@/models/entities/games/reversi/matching';
import { Users } from '../../../index';
import { awaitAll } from '@/prelude/await-all';
import { User } from '@/models/entities/user';
import { Packed } from '@/misc/schema';
@EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
public async pack(
src: ReversiMatching['id'] | ReversiMatching,
me: { id: User['id'] }
): Promise<Packed<'ReversiMatching'>> {
const matching = typeof src === 'object' ? src : await this.findOneOrFail(src);
return await awaitAll({
id: matching.id,
createdAt: matching.createdAt.toISOString(),
parentId: matching.parentId,
parent: Users.pack(matching.parentId, me, {
detail: true,
}),
childId: matching.childId,
child: Users.pack(matching.childId, me, {
detail: true,
}),
});
}
}
export const packedReversiMatchingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
parentId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object' as const,
optional: false as const, nullable: true as const,
ref: 'User' as const,
},
childId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
child: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -24,39 +24,3 @@ export class HashtagRepository extends Repository<Hashtag> {
return Promise.all(hashtags.map(x => this.pack(x))); return Promise.all(hashtags.map(x => this.pack(x)));
} }
} }
export const packedHashtagSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
tag: {
type: 'string' as const,
optional: false as const, nullable: false as const,
example: 'misskey',
},
mentionedUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
mentionedLocalUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
mentionedRemoteUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedLocalUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
attachedRemoteUsersCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -42,78 +42,3 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
}; };
} }
} }
export const packedMessagingMessageSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: true as const, nullable: false as const,
},
text: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
fileId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
},
file: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'DriveFile' as const,
},
recipientId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
recipient: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'User' as const,
},
groupId: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'id',
},
group: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'UserGroup' as const,
},
isRead: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
reads: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
},
};

View File

@ -30,31 +30,3 @@ export class MutingRepository extends Repository<Muting> {
return Promise.all(mutings.map(x => this.pack(x, me))); return Promise.all(mutings.map(x => this.pack(x, me)));
} }
} }
export const packedMutingSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
muteeId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
mutee: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
},
};

View File

@ -26,31 +26,3 @@ export class NoteFavoriteRepository extends Repository<NoteFavorite> {
return Promise.all(favorites.map(x => this.pack(x, me))); return Promise.all(favorites.map(x => this.pack(x, me)));
} }
} }
export const packedNoteFavoriteSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
note: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Note' as const,
},
noteId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
};

View File

@ -31,30 +31,3 @@ export class NoteReactionRepository extends Repository<NoteReaction> {
}; };
} }
} }
export const packedNoteReactionSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
user: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'User' as const,
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -218,7 +218,7 @@ export class NoteRepository extends Repository<Note> {
const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, '')); const reactionEmojiNames = Object.keys(note.reactions).filter(x => x?.startsWith(':')).map(x => decodeReaction(x).reaction).map(x => x.replace(/:/g, ''));
const packed = await awaitAll({ const packed: Packed<'Note'> = await awaitAll({
id: note.id, id: note.id,
createdAt: note.createdAt.toISOString(), createdAt: note.createdAt.toISOString(),
userId: note.userId, userId: note.userId,
@ -320,188 +320,3 @@ export class NoteRepository extends Repository<Note> {
}))); })));
} }
} }
export const packedNoteSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
text: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
cw: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
replyId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'Note' as const,
},
renote: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'Note' as const,
},
isHidden: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
visibility: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
mentions: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
visibleUserIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
fileIds: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
},
files: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'DriveFile' as const,
},
},
tags: {
type: 'array' as const,
optional: true as const, nullable: false as const,
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
},
poll: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
channelId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
channel: {
type: 'object' as const,
optional: true as const, nullable: true as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
},
},
},
localOnly: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
emojis: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
url: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
},
},
},
reactions: {
type: 'object' as const,
optional: false as const, nullable: false as const,
},
renoteCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
repliesCount: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
uri: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
url: {
type: 'string' as const,
optional: true as const, nullable: false as const,
},
myReaction: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
},
};

View File

@ -107,69 +107,3 @@ export class NotificationRepository extends Repository<Notification> {
}))); })));
} }
} }
export const packedNotificationSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
isRead: {
type: 'boolean' as const,
optional: false as const, nullable: false as const,
},
type: {
type: 'string' as const,
optional: false as const, nullable: false as const,
enum: [...notificationTypes],
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: true as const, nullable: true as const,
},
userId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
},
note: {
type: 'object' as const,
ref: 'Note' as const,
optional: true as const, nullable: true as const,
},
reaction: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
choice: {
type: 'number' as const,
optional: true as const, nullable: true as const,
},
invitation: {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
body: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
header: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
icon: {
type: 'string' as const,
optional: true as const, nullable: true as const,
},
},
};

View File

@ -87,56 +87,3 @@ export class PageRepository extends Repository<Page> {
return Promise.all(pages.map(x => this.pack(x, me))); return Promise.all(pages.map(x => this.pack(x, me)));
} }
} }
export const packedPageSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
title: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
summary: {
type: 'string' as const,
optional: false as const, nullable: true as const,
},
content: {
type: 'array' as const,
optional: false as const, nullable: false as const,
},
variables: {
type: 'array' as const,
optional: false as const, nullable: false as const,
},
userId: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
},
user: {
type: 'object' as const,
ref: 'User' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -1,30 +0,0 @@
export const packedQueueCountSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
waiting: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
active: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
completed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
failed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
delayed: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
paused: {
type: 'number' as const,
optional: false as const, nullable: false as const,
},
},
};

View File

@ -23,39 +23,3 @@ export class UserGroupRepository extends Repository<UserGroup> {
}; };
} }
} }
export const packedUserGroupSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
ownerId: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
userIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
},
};

View File

@ -22,34 +22,3 @@ export class UserListRepository extends Repository<UserList> {
}; };
} }
} }
export const packedUserListSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
},
userIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
},
};

View File

@ -4,11 +4,19 @@ import { User, ILocalUser, IRemoteUser } from '@/models/entities/user';
import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index'; import { Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings, Instances } from '../index';
import config from '@/config/index'; import config from '@/config/index';
import { Packed } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { awaitAll } from '@/prelude/await-all'; import { awaitAll, Promiseable } from '@/prelude/await-all';
import { populateEmojis } from '@/misc/populate-emojis'; import { populateEmojis } from '@/misc/populate-emojis';
import { getAntennas } from '@/misc/antenna-cache'; import { getAntennas } from '@/misc/antenna-cache';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const';
type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
Detailed extends true ?
ExpectsMe extends true ? Packed<'MeDetailed'> :
ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
Packed<'UserDetailed'> :
Packed<'UserLite'>;
@EntityRepository(User) @EntityRepository(User)
export class UserRepository extends Repository<User> { export class UserRepository extends Repository<User> {
public async getRelation(me: User['id'], target: User['id']) { public async getRelation(me: User['id'], target: User['id']) {
@ -144,7 +152,7 @@ export class UserRepository extends Repository<User> {
return count > 0; return count > 0;
} }
public getOnlineStatus(user: User): string { public getOnlineStatus(user: User): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown'; if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown'; if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime(); const elapsed = Date.now() - user.lastActiveDate.getTime();
@ -159,18 +167,18 @@ export class UserRepository extends Repository<User> {
if (user.avatarUrl) { if (user.avatarUrl) {
return user.avatarUrl; return user.avatarUrl;
} else { } else {
return `${config.url}/random-avatar/${user.id}`; return `${config.url}/identicon/${user.id}`;
} }
} }
public async pack( public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
src: User['id'] | User, src: User['id'] | User,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
detail?: boolean, detail?: D,
includeSecrets?: boolean, includeSecrets?: boolean,
} }
): Promise<Packed<'User'>> { ): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
const opts = Object.assign({ const opts = Object.assign({
detail: false, detail: false,
includeSecrets: false, includeSecrets: false,
@ -178,8 +186,9 @@ export class UserRepository extends Repository<User> {
const user = typeof src === 'object' ? src : await this.findOneOrFail(src); const user = typeof src === 'object' ? src : await this.findOneOrFail(src);
const meId = me ? me.id : null; const meId = me ? me.id : null;
const isMe = meId === user.id;
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin') const pins = opts.detail ? await UserNotePinings.createQueryBuilder('pin')
.where('pin.userId = :userId', { userId: user.id }) .where('pin.userId = :userId', { userId: user.id })
.innerJoinAndSelect('pin.note', 'note') .innerJoinAndSelect('pin.note', 'note')
@ -188,12 +197,12 @@ export class UserRepository extends Repository<User> {
const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null; const profile = opts.detail ? await UserProfiles.findOneOrFail(user.id) : null;
const followingCount = profile == null ? null : const followingCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followingCount : (profile.ffVisibility === 'public') || isMe ? user.followingCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followingCount :
null; null;
const followersCount = profile == null ? null : const followersCount = profile == null ? null :
(profile.ffVisibility === 'public') || (meId === user.id) ? user.followersCount : (profile.ffVisibility === 'public') || isMe ? user.followersCount :
(profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount : (profile.ffVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
null; null;
@ -227,12 +236,11 @@ export class UserRepository extends Repository<User> {
uri: user.uri, uri: user.uri,
createdAt: user.createdAt.toISOString(), createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
lastFetchedAt: user.lastFetchedAt?.toISOString(), lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
bannerUrl: user.bannerUrl, bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash, bannerBlurhash: user.bannerBlurhash,
bannerColor: null, // 後方互換性のため bannerColor: null, // 後方互換性のため
isLocked: user.isLocked, isLocked: user.isLocked,
isModerator: user.isModerator || falsy,
isSilenced: user.isSilenced || falsy, isSilenced: user.isSilenced || falsy,
isSuspended: user.isSuspended || falsy, isSuspended: user.isSuspended || falsy,
description: profile!.description, description: profile!.description,
@ -260,7 +268,7 @@ export class UserRepository extends Repository<User> {
: false, : false,
} : {}), } : {}),
...(opts.detail && meId === user.id ? { ...(opts.detail && isMe ? {
avatarId: user.avatarId, avatarId: user.avatarId,
bannerId: user.bannerId, bannerId: user.bannerId,
injectFeaturedNote: profile!.injectFeaturedNote, injectFeaturedNote: profile!.injectFeaturedNote,
@ -315,19 +323,19 @@ export class UserRepository extends Repository<User> {
isBlocked: relation.isBlocked, isBlocked: relation.isBlocked,
isMuted: relation.isMuted, isMuted: relation.isMuted,
} : {}), } : {}),
}; } as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
return await awaitAll(packed); return await awaitAll(packed);
} }
public packMany( public packMany<D extends boolean = false>(
users: (User['id'] | User)[], users: (User['id'] | User)[],
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: { options?: {
detail?: boolean, detail?: D,
includeSecrets?: boolean, includeSecrets?: boolean,
} }
) { ): Promise<IsUserDetailed<D>[]> {
return Promise.all(users.map(u => this.pack(u, me, options))); return Promise.all(users.map(u => this.pack(u, me, options)));
} }
@ -352,313 +360,3 @@ export class UserRepository extends Repository<User> {
public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/); public validateBirthday = $.str.match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})$/);
//#endregion //#endregion
} }
export const packedUserSchema = {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
id: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string' as const,
nullable: true as const, optional: false as const,
example: '藍',
},
username: {
type: 'string' as const,
nullable: false as const, optional: false as const,
example: 'ai',
},
host: {
type: 'string' as const,
nullable: true as const, optional: false as const,
example: 'misskey.example.com',
},
avatarUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: false as const,
},
avatarBlurhash: {
type: 'any' as const,
nullable: true as const, optional: false as const,
},
avatarColor: {
type: 'any' as const,
nullable: true as const, optional: false as const,
default: null,
},
isAdmin: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
isModerator: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
isBot: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
isCat: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
emojis: {
type: 'array' as const,
nullable: false as const, optional: false as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
name: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
url: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'url',
},
},
},
},
url: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: true as const,
},
createdAt: {
type: 'string' as const,
nullable: false as const, optional: true as const,
format: 'date-time',
},
updatedAt: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'date-time',
},
bannerUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: true as const,
},
bannerBlurhash: {
type: 'any' as const,
nullable: true as const, optional: true as const,
},
bannerColor: {
type: 'any' as const,
nullable: true as const, optional: true as const,
default: null,
},
isLocked: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
isSuspended: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
example: false,
},
description: {
type: 'string' as const,
nullable: true as const, optional: true as const,
example: 'Hi masters, I am Ai!',
},
location: {
type: 'string' as const,
nullable: true as const, optional: true as const,
},
birthday: {
type: 'string' as const,
nullable: true as const, optional: true as const,
example: '2018-03-12',
},
fields: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
properties: {
name: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
value: {
type: 'string' as const,
nullable: false as const, optional: false as const,
},
},
maxLength: 4,
},
},
followersCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
followingCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
notesCount: {
type: 'number' as const,
nullable: false as const, optional: true as const,
},
pinnedNoteIds: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
pinnedNotes: {
type: 'array' as const,
nullable: false as const, optional: true as const,
items: {
type: 'object' as const,
nullable: false as const, optional: false as const,
ref: 'Note' as const,
},
},
pinnedPageId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
},
pinnedPage: {
type: 'object' as const,
nullable: true as const, optional: true as const,
ref: 'Page' as const,
},
twoFactorEnabled: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
usePasswordLessLogin: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
securityKeys: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
default: false,
},
avatarId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'id',
},
bannerId: {
type: 'string' as const,
nullable: true as const, optional: true as const,
format: 'id',
},
autoWatch: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
injectFeaturedNote: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
alwaysMarkNsfw: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
carefulBot: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
autoAcceptFollowed: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadSpecifiedNotes: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadMentions: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadAnnouncement: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadAntenna: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadChannel: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadMessagingMessage: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasUnreadNotification: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
hasPendingReceivedFollowRequest: {
type: 'boolean' as const,
nullable: false as const, optional: true as const,
},
integrations: {
type: 'object' as const,
nullable: false as const, optional: true as const,
},
mutedWords: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
mutedInstances: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
mutingNotificationTypes: {
type: 'array' as const,
nullable: false as const, optional: true as const,
},
isFollowing: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
hasPendingFollowRequestFromYou: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
hasPendingFollowRequestToYou: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isFollowed: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isBlocking: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isBlocked: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
isMuted: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
},
};

View File

@ -0,0 +1,89 @@
export const packedAntennaSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
keywords: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
excludeKeywords: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
},
src: {
type: 'string',
optional: false, nullable: false,
enum: ['home', 'all', 'users', 'list', 'group'],
},
userListId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
userGroupId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
users: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
caseSensitive: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
notify: {
type: 'boolean',
optional: false, nullable: false,
},
withReplies: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
withFile: {
type: 'boolean',
optional: false, nullable: false,
},
hasUnreadNote: {
type: 'boolean',
optional: false, nullable: false,
default: false,
},
},
} as const;

View File

@ -0,0 +1,33 @@
export const packedAppSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
callbackUrl: {
type: 'string',
optional: false, nullable: true,
},
permission: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
secret: {
type: 'string',
optional: true, nullable: false,
},
isAuthorized: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedBlockingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
blockeeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
blockee: {
type: 'object',
optional: false, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,51 @@
export const packedChannelSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
lastNotedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
nullable: true, optional: false,
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
userId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
},
} as const;

View File

@ -0,0 +1,38 @@
export const packedClipSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
isPublic: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,107 @@
export const packedDriveFileSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
example: 'lenna.jpg',
},
type: {
type: 'string',
optional: false, nullable: false,
example: 'image/jpeg',
},
md5: {
type: 'string',
optional: false, nullable: false,
format: 'md5',
example: '15eca7fba0480996e2245f5185bf39f2',
},
size: {
type: 'number',
optional: false, nullable: false,
example: 51469,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
blurhash: {
type: 'string',
optional: false, nullable: true,
},
properties: {
type: 'object',
optional: false, nullable: false,
properties: {
width: {
type: 'number',
optional: true, nullable: false,
example: 1280,
},
height: {
type: 'number',
optional: true, nullable: false,
example: 720,
},
orientation: {
type: 'number',
optional: true, nullable: false,
example: 8,
},
avgColor: {
type: 'string',
optional: true, nullable: false,
example: 'rgb(40,65,87)',
},
},
},
url: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
thumbnailUrl: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
comment: {
type: 'string',
optional: false, nullable: true,
},
folderId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
folder: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFolder',
},
userId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
user: {
type: 'object',
optional: true, nullable: true,
ref: 'UserLite',
},
},
} as const;

View File

@ -0,0 +1,39 @@
export const packedDriveFolderSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
foldersCount: {
type: 'number',
optional: true, nullable: false,
},
filesCount: {
type: 'number',
optional: true, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFolder',
},
},
} as const;

View File

@ -0,0 +1,36 @@
export const packedEmojiSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
aliases: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
name: {
type: 'string',
optional: false, nullable: false,
},
category: {
type: 'string',
optional: false, nullable: true,
},
host: {
type: 'string',
optional: false, nullable: true,
},
url: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,105 @@
import config from "@/config";
export const packedFederationInstanceSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
caughtAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
host: {
type: 'string',
optional: false, nullable: false,
example: 'misskey.example.com',
},
usersCount: {
type: 'number',
optional: false, nullable: false,
},
notesCount: {
type: 'number',
optional: false, nullable: false,
},
followingCount: {
type: 'number',
optional: false, nullable: false,
},
followersCount: {
type: 'number',
optional: false, nullable: false,
},
driveUsage: {
type: 'number',
optional: false, nullable: false,
},
driveFiles: {
type: 'number',
optional: false, nullable: false,
},
latestRequestSentAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
lastCommunicatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
isNotResponding: {
type: 'boolean',
optional: false, nullable: false,
},
isSuspended: {
type: 'boolean',
optional: false, nullable: false,
},
softwareName: {
type: 'string',
optional: false, nullable: true,
example: 'misskey',
},
softwareVersion: {
type: 'string',
optional: false, nullable: true,
example: config.version,
},
openRegistrations: {
type: 'boolean',
optional: false, nullable: true,
example: true,
},
name: {
type: 'string',
optional: false, nullable: true,
},
description: {
type: 'string',
optional: false, nullable: true,
},
maintainerName: {
type: 'string',
optional: false, nullable: true,
},
maintainerEmail: {
type: 'string',
optional: false, nullable: true,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
format: 'url',
},
infoUpdatedAt: {
type: 'string',
optional: false, nullable: true,
format: 'date-time',
},
},
} as const;

View File

@ -0,0 +1,36 @@
export const packedFollowingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
followeeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
followerId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
follower: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,69 @@
export const packedGalleryPostSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
fileIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
files: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
tags: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,34 @@
export const packedHashtagSchema = {
type: 'object',
properties: {
tag: {
type: 'string',
optional: false, nullable: false,
example: 'misskey',
},
mentionedUsersCount: {
type: 'number',
optional: false, nullable: false,
},
mentionedLocalUsersCount: {
type: 'number',
optional: false, nullable: false,
},
mentionedRemoteUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedLocalUsersCount: {
type: 'number',
optional: false, nullable: false,
},
attachedRemoteUsersCount: {
type: 'number',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,73 @@
export const packedMessagingMessageSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: true, nullable: false,
},
text: {
type: 'string',
optional: false, nullable: true,
},
fileId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
},
file: {
type: 'object',
optional: true, nullable: true,
ref: 'DriveFile',
},
recipientId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
recipient: {
type: 'object',
optional: true, nullable: true,
ref: 'UserLite',
},
groupId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
group: {
type: 'object',
optional: true, nullable: true,
ref: 'UserGroup',
},
isRead: {
type: 'boolean',
optional: true, nullable: false,
},
reads: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedMutingSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
muteeId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
mutee: {
type: 'object',
optional: false, nullable: false,
ref: 'UserDetailed',
},
},
} as const;

View File

@ -0,0 +1,26 @@
export const packedNoteFavoriteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
note: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
noteId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
} as const;

View File

@ -0,0 +1,25 @@
export const packedNoteReactionSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
user: {
type: 'object',
optional: false, nullable: false,
ref: 'UserLite',
},
type: {
type: 'string',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,183 @@
export const packedNoteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
text: {
type: 'string',
optional: false, nullable: true,
},
cw: {
type: 'string',
optional: true, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
replyId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
},
renote: {
type: 'object',
optional: true, nullable: true,
ref: 'Note',
},
isHidden: {
type: 'boolean',
optional: true, nullable: false,
},
visibility: {
type: 'string',
optional: false, nullable: false,
},
mentions: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
visibleUserIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
fileIds: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
},
files: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
tags: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'string',
optional: false, nullable: false,
},
},
poll: {
type: 'object',
optional: true, nullable: true,
},
channelId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
channel: {
type: 'object',
optional: true, nullable: true,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: true,
},
},
},
},
localOnly: {
type: 'boolean',
optional: true, nullable: false,
},
emojis: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
name: {
type: 'string',
optional: false, nullable: false,
},
url: {
type: 'string',
optional: false, nullable: true,
},
},
},
},
reactions: {
type: 'object',
optional: false, nullable: false,
},
renoteCount: {
type: 'number',
optional: false, nullable: false,
},
repliesCount: {
type: 'number',
optional: false, nullable: false,
},
uri: {
type: 'string',
optional: true, nullable: false,
},
url: {
type: 'string',
optional: true, nullable: false,
},
myReaction: {
type: 'object',
optional: true, nullable: true,
},
},
} as const;

View File

@ -0,0 +1,66 @@
import { notificationTypes } from "@/types";
export const packedNotificationSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
isRead: {
type: 'boolean',
optional: false, nullable: false,
},
type: {
type: 'string',
optional: false, nullable: false,
enum: [...notificationTypes],
},
user: {
type: 'object',
ref: 'UserLite',
optional: true, nullable: true,
},
userId: {
type: 'string',
optional: true, nullable: true,
format: 'id',
},
note: {
type: 'object',
ref: 'Note',
optional: true, nullable: true,
},
reaction: {
type: 'string',
optional: true, nullable: true,
},
choice: {
type: 'number',
optional: true, nullable: true,
},
invitation: {
type: 'object',
optional: true, nullable: true,
},
body: {
type: 'string',
optional: true, nullable: true,
},
header: {
type: 'string',
optional: true, nullable: true,
},
icon: {
type: 'string',
optional: true, nullable: true,
},
},
} as const;

View File

@ -0,0 +1,51 @@
export const packedPageSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
summary: {
type: 'string',
optional: false, nullable: true,
},
content: {
type: 'array',
optional: false, nullable: false,
},
variables: {
type: 'array',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,25 @@
export const packedQueueCountSchema = {
type: 'object',
properties: {
waiting: {
type: 'number',
optional: false, nullable: false,
},
active: {
type: 'number',
optional: false, nullable: false,
},
completed: {
type: 'number',
optional: false, nullable: false,
},
failed: {
type: 'number',
optional: false, nullable: false,
},
delayed: {
type: 'number',
optional: false, nullable: false,
},
},
} as const;

View File

@ -0,0 +1,34 @@
export const packedUserGroupSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
ownerId: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
userIds: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,29 @@
export const packedUserListSchema = {
type: 'object',
properties: {
id: {
type: 'string',
optional: false, nullable: false,
format: 'id',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string',
optional: false, nullable: false,
format: 'date-time',
},
name: {
type: 'string',
optional: false, nullable: false,
},
userIds: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
},
} as const;

View File

@ -0,0 +1,467 @@
export const packedUserLiteSchema = {
type: 'object',
properties: {
id: {
type: 'string',
nullable: false, optional: false,
format: 'id',
example: 'xxxxxxxxxx',
},
name: {
type: 'string',
nullable: true, optional: false,
example: '藍',
},
username: {
type: 'string',
nullable: false, optional: false,
example: 'ai',
},
host: {
type: 'string',
nullable: true, optional: false,
example: 'misskey.example.com',
},
avatarUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
avatarBlurhash: {
type: 'any',
nullable: true, optional: false,
},
avatarColor: {
type: 'any',
nullable: true, optional: false,
default: null,
},
isAdmin: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isModerator: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isBot: {
type: 'boolean',
nullable: false, optional: true,
},
isCat: {
type: 'boolean',
nullable: false, optional: true,
},
emojis: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
url: {
type: 'string',
nullable: false, optional: false,
format: 'url',
},
},
},
},
onlineStatus: {
type: 'string',
format: 'url',
nullable: true, optional: false,
enum: ['unknown', 'online', 'active', 'offline'],
},
},
} as const;
export const packedUserDetailedNotMeOnlySchema = {
type: 'object',
properties: {
url: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
uri: {
type: 'string',
format: 'uri',
nullable: true, optional: false,
},
createdAt: {
type: 'string',
nullable: false, optional: false,
format: 'date-time',
},
updatedAt: {
type: 'string',
nullable: true, optional: false,
format: 'date-time',
},
lastFetchedAt: {
type: 'string',
nullable: true, optional: false,
format: 'date-time',
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
bannerBlurhash: {
type: 'any',
nullable: true, optional: false,
},
bannerColor: {
type: 'any',
nullable: true, optional: false,
default: null,
},
isLocked: {
type: 'boolean',
nullable: false, optional: false,
},
isSilenced: {
type: 'boolean',
nullable: false, optional: false,
},
isSuspended: {
type: 'boolean',
nullable: false, optional: false,
example: false,
},
description: {
type: 'string',
nullable: true, optional: false,
example: 'Hi masters, I am Ai!',
},
location: {
type: 'string',
nullable: true, optional: false,
},
birthday: {
type: 'string',
nullable: true, optional: false,
example: '2018-03-12',
},
lang: {
type: 'string',
nullable: true, optional: false,
example: 'ja-JP',
},
fields: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
value: {
type: 'string',
nullable: false, optional: false,
},
},
maxLength: 4,
},
},
followersCount: {
type: 'number',
nullable: false, optional: false,
},
followingCount: {
type: 'number',
nullable: false, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
pinnedNoteIds: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
},
pinnedNotes: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
ref: 'Note',
},
},
pinnedPageId: {
type: 'string',
nullable: true, optional: false,
},
pinnedPage: {
type: 'object',
nullable: true, optional: false,
ref: 'Page',
},
publicReactions: {
type: 'boolean',
nullable: false, optional: false,
},
twoFactorEnabled: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
usePasswordLessLogin: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
securityKeys: {
type: 'boolean',
nullable: false, optional: false,
default: false,
},
//#region relations
isFollowing: {
type: 'boolean',
nullable: false, optional: true,
},
isFollowed: {
type: 'boolean',
nullable: false, optional: true,
},
hasPendingFollowRequestFromYou: {
type: 'boolean',
nullable: false, optional: true,
},
hasPendingFollowRequestToYou: {
type: 'boolean',
nullable: false, optional: true,
},
isBlocking: {
type: 'boolean',
nullable: false, optional: true,
},
isBlocked: {
type: 'boolean',
nullable: false, optional: true,
},
isMuted: {
type: 'boolean',
nullable: false, optional: true,
},
//#endregion
},
} as const;
export const packedMeDetailedOnlySchema = {
type: 'object',
properties: {
avatarId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
bannerId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
injectFeaturedNote: {
type: 'boolean',
nullable: true, optional: false,
},
receiveAnnouncementEmail: {
type: 'boolean',
nullable: true, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: true, optional: false,
},
carefulBot: {
type: 'boolean',
nullable: true, optional: false,
},
autoAcceptFollowed: {
type: 'boolean',
nullable: true, optional: false,
},
noCrawle: {
type: 'boolean',
nullable: true, optional: false,
},
isExplorable: {
type: 'boolean',
nullable: false, optional: false,
},
isDeleted: {
type: 'boolean',
nullable: false, optional: false,
},
hideOnlineStatus: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadSpecifiedNotes: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadMentions: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadAnnouncement: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadAntenna: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadChannel: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadMessagingMessage: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadNotification: {
type: 'boolean',
nullable: false, optional: false,
},
hasPendingReceivedFollowRequest: {
type: 'boolean',
nullable: false, optional: false,
},
integrations: {
type: 'object',
nullable: true, optional: false,
},
mutedWords: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
},
mutedInstances: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
mutingNotificationTypes: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
emailNotificationTypes: {
type: 'array',
nullable: true, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
//#region secrets
email: {
type: 'string',
nullable: true, optional: true,
},
emailVerified: {
type: 'boolean',
nullable: true, optional: true,
},
securityKeysList: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'object',
nullable: false, optional: false,
},
},
//#endregion
},
} as const;
export const packedUserDetailedNotMeSchema = {
type: 'object',
allOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailedNotMeOnly',
},
],
} as const;
export const packedMeDetailedSchema = {
type: 'object',
allOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailedNotMeOnly',
},
{
type: 'object',
ref: 'MeDetailedOnly',
},
],
} as const;
export const packedUserDetailedSchema = {
oneOf: [
{
type: 'object',
ref: 'UserDetailedNotMe',
},
{
type: 'object',
ref: 'MeDetailed',
},
],
} as const;
export const packedUserSchema = {
oneOf: [
{
type: 'object',
ref: 'UserLite',
},
{
type: 'object',
ref: 'UserDetailed',
},
],
} as const;

View File

@ -1,13 +1,11 @@
type Await<T> = T extends Promise<infer U> ? U : T; export type Promiseable<T> = {
[K in keyof T]: Promise<T[K]> | T[K];
type AwaitAll<T> = {
[P in keyof T]: Await<T[P]>;
}; };
export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> { export async function awaitAll<T>(obj: Promiseable<T>): Promise<T> {
const target = {} as any; const target = {} as T;
const keys = Object.keys(obj); const keys = Object.keys(obj) as unknown as (keyof T)[];
const values = Object.values(obj); const values = Object.values(obj) as any[];
const resolvedValues = await Promise.all(values.map(value => const resolvedValues = await Promise.all(values.map(value =>
(!value || !value.constructor || value.constructor.name !== 'Object') (!value || !value.constructor || value.constructor.name !== 'Object')

View File

@ -213,6 +213,16 @@ export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']
}); });
} }
export function createImportCustomEmojisJob(user: ThinUser, fileId: DriveFile['id']) {
return dbQueue.add('importCustomEmojis', {
user: user,
fileId: fileId,
}, {
removeOnComplete: true,
removeOnFail: true,
});
}
export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) { export function createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) {
return dbQueue.add('deleteAccount', { return dbQueue.add('deleteAccount', {
user: user, user: user,

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs'; import * as fs from 'fs';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Blockings } from '@/models/index'; import { Users, Blockings } from '@/models/index';
@ -86,7 +86,7 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true); const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -6,11 +6,12 @@ import { ulid } from 'ulid';
const mime = require('mime-types'); const mime = require('mime-types');
const archiver = require('archiver'); const archiver = require('archiver');
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { Users, Emojis } from '@/models/index'; import { Users, Emojis } from '@/models/index';
import { } from '@/queue/types'; import { } from '@/queue/types';
import { downloadUrl } from '@/misc/download-url'; import { downloadUrl } from '@/misc/download-url';
import config from '@/config/index';
const logger = queueLogger.createSubLogger('export-custom-emojis'); const logger = queueLogger.createSubLogger('export-custom-emojis');
@ -52,7 +53,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
}); });
}; };
await writeMeta(`{"metaVersion":1,"emojis":[`); await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`);
const customEmojis = await Emojis.find({ const customEmojis = await Emojis.find({
where: { where: {
@ -64,21 +65,25 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
}); });
for (const emoji of customEmojis) { for (const emoji of customEmojis) {
const exportId = ulid().toLowerCase();
const ext = mime.extension(emoji.type); const ext = mime.extension(emoji.type);
const emojiPath = path + '/' + exportId + (ext ? '.' + ext : ''); const fileName = emoji.name + (ext ? '.' + ext : '');
const emojiPath = path + '/' + fileName;
fs.writeFileSync(emojiPath, '', 'binary'); fs.writeFileSync(emojiPath, '', 'binary');
let downloaded = false; let downloaded = false;
try { try {
await downloadUrl(emoji.url, emojiPath); await downloadUrl(emoji.originalUrl, emojiPath);
downloaded = true; downloaded = true;
} catch (e) { // TODO: 何度か再試行 } catch (e) { // TODO: 何度か再試行
logger.error(e); logger.error(e);
} }
if (!downloaded) {
fs.unlinkSync(emojiPath);
}
const content = JSON.stringify({ const content = JSON.stringify({
id: exportId, fileName: fileName,
downloaded: downloaded, downloaded: downloaded,
emoji: emoji, emoji: emoji,
}); });
@ -106,7 +111,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
logger.succ(`Exported to: ${archivePath}`); logger.succ(`Exported to: ${archivePath}`);
const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.zip'; const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.zip';
const driveFile = await addFile(user, archivePath, fileName, null, null, true); const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs'; import * as fs from 'fs';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Followings, Mutings } from '@/models/index'; import { Users, Followings, Mutings } from '@/models/index';
@ -87,7 +87,7 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true); const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs'; import * as fs from 'fs';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, Mutings } from '@/models/index'; import { Users, Mutings } from '@/models/index';
@ -86,7 +86,7 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true); const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs'; import * as fs from 'fs';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { Users, Notes, Polls } from '@/models/index'; import { Users, Notes, Polls } from '@/models/index';
import { MoreThan } from 'typeorm'; import { MoreThan } from 'typeorm';
@ -95,7 +95,7 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json';
const driveFile = await addFile(user, path, fileName, null, null, true); const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -3,7 +3,7 @@ import * as tmp from 'tmp';
import * as fs from 'fs'; import * as fs from 'fs';
import { queueLogger } from '../../logger'; import { queueLogger } from '../../logger';
import addFile from '@/services/drive/add-file'; import { addFile } from '@/services/drive/add-file';
import * as dateFormat from 'dateformat'; import * as dateFormat from 'dateformat';
import { getFullApAccount } from '@/misc/convert-host'; import { getFullApAccount } from '@/misc/convert-host';
import { Users, UserLists, UserListJoinings } from '@/models/index'; import { Users, UserLists, UserListJoinings } from '@/models/index';
@ -63,7 +63,7 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any):
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName, null, null, true); const driveFile = await addFile({ user, path, name: fileName, force: true });
logger.succ(`Exported to: ${driveFile.id}`); logger.succ(`Exported to: ${driveFile.id}`);
cleanup(); cleanup();

View File

@ -0,0 +1,85 @@
import * as Bull from 'bull';
import * as tmp from 'tmp';
import * as fs from 'fs';
const unzipper = require('unzipper');
import { getConnection } from 'typeorm';
import { queueLogger } from '../../logger';
import { downloadUrl } from '@/misc/download-url';
import { DriveFiles, Emojis } from '@/models/index';
import { DbUserImportJobData } from '@/queue/types';
import { addFile } from '@/services/drive/add-file';
import { genId } from '@/misc/gen-id';
const logger = queueLogger.createSubLogger('import-custom-emojis');
// TODO: 名前衝突時の動作を選べるようにする
export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> {
logger.info(`Importing custom emojis ...`);
const file = await DriveFiles.findOne({
id: job.data.fileId,
});
if (file == null) {
done();
return;
}
// Create temp dir
const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => {
tmp.dir((e, path, cleanup) => {
if (e) return rej(e);
res([path, cleanup]);
});
});
logger.info(`Temp dir is ${path}`);
const destPath = path + '/emojis.zip';
try {
fs.writeFileSync(destPath, '', 'binary');
await downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
logger.error(e);
throw e;
}
const outputPath = path + '/emojis';
const unzipStream = fs.createReadStream(destPath);
const extractor = unzipper.Extract({ path: outputPath });
extractor.on('close', async () => {
const metaRaw = fs.readFileSync(outputPath + '/meta.json', 'utf-8');
const meta = JSON.parse(metaRaw);
for (const record of meta.emojis) {
if (!record.downloaded) continue;
const emojiInfo = record.emoji;
const emojiPath = outputPath + '/' + record.fileName;
await Emojis.delete({
name: emojiInfo.name,
});
const driveFile = await addFile({ user: null, path: emojiPath, name: record.fileName, force: true });
const emoji = await Emojis.insert({
id: genId(),
updatedAt: new Date(),
name: emojiInfo.name,
category: emojiInfo.category,
host: null,
aliases: emojiInfo.aliases,
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
}).then(x => Emojis.findOneOrFail(x.identifiers[0]));
}
await getConnection().queryResultCache!.remove(['meta_emojis']);
cleanup();
logger.succ('Imported');
done();
});
unzipStream.pipe(extractor);
logger.succ(`Unzipping to ${outputPath}`);
}

View File

@ -46,13 +46,13 @@ export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done:
}); });
if (list == null) { if (list == null) {
list = await UserLists.save({ list = await UserLists.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
name: listName, name: listName,
userIds: [], userIds: [],
}); }).then(x => UserLists.findOneOrFail(x.identifiers[0]));
} }
let target = isSelfHost(host!) ? await Users.findOne({ let target = isSelfHost(host!) ? await Users.findOne({

View File

@ -12,6 +12,7 @@ import { importUserLists } from './import-user-lists';
import { deleteAccount } from './delete-account'; import { deleteAccount } from './delete-account';
import { importMuting } from './import-muting'; import { importMuting } from './import-muting';
import { importBlocking } from './import-blocking'; import { importBlocking } from './import-blocking';
import { importCustomEmojis } from './import-custom-emojis';
const jobs = { const jobs = {
deleteDriveFiles, deleteDriveFiles,
@ -25,6 +26,7 @@ const jobs = {
importMuting, importMuting,
importBlocking, importBlocking,
importUserLists, importUserLists,
importCustomEmojis,
deleteAccount, deleteAccount,
} as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; } as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>;

View File

@ -1,4 +1,4 @@
import uploadFromUrl from '@/services/drive/upload-from-url'; import { uploadFromUrl } from '@/services/drive/upload-from-url';
import { IRemoteUser } from '@/models/entities/user'; import { IRemoteUser } from '@/models/entities/user';
import Resolver from '../resolver'; import Resolver from '../resolver';
import { fetchMeta } from '@/misc/fetch-meta'; import { fetchMeta } from '@/misc/fetch-meta';
@ -28,9 +28,15 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
logger.info(`Creating the Image: ${image.url}`); logger.info(`Creating the Image: ${image.url}`);
const instance = await fetchMeta(); const instance = await fetchMeta();
const cache = instance.cacheRemoteFiles;
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)); let file = await uploadFromUrl({
url: image.url,
user: actor,
uri: image.url,
sensitive: image.sensitive,
isLink: !instance.cacheRemoteFiles,
comment: truncate(image.name, DB_MAX_IMAGE_COMMENT_LENGTH)
});
if (file.isLink) { if (file.isLink) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、

View File

@ -320,14 +320,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
if ((tag.updated != null && exists.updatedAt == null) if ((tag.updated != null && exists.updatedAt == null)
|| (tag.id != null && exists.uri == null) || (tag.id != null && exists.uri == null)
|| (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt)
|| (tag.icon!.url !== exists.url) || (tag.icon!.url !== exists.originalUrl)
) { ) {
await Emojis.update({ await Emojis.update({
host, host,
name, name,
}, { }, {
uri: tag.id, uri: tag.id,
url: tag.icon!.url, originalUrl: tag.icon!.url,
publicUrl: tag.icon!.url,
updatedAt: new Date(), updatedAt: new Date(),
}); });
@ -342,14 +343,15 @@ export async function extractEmojis(tags: IObject | IObject[], host: string): Pr
logger.info(`register emoji host=${host}, name=${name}`); logger.info(`register emoji host=${host}, name=${name}`);
return await Emojis.save({ return await Emojis.insert({
id: genId(), id: genId(),
host, host,
name, name,
uri: tag.id, uri: tag.id,
url: tag.icon!.url, originalUrl: tag.icon!.url,
publicUrl: tag.icon!.url,
updatedAt: new Date(), updatedAt: new Date(),
aliases: [], aliases: [],
} as Partial<Emoji>); } as Partial<Emoji>).then(x => Emojis.findOneOrFail(x.identifiers[0]));
})); }));
} }

View File

@ -9,6 +9,6 @@ export default (emoji: Emoji) => ({
icon: { icon: {
type: 'Image', type: 'Image',
mediaType: emoji.type || 'image/png', mediaType: emoji.type || 'image/png',
url: emoji.url, url: emoji.publicUrl || emoji.originalUrl, // || emoji.originalUrl してるのは後方互換性のため
}, },
}); });

View File

@ -0,0 +1,15 @@
import config from '@/config/index';
import { IObject, IActivity } from '@/remote/activitypub/type';
import { ILocalUser, IRemoteUser } from '@/models/entities/user';
import { getInstanceActor } from '@/services/instance-actor';
// to anonymise reporters, the reporting actor must be a system user
// object has to be a uri or array of uris
export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => {
return {
type: 'Flag',
actor: `${config.url}/users/${user.id}`,
content,
object,
};
};

View File

@ -32,7 +32,7 @@ export const renderActivity = (x: any): IActivity | null => {
PropertyValue: 'schema:PropertyValue', PropertyValue: 'schema:PropertyValue',
value: 'schema:value', value: 'schema:value',
// Misskey // Misskey
misskey: `${config.url}/ns#`, misskey: 'https://misskey-hub.net/ns#',
'_misskey_content': 'misskey:_misskey_content', '_misskey_content': 'misskey:_misskey_content',
'_misskey_quote': 'misskey:_misskey_quote', '_misskey_quote': 'misskey:_misskey_quote',
'_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_reaction': 'misskey:_misskey_reaction',

View File

@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option?
}); });
} }
const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser; const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser | null;
const acctLower = `${usernameLower}@${host}`; const acctLower = `${usernameLower}@${host}`;

View File

@ -67,7 +67,7 @@ router.get('/notes/:note', async (ctx, next) => {
const note = await Notes.findOne({ const note = await Notes.findOne({
id: ctx.params.note, id: ctx.params.note,
visibility: In(['public', 'home']), visibility: In(['public' as const, 'home' as const]),
localOnly: false, localOnly: false,
}); });
@ -96,7 +96,7 @@ router.get('/notes/:note/activity', async ctx => {
const note = await Notes.findOne({ const note = await Notes.findOne({
id: ctx.params.note, id: ctx.params.note,
userHost: null, userHost: null,
visibility: In(['public', 'home']), visibility: In(['public' as const, 'home' as const]),
localOnly: false, localOnly: false,
}); });

View File

@ -10,7 +10,7 @@ export class AuthenticationError extends Error {
} }
} }
export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { export default async (token: string | null): Promise<[User | null | undefined, AccessToken | null | undefined]> => {
if (token == null) { if (token == null) {
return [null, null]; return [null, null];
} }

View File

@ -1,5 +1,5 @@
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import limiter from './limiter'; import { limiter } from './limiter';
import { User } from '@/models/entities/user'; import { User } from '@/models/entities/user';
import endpoints from './endpoints'; import endpoints from './endpoints';
import { ApiError } from './error'; import { ApiError } from './error';

View File

@ -29,14 +29,14 @@ export default function(ctx: Koa.Context, user: ILocalUser, redirect = false) {
(async () => { (async () => {
// Append signin history // Append signin history
const record = await Signins.save({ const record = await Signins.insert({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
userId: user.id, userId: user.id,
ip: ctx.ip, ip: ctx.ip,
headers: ctx.headers, headers: ctx.headers,
success: true, success: true,
}); }).then(x => Signins.findOneOrFail(x.identifiers[0]));
// Publish signin event // Publish signin event
publishMainStream(user.id, 'signin', await Signins.pack(record)); publishMainStream(user.id, 'signin', await Signins.pack(record));

Some files were not shown because too many files have changed in this diff Show More