mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-06 13:58:05 +03:00
Merge pull request #486 from TNG-dev/develop
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Dockerfile
|
||||
@@ -0,0 +1,8 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
trim_trailing_whitespace = true
|
||||
+112
-80
@@ -1,81 +1,113 @@
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"import"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/errors"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error", {
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "auto"
|
||||
}],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"require-await": "warn",
|
||||
"no-await-in-loop": "warn",
|
||||
"no-unused-labels": "error",
|
||||
"prefer-const": "error",
|
||||
"no-console": "error",
|
||||
"eqeqeq": "error",
|
||||
"no-eval": "error",
|
||||
"no-var": "error",
|
||||
"no-new-object": "error",
|
||||
"quote-props": ["error", "as-needed"],
|
||||
"no-array-constructor": "error",
|
||||
"prefer-template": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-loop-func": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"default-param-last": "error",
|
||||
"no-new-func": "error",
|
||||
"arrow-body-style": "error",
|
||||
// "arrow-parens": ["error", "as-needed"] prettier hates this
|
||||
"no-useless-constructor": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"space-before-blocks": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"curly": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-implied-eval": "error",
|
||||
"yoda": "error",
|
||||
"no-labels": "error",
|
||||
"no-useless-concat": "error",
|
||||
"radix": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-proto": "error",
|
||||
"no-return-await": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"require-unicode-regexp": "error",
|
||||
|
||||
}
|
||||
{
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"import"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts",
|
||||
"*.tsx"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": [
|
||||
"./tsconfig.json"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:import/typescript",
|
||||
"plugin:import/warnings",
|
||||
"plugin:import/errors"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"endOfLine": "lf",
|
||||
"useTabs": true
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{
|
||||
"checksVoidReturn": false
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"argsIgnorePattern": "logger"
|
||||
}
|
||||
],
|
||||
// breaks with baseUrl
|
||||
"import/no-unresolved": "off",
|
||||
"require-await": "warn",
|
||||
"no-await-in-loop": "warn",
|
||||
"no-unused-labels": "error",
|
||||
"prefer-const": "error",
|
||||
"no-console": "error",
|
||||
"eqeqeq": "error",
|
||||
"no-eval": "error",
|
||||
"no-var": "error",
|
||||
"no-new-object": "error",
|
||||
"quote-props": [
|
||||
"error",
|
||||
"as-needed"
|
||||
],
|
||||
"no-array-constructor": "error",
|
||||
"prefer-template": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-loop-func": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"default-param-last": "error",
|
||||
"no-new-func": "error",
|
||||
"arrow-body-style": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"dot-notation": "error",
|
||||
"space-before-blocks": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"prefer-arrow-callback": "error",
|
||||
"curly": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-implied-eval": "error",
|
||||
"yoda": "error",
|
||||
"no-labels": "error",
|
||||
"no-useless-concat": "error",
|
||||
"radix": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-param-reassign": "error",
|
||||
"no-proto": "error",
|
||||
"no-return-await": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-return": "error",
|
||||
"require-unicode-regexp": "error"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#188 - Spaces to tabs
|
||||
47c981f4b3189e9cf995ed03256609f3bf56b930
|
||||
|
||||
#219 - Refactor entire repo to use baseUrl "/src"
|
||||
897e832b01fafade554f99c4fba629c42a50468f
|
||||
|
||||
#435 - Standardise line breaks to LF.
|
||||
7d477970c610b223d42fbd0a3fccffd53498271e
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: zkldi
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: zkldi
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
codecov:
|
||||
require_ci_to_pass: yes
|
||||
branch: develop
|
||||
|
||||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "50...100"
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
informational: true
|
||||
project:
|
||||
default:
|
||||
target: 80
|
||||
threshold: 2
|
||||
base: auto
|
||||
informational: true
|
||||
|
||||
parsers:
|
||||
gcov:
|
||||
branch_detection:
|
||||
conditional: yes
|
||||
loop: yes
|
||||
method: no
|
||||
macro: no
|
||||
Vendored
+77
@@ -0,0 +1,77 @@
|
||||
// Config file that the CI deployment uses. This is moved to $pwd/conf.json5 before CI tests are ran.
|
||||
|
||||
{
|
||||
MONGO_DATABASE_NAME: "testingdb",
|
||||
CAPTCHA_SECRET_KEY: "unused",
|
||||
SESSION_SECRET: "unused",
|
||||
FLO_API_URL: "https://flo.example.com",
|
||||
EAG_API_URL: "https://eag.example.com",
|
||||
MIN_API_URL: "https://min.example.com",
|
||||
ARC_API_URL: "https://arc.example.com",
|
||||
FLO_OAUTH2_INFO: {
|
||||
CLIENT_ID: "DUMMY_CLIENT_ID",
|
||||
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
|
||||
REDIRECT_URI: "https://example.com",
|
||||
},
|
||||
EAG_OAUTH2_INFO: {
|
||||
CLIENT_ID: "DUMMY_CLIENT_ID",
|
||||
CLIENT_SECRET: "DUMMY_CLIENT_SECRET",
|
||||
REDIRECT_URI: "https://example.com",
|
||||
},
|
||||
ARC_AUTH_TOKEN: "unused",
|
||||
ENABLE_SERVER_HTTPS: false,
|
||||
OUR_URL: "https://example.com",
|
||||
INVITE_CODE_CONFIG: {
|
||||
BATCH_SIZE: 2,
|
||||
INVITE_CAP: 100,
|
||||
BETA_USER_BONUS: 5,
|
||||
},
|
||||
CDN_CONFIG: {
|
||||
WEB_LOCATION: "/cdn",
|
||||
SAVE_LOCATION: {
|
||||
TYPE: "LOCAL_FILESYSTEM",
|
||||
LOCATION: "./test-cdn",
|
||||
SERVE_OWN_CDN: true,
|
||||
},
|
||||
},
|
||||
TACHI_CONFIG: {
|
||||
TYPE: "omni",
|
||||
NAME: "Tachi Testing",
|
||||
GAMES: [
|
||||
"iidx",
|
||||
"museca",
|
||||
"maimai",
|
||||
"sdvx",
|
||||
"ddr",
|
||||
"bms",
|
||||
"chunithm",
|
||||
"usc",
|
||||
],
|
||||
IMPORT_TYPES: [
|
||||
"file/eamusement-iidx-csv",
|
||||
"file/batch-manual",
|
||||
"file/solid-state-squad",
|
||||
"file/mer-iidx",
|
||||
"file/pli-iidx-csv",
|
||||
"ir/direct-manual",
|
||||
"ir/barbatos",
|
||||
"ir/fervidex",
|
||||
"ir/fervidex-static",
|
||||
"ir/beatoraja",
|
||||
"ir/usc",
|
||||
"ir/kshook-sv3c",
|
||||
"api/arc-iidx",
|
||||
"api/arc-sdvx",
|
||||
"api/eag-iidx",
|
||||
"api/eag-sdvx",
|
||||
"api/flo-iidx",
|
||||
"api/flo-sdvx",
|
||||
"api/min-sdvx",
|
||||
],
|
||||
},
|
||||
LOGGER_CONFIG: {
|
||||
FILE: false,
|
||||
CONSOLE: true,
|
||||
LOG_LEVEL: "info",
|
||||
},
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
name: Test, Build, Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, develop]
|
||||
pull_request:
|
||||
branches: [master, develop]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NODE_ENV: "test"
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js 16
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Install PNPM
|
||||
uses: pnpm/action-setup@v2.0.1
|
||||
with:
|
||||
version: 6.0.2
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm i
|
||||
|
||||
# configure external dbs
|
||||
- uses: supercharge/mongodb-github-action@1.4.1
|
||||
- uses: supercharge/redis-github-action@1.1.0
|
||||
|
||||
- name: Move Config File
|
||||
run: mv ./.github/test.conf.json5 ./test.conf.json5
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test
|
||||
env:
|
||||
NODE_ENV: "test"
|
||||
PORT: 8080
|
||||
MONGO_URL: "127.0.0.1"
|
||||
REDIS_URL: "127.0.0.1"
|
||||
|
||||
- name: Lint Code
|
||||
run: pnpm lint
|
||||
|
||||
- name: Upload Coverage
|
||||
if: always()
|
||||
run: |
|
||||
pnpm install -g codecov
|
||||
cat coverage/lcov.info | codecov
|
||||
docker-push:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [test]
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Docker Hub login
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Develop tag
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
run: echo "BRANCH_TAG=develop" >> $GITHUB_ENV
|
||||
|
||||
- name: Stable tag
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: echo "BRANCH_TAG=stable" >> $GITHUB_ENV
|
||||
|
||||
- name: Version tag
|
||||
run: 'echo "VERSION_TAG=$(cat package.json | grep version | head -1 | awk -F: ''{ print $2 }'' | sed ''s/[", ]//g'')" >> $GITHUB_ENV'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/tachi-server:${{ env.BRANCH_TAG }},${{ secrets.DOCKER_USERNAME }}/tachi-server:${{ env.VERSION_TAG }},
|
||||
|
||||
- name: Image digest
|
||||
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docker-push]
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- name: Deploy to staging
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
uses: appleboy/ssh-action@1a8b3784eaa665f677fa114edd5683bb6a6bfaa8
|
||||
with:
|
||||
host: ${{ secrets.TACHI_HOST }}
|
||||
username: tachi
|
||||
key: ${{ secrets.TACHI_KEY }}
|
||||
script: tachi-server-deploy -s
|
||||
|
||||
- name: Deploy to live
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: appleboy/ssh-action@1a8b3784eaa665f677fa114edd5683bb6a6bfaa8
|
||||
with:
|
||||
host: ${{ secrets.TACHI_HOST }}
|
||||
username: tachi
|
||||
key: ${{ secrets.TACHI_KEY }}
|
||||
script: tachi-server-deploy
|
||||
Vendored
-89
@@ -1,89 +0,0 @@
|
||||
name: KTBlack-Server CI
|
||||
|
||||
env:
|
||||
KTBKS_MONGO_BASE_URL: "127.0.0.1"
|
||||
NODE_ENV: "test"
|
||||
KTBKS_LOG_LEVEL: "info"
|
||||
KTBKS_CAPTCHA_SECRET_KEY: "unused"
|
||||
KTBKS_SESSION_SECRET: "unused"
|
||||
KTBKS_FLO_API_URL: "unused"
|
||||
KTBKS_EAG_API_URL: "unused"
|
||||
KTBKS_ARC_API_URL: "unused"
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Build and Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Node.js 15
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 15.x
|
||||
|
||||
- name: Install PNPM
|
||||
uses: pnpm/action-setup@v2.0.1
|
||||
with:
|
||||
version: 6.0.2
|
||||
|
||||
# configure external dbs
|
||||
- uses: supercharge/mongodb-github-action@1.4.1
|
||||
- uses: supercharge/redis-github-action@1.1.0
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm i
|
||||
|
||||
# for the time being, disable lint checking.
|
||||
# - name: Lint Code
|
||||
# run: pnpm lint
|
||||
|
||||
# enable indexing for our testingdb, this results in faster action time.
|
||||
- name: Set Mongo Indexes
|
||||
run: pnpx ts-node scripts/set-indexes.ts
|
||||
|
||||
- name: Run Tests
|
||||
run: pnpm test
|
||||
|
||||
- name: Upload Coverage
|
||||
if: always()
|
||||
run: |
|
||||
pnpm install -g codecov
|
||||
cat coverage/lcov.info | codecov
|
||||
# deploy:
|
||||
# if: ${{ github.ref == 'master' }}
|
||||
# needs: test
|
||||
# name: Deploy
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout Code
|
||||
# uses: actions/checkout@v2
|
||||
|
||||
# - name: Install Node.js 15
|
||||
# uses: actions/setup-node@v2
|
||||
# with:
|
||||
# node-version: 15.x
|
||||
|
||||
# - name: Install PNPM
|
||||
# uses: pnpm/action-setup@v2.0.1
|
||||
# with:
|
||||
# version: 6.0.2
|
||||
|
||||
# # configure external dbs
|
||||
# - uses: supercharge/mongodb-github-action@1.4.1
|
||||
# - uses: supercharge/redis-github-action@1.1.0
|
||||
|
||||
# - name: Install Dependencies
|
||||
# run: pnpm i
|
||||
|
||||
# # for the time being, disable lint checking.
|
||||
# # - name: Lint Code
|
||||
# # run: pnpm lint
|
||||
+39
-17
@@ -1,17 +1,39 @@
|
||||
node_modules
|
||||
js
|
||||
# .vscode
|
||||
logs
|
||||
|
||||
.nyc_output
|
||||
coverage
|
||||
.env
|
||||
.setup_env
|
||||
|
||||
scripts/arc-db-sync/charts.json
|
||||
scripts/arc-db-sync/songs.json
|
||||
scripts/validate-database-errs/*
|
||||
|
||||
scripts/__JSON-TO-KTDB/**/*.json
|
||||
|
||||
/src/lib/env/config.json
|
||||
# General node stuffs
|
||||
node_modules
|
||||
js
|
||||
logs
|
||||
.pnpm-debug.log
|
||||
|
||||
# Test stuffs
|
||||
.nyc_output
|
||||
coverage
|
||||
.env
|
||||
.setup_env
|
||||
failed-tests.txt
|
||||
|
||||
# Config files
|
||||
conf.json5
|
||||
*.conf.json5
|
||||
|
||||
# HTTPS local certs
|
||||
cert
|
||||
|
||||
# Error Files
|
||||
scripts/validate-database-errs/*
|
||||
|
||||
# IDE Specific files
|
||||
.vscode
|
||||
.idea
|
||||
|
||||
# Inline CDN
|
||||
local-cdn
|
||||
test-cdn
|
||||
|
||||
# Weird Redis Dumps
|
||||
dump.rdb
|
||||
|
||||
scripts/quick-insert/insert.ts
|
||||
scripts/bpi-poyashi-data/output.json
|
||||
|
||||
scripts/sync-database/tachi-database-seeds
|
||||
scripts/arc-sync/cache.json
|
||||
+11
-7
@@ -1,12 +1,16 @@
|
||||
files: "src/**/*.test.ts"
|
||||
test-env: NODE_ENV=test
|
||||
jobs: 1 # disable parallel to avoid db clashes
|
||||
timeout: 500 # might need to be increased
|
||||
files: "src/test-utils/single-process-tap.ts"
|
||||
test-env: [NODE_ENV=test, TCHIS_CONF_LOCATION=test.conf.json5]
|
||||
# On a local machine, these tests run in under 2-3 minutes.
|
||||
# On github actions, these tests take an inordinate amount of time.
|
||||
# I do not know why, and it suddenly started happening one day.
|
||||
# The timeout is set to 16 minutes, which is ridiculous.
|
||||
# Doesn't matter for real machines.
|
||||
timeout: 960
|
||||
ts: true
|
||||
before: "./src/test-utils/setup.ts"
|
||||
before: "./src/test-utils/hack-setup.js"
|
||||
check-coverage: true
|
||||
branches: 80
|
||||
branches: 60
|
||||
functions: 80
|
||||
lines: 80
|
||||
statements: 80
|
||||
reporter: terse
|
||||
reporter: terse
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export $(xargs < .env) && ts-node $1
|
||||
Vendored
-24
@@ -1,24 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Run Server",
|
||||
"preLaunchTask": "npm: build",
|
||||
"envFile": "${workspaceFolder}/.env",
|
||||
"program": "${workspaceFolder}/src/main.ts",
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/js/**/*.js"
|
||||
],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**",
|
||||
"${workspaceFolder}/node_modules/**/*.js",
|
||||
],
|
||||
"outputCapture": "std",
|
||||
},
|
||||
]
|
||||
}
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||
}
|
||||
Vendored
-42
@@ -1,42 +0,0 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "devrun",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: devrun",
|
||||
"detail": "tsc && export NODE_ENV=\"dev\" && node js/main.js",
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "fulltest",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: fulltest",
|
||||
"detail": "tap",
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "runWithTsnode",
|
||||
"type": "shell",
|
||||
"command": "./.vscode/execTsNode.sh",
|
||||
"args": [
|
||||
"${relativeFile}"
|
||||
],
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "dedicated"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+12
-12
@@ -2,17 +2,17 @@
|
||||
// this is a hack fix for express-request-mock's lack of typing.
|
||||
|
||||
declare module "express-request-mock" {
|
||||
import { Request, RequestHandler, Response } from "express";
|
||||
import { RequestOptions, MockResponse } from "node-mocks-http";
|
||||
import { Request, RequestHandler, Response } from "express";
|
||||
import { RequestOptions, MockResponse } from "node-mocks-http";
|
||||
|
||||
export default function (
|
||||
callback: RequestHandler,
|
||||
options?: RequestOptions,
|
||||
decorators?: Record<string, unknown>
|
||||
): Promise<{
|
||||
req: Request;
|
||||
res: MockResponse<Response<any, Record<string, any>>>;
|
||||
request: Request;
|
||||
response: MockResponse<Response<any, Record<string, any>>>;
|
||||
}>;
|
||||
export default function (
|
||||
callback: RequestHandler,
|
||||
options?: RequestOptions,
|
||||
decorators?: Record<string, unknown>
|
||||
): Promise<{
|
||||
req: Request;
|
||||
res: MockResponse<Response<any, Record<string, any>>>;
|
||||
request: Request;
|
||||
response: MockResponse<Response<any, Record<string, any>>>;
|
||||
}>;
|
||||
}
|
||||
|
||||
Vendored
+27
-23
@@ -1,23 +1,27 @@
|
||||
import { Session, SessionData } from "express-session";
|
||||
import { SYMBOL_KtchiData } from "../../src/lib/constants/ktchi";
|
||||
import { KtchiRequestData, KtchiSessionData } from "../../src/utils/types";
|
||||
|
||||
export {};
|
||||
|
||||
// this only exists for testing context - it doesn't seem to work properly otherwise.
|
||||
declare module "express-session" {
|
||||
// Inject additional properties on express-session
|
||||
interface SessionData {
|
||||
ktchi: KtchiSessionData;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
session: Session & Partial<SessionData>;
|
||||
|
||||
[SYMBOL_KtchiData]?: Partial<KtchiRequestData>;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { Session, SessionData } from "express-session";
|
||||
import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "../../src/lib/constants/tachi";
|
||||
import { TachiRequestData, TachiSessionData } from "../../src/utils/types";
|
||||
import { APITokenDocument } from "tachi-common";
|
||||
|
||||
export {};
|
||||
|
||||
// this only exists for testing context - it doesn't seem to work properly otherwise.
|
||||
declare module "express-session" {
|
||||
// Inject additional properties on express-session
|
||||
interface SessionData {
|
||||
tachi: TachiSessionData;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
session: Session & Partial<SessionData>;
|
||||
|
||||
[SYMBOL_TachiData]?: Partial<TachiRequestData>;
|
||||
// even though this is technically *not* present on every request
|
||||
// it's always assigned in the main router, so its functionally equivalent.
|
||||
[SYMBOL_TachiAPIAuth]: APITokenDocument;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
declare module "mongodb" {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface FindOneOptions<T> {
|
||||
projectID?: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
declare module "why-is-node-running" {
|
||||
export default function log(): void;
|
||||
export default function log(): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM node:16
|
||||
WORKDIR /app
|
||||
COPY --chown=node:node . /app
|
||||
RUN npm install --silent -g pnpm && groupmod -g 1003 node && chown node:node /app
|
||||
USER node
|
||||
RUN pnpm i --silent && pnpx tsc --project tsconfig.build.json
|
||||
HEALTHCHECK --interval=15s --timeout=5s CMD curl -f http://localhost:8080/api/v1/status || exit 1
|
||||
ENV NODE_PATH=js/
|
||||
CMD ["node", "js/main.js"]
|
||||
+35
-5
@@ -1,8 +1,38 @@
|
||||

|
||||
# Tachi-Server
|
||||
|
||||
(Black)
|
||||
[](https://codecov.io/gh/zkldi/tachi-server)
|
||||
|
||||
Any colour you like...
|
||||
Tachi-Server is the backend for Tachi.
|
||||
|
||||
[](https://codecov.io/gh/zkldi/ktblack-server)
|
||||
[](https://github.com/zkldi/ktblack-server/actions/workflows/tests.yml)
|
||||
Tachi itself is a supercharged Rhythm Game Score Tracker, powering multiple games!
|
||||
|
||||
## What's it do?
|
||||
|
||||
- Hosts an express backend server.
|
||||
- Opens a public API on `/api`.
|
||||
- Supports various IR frameworks under `/ir`
|
||||
- Powers a ridiculously overengineered score import framework.
|
||||
|
||||
## Other Repos
|
||||
|
||||
`tachi-server` is one part of the larger Tachi framework. Other repositories include:
|
||||
|
||||
[tachi-common](https://github.com/tng-dev/tachi-common): Common Types and Utilities for Tachi. (Stuff that's shared between the client and the server, basically.)
|
||||
|
||||
[tachi-docs](https://github.com/tng-dev/tachi-docs): Documentation for Tachi.
|
||||
|
||||
[tachi-import-scripts](https://github.com/tng-dev/tachi-import-scripts): A UI for converting local files into Batch-Manual format, then uploading to Tachi.
|
||||
|
||||
[tachi-beatoraja-ir](https://github.com/tng-dev/tachi-beatoraja-ir): A beatoraja IR client for integrating with Tachi.
|
||||
|
||||
[tachi-database-seeds](https://github.com/tng-dev/tachi-database-seeds): Backbone data for Tachi, such as song and chart information. Free to use.
|
||||
|
||||
[tachi-bot](https://github.com/tng-dev/tachi-bot): A discord bot that integrates with the Tachi API.
|
||||
|
||||
## Where's the client?
|
||||
|
||||
The client code is private. However, making a client is *definitely* not the hard part here, and you're completely free to make your own.
|
||||
|
||||
## Documentation
|
||||
|
||||
[Over here!](https://tachi.rtfd.io)
|
||||
|
||||
Executable
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
hostname=$(hostname)
|
||||
ip=$(hostname -i)
|
||||
|
||||
cp /etc/hosts ~/hosts
|
||||
|
||||
sed -i 's/127\.0\.0\.1\s+/127\.0\.0\.1 localhost.localdomain /' ~/hosts
|
||||
sed -i "s/${ip}\s+/${ip} ${hostname}.localdomain /" ~/hosts
|
||||
|
||||
cp ~/hosts /etc/hosts
|
||||
rm ~/hosts
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#! /bin/bash
|
||||
|
||||
# This command is for dev servers only! You should use this to
|
||||
# create a self-signed HTTPS certificate for local dev.
|
||||
# That is it. This is not secure.
|
||||
mkdir -p cert
|
||||
|
||||
yes "" | openssl req -x509 -newkey rsa:4096 -keyout ./cert/key.pem -out ./cert/cert.pem -sha256 -days 365 --nodes &> /dev/null
|
||||
|
||||
echo "Created HTTPS Certificates!"
|
||||
+105
-78
@@ -1,79 +1,106 @@
|
||||
{
|
||||
"name": "ktblack-server",
|
||||
"version": "0.1.0",
|
||||
"description": "The server for Kamaitachi Black",
|
||||
"main": "js/index.js",
|
||||
"scripts": {
|
||||
"fulltest": "tap",
|
||||
"test": "tap",
|
||||
"watchtest": "tap --watch",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"lint": "eslint ./src --ext .ts --fix",
|
||||
"devrun": "tsc --project tsconfig.build.json && node -r dotenv/config js/main.js"
|
||||
},
|
||||
"author": "zkldi",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^3.0.1",
|
||||
"@types/connect-redis": "^0.0.16",
|
||||
"@types/express": "^4.17.11",
|
||||
"@types/express-session": "^1.17.3",
|
||||
"@types/multer": "^1.4.5",
|
||||
"@types/node": "^15.0.1",
|
||||
"@types/node-fetch": "^2.5.10",
|
||||
"@types/range-parser": "^1.2.3",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/tap": "^15.0.0",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"eslint": "^7.25.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"express-request-mock": "^3.1.0",
|
||||
"node-mocks-http": "^1.10.1",
|
||||
"prettier": "^2.2.1",
|
||||
"supertest": "^6.1.3",
|
||||
"tap": "^15.0.5",
|
||||
"ts-node": "^9.1.1",
|
||||
"why-is-node-running": "^2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.0.1",
|
||||
"bee-queue": "^1.3.1",
|
||||
"commander": "^7.2.0",
|
||||
"connect-redis": "^5.2.0",
|
||||
"csv-parse": "^4.15.4",
|
||||
"deepmerge": "^4.2.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.17.1",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"express-session": "^1.17.1",
|
||||
"fast-json-stable-hash": "^1.0.2",
|
||||
"fast-xml-parser": "^3.19.0",
|
||||
"kamaitachi-common": "github:zkldi/Kamaitachi-common",
|
||||
"mongodb": "^3.6.6",
|
||||
"monk": "^7.3.4",
|
||||
"multer": "^1.4.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"prudence": "^0.9.6",
|
||||
"redis": "^3.1.2",
|
||||
"typescript": "^4.2.4",
|
||||
"uuid": "^8.3.2",
|
||||
"winston": "^3.3.3"
|
||||
},
|
||||
"nyc": {
|
||||
"reporter": [
|
||||
"html",
|
||||
"lcov"
|
||||
],
|
||||
"all": true,
|
||||
"include": [
|
||||
"src/*/!(test-utils)/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
"name": "tachi-server",
|
||||
"version": "2.0.0-prerelease",
|
||||
"description": "A score tracking server.",
|
||||
"main": "js/index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "tap",
|
||||
"watchtest": "tap --watch",
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"lint": "eslint ./src --ext .ts --fix",
|
||||
"start": "tsc --project tsconfig.build.json && export NODE_PATH=js/ && node js/main.js",
|
||||
"run": "export NODE_PATH=js/ && node js/main.js",
|
||||
"runscoreworker": "export NODE_PATH=js/ && node js/lib/score-import/worker/worker.js"
|
||||
},
|
||||
"author": "zkldi",
|
||||
"license": "AGPL3",
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "5.0.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/bunyan": "^1.8.7",
|
||||
"@types/connect-redis": "0.0.16",
|
||||
"@types/express": "4.17.12",
|
||||
"@types/express-rate-limit": "5.1.2",
|
||||
"@types/express-session": "1.17.3",
|
||||
"@types/mkdirp": "1.0.1",
|
||||
"@types/multer": "1.4.6",
|
||||
"@types/node": "15.12.4",
|
||||
"@types/node-fetch": "2.5.10",
|
||||
"@types/nodemailer": "^6.4.4",
|
||||
"@types/range-parser": "1.2.3",
|
||||
"@types/rate-limit-redis": "1.7.1",
|
||||
"@types/rimraf": "3.0.0",
|
||||
"@types/safe-json-stringify": "1.1.2",
|
||||
"@types/supertest": "2.0.11",
|
||||
"@types/tap": "15.0.4",
|
||||
"@types/uuid": "8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.28.0",
|
||||
"@typescript-eslint/parser": "4.28.0",
|
||||
"eslint": "7.29.0",
|
||||
"eslint-config-prettier": "8.3.0",
|
||||
"eslint-plugin-import": "2.23.4",
|
||||
"eslint-plugin-prettier": "3.4.0",
|
||||
"express-request-mock": "3.1.0",
|
||||
"node-mocks-http": "1.10.1",
|
||||
"prettier": "2.3.1",
|
||||
"supertest": "6.1.3",
|
||||
"tap": "15.0.9",
|
||||
"tsconfig-paths": "3.10.1",
|
||||
"why-is-node-running": "2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.41.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@valuabletouch/winston-seq": "^1.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bullmq": "^1.54.4",
|
||||
"bunyan": "^1.8.15",
|
||||
"commander": "7.2.0",
|
||||
"connect-redis": "6.0.0",
|
||||
"csv-parse": "4.16.0",
|
||||
"deepmerge": "4.2.2",
|
||||
"dotenv": "10.0.0",
|
||||
"express": "4.17.1",
|
||||
"express-async-errors": "3.1.1",
|
||||
"express-rate-limit": "5.2.6",
|
||||
"express-session": "1.17.2",
|
||||
"fast-json-stable-hash": "1.0.3",
|
||||
"fast-xml-parser": "3.19.0",
|
||||
"helmet": "4.6.0",
|
||||
"json5": "2.2.0",
|
||||
"mkdirp": "1.0.4",
|
||||
"mongodb": "3.6.3",
|
||||
"monk": "7.3.4",
|
||||
"multer": "1.4.2",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-fetch": "2.6.1",
|
||||
"nodemailer": "^6.6.3",
|
||||
"prudence": "0.9.7",
|
||||
"rate-limit-redis": "2.1.0",
|
||||
"redis": "3.1.2",
|
||||
"rimraf": "3.0.2",
|
||||
"safe-json-stringify": "1.2.0",
|
||||
"seq-logging": "^1.1.1",
|
||||
"tachi-common": "0.3.10",
|
||||
"ts-node": "10.0.0",
|
||||
"typescript": "4.3.4",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "^4.5.5",
|
||||
"winston-transport": "^4.4.0"
|
||||
},
|
||||
"nyc": {
|
||||
"reporter": [
|
||||
"html",
|
||||
"lcov"
|
||||
],
|
||||
"all": true,
|
||||
"include": [
|
||||
"src/*/!(test-utils)/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.test.ts",
|
||||
"src/external/mongo/schemas.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+1917
-882
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"0 debug pnpm:scope": {
|
||||
"selected": 1
|
||||
},
|
||||
"1 error pnpm": {
|
||||
"code": "ERR_PNPM_CANNOT_REMOVE_MISSING_DEPS",
|
||||
"err": {
|
||||
"name": "Error",
|
||||
"message": "Cannot remove 'better-sqlite3': project has no dependencies of any kind",
|
||||
"code": "ERR_PNPM_CANNOT_REMOVE_MISSING_DEPS",
|
||||
"stack": "Error: Cannot remove 'better-sqlite3': project has no dependencies of any kind\n at Object.handler (/home/zkldi/pnpm-global/5/node_modules/.pnpm/pnpm@6.4.0/node_modules/pnpm/dist/pnpm.cjs:116381:15)\n at async Timeout._onTimeout (/home/zkldi/pnpm-global/5/node_modules/.pnpm/pnpm@6.4.0/node_modules/pnpm/dist/pnpm.cjs:124690:24)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,4 @@ https://github.com/TypeStrong/ts-node
|
||||
|
||||
They have only been tested for ts-node.
|
||||
|
||||
npm install -g ts-node
|
||||
|
||||
it's probably installed on the machine you're seeing this on, winter(?). love ya.
|
||||
pnpm install -g ts-node
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import CreateLogCtx from "../../../src/common/logger";
|
||||
import db from "../../../src/db/db";
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const charts = JSON.parse(fs.readFileSync(path.join(__dirname, "./charts.json"), "utf-8"));
|
||||
const songs = JSON.parse(fs.readFileSync(path.join(__dirname, "./songs.json"), "utf-8"));
|
||||
|
||||
(async () => {
|
||||
logger.info("starting chart import.");
|
||||
await db.charts.bms.remove({});
|
||||
await db.charts.bms.insert(charts);
|
||||
logger.info("imported charts.");
|
||||
|
||||
await db.songs.bms.remove({});
|
||||
await db.songs.bms.insert(songs);
|
||||
|
||||
logger.info("done.");
|
||||
})();
|
||||
@@ -1,100 +0,0 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
// diffs an out JSON with the kamaitachi db, optionally syncs with -s.
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import db from "../../../src/db/db";
|
||||
import { MDBJson } from "./types";
|
||||
import { Difficulties, Playtypes, ChartDocument } from "kamaitachi-common";
|
||||
import CreateLogCtx from "../../../src/common/logger";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
const program = new Command();
|
||||
program.option("-s --sync", "Sync KTDB with the file.");
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
const dataset = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "./out.json"), "utf-8")
|
||||
) as MDBJson[];
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
(async () => {
|
||||
for (const data of dataset) {
|
||||
for (const d in data.difficulties) {
|
||||
const diff = d as keyof typeof data.difficulties;
|
||||
|
||||
if (data.difficulties[diff] === 0) {
|
||||
logger.verbose(`Skipping chart ${data.title} ${diff}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const spl = diff.split("-");
|
||||
const chart = (await db.charts.iidx.findOne({
|
||||
"data.inGameID": data.songID,
|
||||
playtype: spl[0] as Playtypes["iidx"],
|
||||
difficulty: spl[1] as Difficulties["iidx:SP" | "iidx:DP"],
|
||||
isPrimary: true,
|
||||
})) as ChartDocument<"iidx:SP" | "iidx:DP">;
|
||||
|
||||
if (!chart) {
|
||||
logger.warn(`No chart exists for ${data.title} (${diff}).`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (chart.levelNum !== data.difficulties[diff]) {
|
||||
logger.warn(
|
||||
`Level Mismatch with ${data.title} (${diff}) [${chart.levelNum} -> ${data.difficulties[diff]}]`
|
||||
);
|
||||
|
||||
if (options.sync) {
|
||||
await db.charts.iidx.update(
|
||||
{
|
||||
_id: chart._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
levelNum: data.difficulties[diff],
|
||||
level: data.difficulties[diff].toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (chart.data.notecount !== data.notecounts[diff]) {
|
||||
if (
|
||||
data.notecounts[diff] === undefined ||
|
||||
Math.abs(chart.data.notecount - data.notecounts[diff]!) > 2
|
||||
) {
|
||||
logger.error(
|
||||
`Significant Notecount Mismatch with ${data.title} (${diff}) [${chart.data.notecount} -> ${data.notecounts[diff]}] - skipping.`
|
||||
);
|
||||
// continue;
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Notecount Mismatch with ${data.title} (${diff}) [${chart.data.notecount} -> ${data.notecounts[diff]}]`
|
||||
);
|
||||
|
||||
if (options.sync) {
|
||||
await db.charts.iidx.update(
|
||||
{
|
||||
_id: chart._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
"data.notecount": data.notecounts[diff],
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info("done");
|
||||
})();
|
||||
@@ -1,12 +0,0 @@
|
||||
export interface MDBJson {
|
||||
title: string;
|
||||
artist: string;
|
||||
marquee: string;
|
||||
folder: number;
|
||||
genre: string;
|
||||
difficulties: Record<DiffNames, number>;
|
||||
notecounts: Partial<Record<DiffNames, number>>;
|
||||
songID: number;
|
||||
}
|
||||
|
||||
type DiffNames = `${"SP" | "DP"}-${"BEGINNER" | "NORMAL" | "HYPER" | "ANOTHER" | "LEGGENDARIA"}`;
|
||||
@@ -1,56 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"chunithm:Single">> {
|
||||
const song = await db.songs.chunithm.findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"chunithm:Single"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": !!c.flags["IN BASE GAME"],
|
||||
OMNIMIX: !!c.flags.OMNIMIX,
|
||||
},
|
||||
data: {
|
||||
inGameID: c.internals.inGameID,
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.chunithm.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.chunithm.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.chunithm, "charts-chunithm", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,62 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { ChartDocument, SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
import { oldKTDB } from "./old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"ddr:SP" | "ddr:DP">> {
|
||||
const song = (await db.songs.ddr.findOne({
|
||||
id: c.id,
|
||||
})) as SongDocument<"ddr">;
|
||||
|
||||
const oldSong = await oldKTDB.get("songs-ddr").findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"ddr:SP" | "ddr:DP"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": true,
|
||||
"N-1": true,
|
||||
},
|
||||
data: {
|
||||
inGameID: c.internals.inGameID,
|
||||
songHash: oldSong.internals.songHash,
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.ddr.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.ddr.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.ddr, "charts-ddr", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,60 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ChartDocument, SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
import { oldKTDB } from "./old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"gitadora:Gita" | "gitadora:Dora">> {
|
||||
const song = (await db.songs.gitadora.findOne({
|
||||
id: c.id,
|
||||
})) as SongDocument<"gitadora">;
|
||||
|
||||
const oldSong = await oldKTDB.get("songs-gitadora").findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"gitadora:Gita" | "gitadora:Dora"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": true,
|
||||
OMNIMIX: true,
|
||||
},
|
||||
data: {
|
||||
inGameID: c.internals.inGameINTID,
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.gitadora.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.gitadora.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.gitadora, "charts-gitadora", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,59 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"iidx:SP" | "iidx:DP">> {
|
||||
const song = await db.songs.iidx.findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"iidx:SP" | "iidx:DP"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": !!c.flags["IN BASE GAME"],
|
||||
OMNIMIX: !!c.flags.OMNIMIX,
|
||||
"N-1": !!c.flags["N-1"],
|
||||
},
|
||||
data: {
|
||||
inGameID: c.internals.inGameINTID,
|
||||
notecount: c.notedata.notecount,
|
||||
arcChartID: null,
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.iidx.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.iidx.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.iidx, "charts-iidx", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,53 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"museca:Single">> {
|
||||
const song = await db.songs.museca.findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"museca:Single"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": true,
|
||||
OMNIMIX: true, // false, needs to be overrode for 3 or so charts
|
||||
},
|
||||
data: {},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.museca.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.museca.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.museca, "charts-museca", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,56 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ChartDocument<"sdvx:Single">> {
|
||||
const song = await db.songs.sdvx.findOne({
|
||||
id: c.id,
|
||||
});
|
||||
|
||||
if (!song) {
|
||||
logger.severe(`Cannot find song with ID ${c.id}?`);
|
||||
throw new Error(`Cannot find song with ID ${c.id}?`);
|
||||
}
|
||||
|
||||
const newChartDoc: ChartDocument<"sdvx:Single"> = {
|
||||
rgcID: null,
|
||||
chartID: c.chartID,
|
||||
difficulty: c.difficulty,
|
||||
songID: c.id,
|
||||
playtype: c.playtype,
|
||||
levelNum: c.levelNum,
|
||||
level: c.level.toString(),
|
||||
flags: {
|
||||
"IN BASE GAME": !!c.flags["IN BASE GAME"],
|
||||
OMNIMIX: !!c.flags.OMNIMIX,
|
||||
"N-1": !!c.flags["N-1"],
|
||||
},
|
||||
data: {
|
||||
inGameID: c.internals.inGameINTID,
|
||||
},
|
||||
isPrimary: true,
|
||||
versions: [], // sentinel
|
||||
};
|
||||
|
||||
const idx = gameOrders.sdvx.indexOf(song.firstVersion!);
|
||||
|
||||
if (idx === -1) {
|
||||
logger.warn(`Invalid firstAppearance of ${song.firstVersion!}, running anyway.`);
|
||||
newChartDoc.versions = [song.firstVersion!];
|
||||
} else {
|
||||
newChartDoc.versions = gameOrders.sdvx.slice(idx);
|
||||
}
|
||||
|
||||
return newChartDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.charts.sdvx, "charts-sdvx", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,48 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { FolderDocument, Game } from "kamaitachi-common";
|
||||
import { validPlaytypes, gameHuman } from "kamaitachi-common/js/config";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
import crypto from "crypto";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
function ConvertFn(c: any): FolderDocument[] {
|
||||
throw new Error("This doesn't work, It was migrated elsewhere.");
|
||||
|
||||
// let docs = [];
|
||||
|
||||
// for (const playtype of validPlaytypes[c.game as Game]) {
|
||||
// const fd: FolderDocument = {
|
||||
// title: c.title,
|
||||
// game: c.game,
|
||||
// playtype,
|
||||
// type: "query" as const,
|
||||
// query: {
|
||||
// collection: c.query.collection,
|
||||
// body: c.query.query,
|
||||
// },
|
||||
// folderID: crypto.randomBytes(20).toString("hex"),
|
||||
// table: c.table,
|
||||
// tableIndex: 0, // ???
|
||||
// };
|
||||
|
||||
// let gh = gameHuman[c.game as Game];
|
||||
|
||||
// fd.title = fd.title.replace(new RegExp(`${gh} +`, "giu"), "");
|
||||
|
||||
// logger.info(`Porting folder ${fd.title} (${playtype})`);
|
||||
|
||||
// docs.push(fd);
|
||||
// }
|
||||
|
||||
// return docs;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.folders, "folders", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,79 +1,79 @@
|
||||
// oh boy
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { GoalDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import db from "external/mongo/db";
|
||||
import { GoalDocument } from "tachi-common";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
function ConvertFn(c: any): GoalDocument | null {
|
||||
let charts: GoalDocument["charts"];
|
||||
let charts: GoalDocument["charts"];
|
||||
|
||||
if (c.directChartID) {
|
||||
charts = {
|
||||
type: "single",
|
||||
data: c.directChartID,
|
||||
};
|
||||
} else if (c.directChartIDs) {
|
||||
charts = {
|
||||
type: "multi",
|
||||
data: c.directChartIDs,
|
||||
};
|
||||
} else if (c.chartQuery) {
|
||||
logger.warn(`Skipped goal ${c.title}, as it used cQuery.`);
|
||||
return null;
|
||||
} else {
|
||||
logger.warn(`Any Goal ${c.goalID}, ${c.title}. Skipping.`);
|
||||
return null;
|
||||
}
|
||||
if (c.directChartID) {
|
||||
charts = {
|
||||
type: "single",
|
||||
data: c.directChartID,
|
||||
};
|
||||
} else if (c.directChartIDs) {
|
||||
charts = {
|
||||
type: "multi",
|
||||
data: c.directChartIDs,
|
||||
};
|
||||
} else if (c.chartQuery) {
|
||||
logger.warn(`Skipped goal ${c.title}, as it used cQuery.`);
|
||||
return null;
|
||||
} else {
|
||||
logger.warn(`Any Goal ${c.goalID}, ${c.title}. Skipping.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const partialCriteria: Partial<GoalDocument["criteria"]> = {
|
||||
mode: "single",
|
||||
};
|
||||
const partialCriteria: Partial<GoalDocument["criteria"]> = {
|
||||
mode: "single",
|
||||
};
|
||||
|
||||
if (c.scoreQuery["scoreData¬lampIndex"]) {
|
||||
partialCriteria.key = "scoreData.lampIndex";
|
||||
} else if (c.scoreQuery["scoreData¬gradeIndex"]) {
|
||||
partialCriteria.key = "scoreData.gradeIndex";
|
||||
} else if (c.scoreQuery["scoreData¬score"]) {
|
||||
partialCriteria.key = "scoreData.score";
|
||||
} else if (c.scoreQuery["scoreData¬percent"]) {
|
||||
partialCriteria.key = "scoreData.percent";
|
||||
} else {
|
||||
throw new Error(`Invalid goal ${c.goalID} ${c.title}.`);
|
||||
}
|
||||
if (c.scoreQuery["scoreData¬lampIndex"]) {
|
||||
partialCriteria.key = "scoreData.lampIndex";
|
||||
} else if (c.scoreQuery["scoreData¬gradeIndex"]) {
|
||||
partialCriteria.key = "scoreData.gradeIndex";
|
||||
} else if (c.scoreQuery["scoreData¬score"]) {
|
||||
partialCriteria.key = "scoreData.score";
|
||||
} else if (c.scoreQuery["scoreData¬percent"]) {
|
||||
partialCriteria.key = "scoreData.percent";
|
||||
} else {
|
||||
throw new Error(`Invalid goal ${c.goalID} ${c.title}.`);
|
||||
}
|
||||
|
||||
const v = c.scoreQuery[partialCriteria.key.replace(/\./gu, "¬")];
|
||||
const v = c.scoreQuery[partialCriteria.key.replace(/\./gu, "¬")];
|
||||
|
||||
const value = v ? v["~gte"] : undefined;
|
||||
const value = v ? v["~gte"] : undefined;
|
||||
|
||||
// would break for value == 0, but a goal would never be
|
||||
// zero.
|
||||
if (!value) {
|
||||
throw new Error(`Invalid goal ${c.goalID} ${c.title}.`);
|
||||
}
|
||||
// would break for value == 0, but a goal would never be
|
||||
// zero.
|
||||
if (!value) {
|
||||
throw new Error(`Invalid goal ${c.goalID} ${c.title}.`);
|
||||
}
|
||||
|
||||
partialCriteria.value = value;
|
||||
partialCriteria.value = value;
|
||||
|
||||
// @ts-expect-error ts cant resolve this union properly
|
||||
const newGoalDoc: GoalDocument = {
|
||||
game: c.game,
|
||||
playtype: c.playtype,
|
||||
title: c.title,
|
||||
timeAdded: c.timeAdded,
|
||||
goalID: c.goalID, // maybe
|
||||
createdBy: c.createdBy,
|
||||
charts,
|
||||
criteria: partialCriteria as GoalDocument["criteria"],
|
||||
};
|
||||
// @ts-expect-error ts cant resolve this union properly
|
||||
const newGoalDoc: GoalDocument = {
|
||||
game: c.game,
|
||||
playtype: c.playtype,
|
||||
title: c.title,
|
||||
timeAdded: c.timeAdded,
|
||||
goalID: c.goalID, // maybe
|
||||
createdBy: c.createdBy,
|
||||
charts,
|
||||
criteria: partialCriteria as GoalDocument["criteria"],
|
||||
};
|
||||
|
||||
return newGoalDoc;
|
||||
return newGoalDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.goals, "goals", ConvertFn);
|
||||
await MigrateRecords(db.goals, "goals", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
process.exit(0);
|
||||
})();
|
||||
|
||||
@@ -1,49 +1,62 @@
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ICollection } from "monk";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import { oldKTDB } from "./old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const newDocuments: unknown[] = [];
|
||||
|
||||
export default async function MigrateRecords(
|
||||
existingCollection: ICollection,
|
||||
collectionName: string,
|
||||
HandlerFN: (c: unknown) => unknown
|
||||
existingCollection: ICollection,
|
||||
collectionName: string,
|
||||
HandlerFN: (c: unknown) => unknown,
|
||||
filter: Record<string, any> = {},
|
||||
force = false
|
||||
) {
|
||||
const existingRecords = await existingCollection.count({});
|
||||
logger.info(`Starting migration for ${collectionName}...`);
|
||||
const existingRecords = await existingCollection.count({});
|
||||
logger.info(`Starting migration for ${collectionName}...`);
|
||||
|
||||
if (existingRecords > 0) {
|
||||
logger.error(`${existingRecords} documents already exist in db, terminating.`);
|
||||
process.exit(1);
|
||||
}
|
||||
if (existingRecords > 0) {
|
||||
if (force) {
|
||||
logger.warn(
|
||||
`Forcing migration of documents - appending to ${existingRecords} existing records.`
|
||||
);
|
||||
} else {
|
||||
logger.error(`${existingRecords} documents already exist in db, terminating.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let i = 0;
|
||||
const bucketSize = 10_000;
|
||||
|
||||
await oldKTDB
|
||||
.get(collectionName)
|
||||
.find({})
|
||||
// @ts-expect-error it exists.
|
||||
.each(async (c, { pause, resume }) => {
|
||||
i++;
|
||||
pause();
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const docs = await oldKTDB.get(collectionName).find(filter, { limit: bucketSize, skip: i });
|
||||
|
||||
if (i % 10000 === 0) {
|
||||
logger.info(`Processed ${i} documents.`);
|
||||
}
|
||||
const newDoc = await HandlerFN(c);
|
||||
logger.info(`Migrating ${i} - ${i + docs.length} documents.`);
|
||||
|
||||
if (newDoc !== null) {
|
||||
newDocuments.push(newDoc);
|
||||
}
|
||||
if (docs.length === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
resume();
|
||||
});
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const newDocs = await Promise.all(docs.map(HandlerFN));
|
||||
|
||||
logger.info(`Inserting ${newDocuments.length} documents.`);
|
||||
await existingCollection.insert(newDocuments.flat());
|
||||
logger.info(`Done!`);
|
||||
i += bucketSize;
|
||||
|
||||
process.exit(0);
|
||||
const notNulls = newDocs.filter((e) => e !== null);
|
||||
|
||||
logger.info(`Success: ${notNulls.length}, Failures: ${newDocs.length - notNulls.length}.`);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await existingCollection.insert(notNulls);
|
||||
|
||||
logger.info(`Migrated ${docs.length} documents.`);
|
||||
|
||||
if (docs.length < bucketSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Done!`);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { Environment } from "lib/setup/config";
|
||||
import monk from "monk";
|
||||
|
||||
export const oldKTDB = monk(`${process.env.MONGO_BASE_URL}/kamaitachidb`);
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
logger.info(`Connecting to Old KTDB at ${Environment.mongoUrl}/kamaitachidb.`);
|
||||
|
||||
export const oldKTDB = monk(`${Environment.mongoUrl}/kamaitachidb`);
|
||||
|
||||
oldKTDB
|
||||
.then(() => {
|
||||
logger.info(`Done.`);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.crit(`Fatal in connecting to old KT DB.`, { err });
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import db from "../../../src/db/db";
|
||||
import { TierlistDataDocument } from "kamaitachi-common";
|
||||
import { CalculateTierlistDataID } from "../../../src/common/tierlist";
|
||||
import { oldKTDB } from "../old-db";
|
||||
import CreateLogCtx from "../../../src/common/logger";
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const TIERLIST_ID = "ed45403c2cd93922d8a45b3c5bc352a962a8f115";
|
||||
|
||||
function ConvertFn(c: any) {
|
||||
const tdd: TierlistDataDocument<"Individual Difference"> = {
|
||||
chartID: c.chartID,
|
||||
type: "score",
|
||||
key: null,
|
||||
tierlistDataID: CalculateTierlistDataID(c.chartID, "score", null, TIERLIST_ID),
|
||||
tierlistID: TIERLIST_ID,
|
||||
data: {
|
||||
flags: {
|
||||
"Individual Difference": c.idvDifference === "FALSE",
|
||||
},
|
||||
value: c.tiers["score-tier"],
|
||||
humanised: `☆${c.tiers["score-tier"]}`,
|
||||
},
|
||||
};
|
||||
|
||||
return tdd;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const exists = await db["tierlist-data"].count({
|
||||
tierlistID: TIERLIST_ID,
|
||||
});
|
||||
|
||||
if (exists > 0) {
|
||||
logger.error(`Cancelling migration request as ${exists} documents already exist.`);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
const docs = await oldKTDB.get("tierlistdata").find({
|
||||
tierlistID: TIERLIST_ID,
|
||||
});
|
||||
|
||||
const newDocs = await Promise.all(docs.map(ConvertFn));
|
||||
|
||||
await db["tierlist-data"].insert(newDocs);
|
||||
})();
|
||||
@@ -1,144 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { PrivateUserDocument, ScoreDocument } from "kamaitachi-common";
|
||||
import { grades, lamps, supportedGames, validPlaytypes } from "kamaitachi-common/js/config";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import { CreateScoreID } from "../../src/score-import/framework/common/score-utils";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
// HERE. WE. GO!
|
||||
|
||||
function ConditionalAssign(base: any, baseProp: string, other: any, otherProp: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(other, otherProp)) {
|
||||
base[baseProp] = other[otherProp];
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertFn(c: any): ScoreDocument | null {
|
||||
if (!supportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (["popn", "jubeat", "gitadora", "usc"].includes(c.game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (validPlaytypes[c.game].includes(c.playtype)) {
|
||||
logger.warn(`Ignored game pt ${c.game}, ${c.playtype}`);
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: c.songID,
|
||||
playtype: c.scoreData.playtype,
|
||||
chartID: c.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
calculatedData: c.calculatedData,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: grades[c.game].indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: lamps[c.game].indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
hitData: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
if (c.scoreMeta) {
|
||||
if (base.game === "iidx") {
|
||||
ConditionalAssign(base.scoreMeta, "random", c.scoreMeta, "optionsRandom");
|
||||
ConditionalAssign(base.scoreMeta, "gauge", c.scoreMeta, "optionsGauge");
|
||||
ConditionalAssign(base.scoreMeta, "assist", c.scoreMeta, "optionsAssist");
|
||||
ConditionalAssign(base.scoreMeta, "range", c.scoreMeta, "optionsRange");
|
||||
ConditionalAssign(base.scoreMeta, "pacemaker", c.scoreMeta, "pacemaker");
|
||||
ConditionalAssign(base.scoreMeta, "pacemakerName", c.scoreMeta, "pacemakerName");
|
||||
ConditionalAssign(base.scoreMeta, "pacemakerTarget", c.scoreMeta, "pacemakerTarget");
|
||||
|
||||
if (base.playtype === "DP" && !Array.isArray(base.scoreMeta.random)) {
|
||||
base.scoreMeta.random = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error asdf
|
||||
if (base.scoreMeta.range === "") {
|
||||
// @ts-expect-error asdf
|
||||
base.scoreMeta.range = "NONE";
|
||||
}
|
||||
} else if (base.game === "bms") {
|
||||
ConditionalAssign(base.scoreMeta, "random", c.scoreMeta, "optionsRandom");
|
||||
ConditionalAssign(base.scoreMeta, "inputDevice", c.scoreData.hitMeta, "inputDevice");
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.bp === -1 || Number.isNaN(base.scoreData.hitMeta.bp)) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
|
||||
if (base.game === "iidx") {
|
||||
// @ts-expect-error nope
|
||||
if (base.scoreData.hitMeta.gauge > 200) {
|
||||
// @ts-expect-error nope
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error nope
|
||||
if (base.scoreData.hitMeta.gaugeHistory) {
|
||||
// @ts-expect-error nope
|
||||
base.scoreData.hitMeta.gaugeHistory = base.scoreData.hitMeta.gaugeHistory.map((e) =>
|
||||
e > 200 ? null : e
|
||||
);
|
||||
}
|
||||
} else if (base.game === "bms") {
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.gauge === -1) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
delete base.scoreData.hitMeta.inputDevice;
|
||||
} else if (base.game === "sdvx" || base.game === "usc") {
|
||||
ConditionalAssign(base.scoreData.hitData, "miss", base.scoreData.hitData, "error");
|
||||
|
||||
//@ts-expect-error yea
|
||||
delete base.scoreData.hitData.error;
|
||||
} else if (base.game === "chunithm") {
|
||||
if (base.scoreData.percent === 101) {
|
||||
base.scoreData.grade === "SSS";
|
||||
}
|
||||
}
|
||||
|
||||
if (base.service === "mm") {
|
||||
base.service = "maimagic";
|
||||
}
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,153 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGamePTConfig, ScoreDocument, StaticConfig } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
function ConditionalAssign(base: any, baseProp: string, other: any, otherProp: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(other, otherProp)) {
|
||||
base[baseProp] = other[otherProp];
|
||||
}
|
||||
}
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
if (!StaticConfig.allSupportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
const game = c.game;
|
||||
const playtype = c.scoreData.playtype;
|
||||
|
||||
const oldChartDoc = await oldKTDB.get("charts-bms").findOne({
|
||||
chartID: c.chartID,
|
||||
});
|
||||
|
||||
if (!oldChartDoc) {
|
||||
logger.warn(`Cannot find oldChartDoc for ${c.chartID}`);
|
||||
}
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
"data.hashMD5": oldChartDoc.internals.hash,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
const oldSong = await oldKTDB.get("songs-bms").findOne({ id: c.songID });
|
||||
logger.warn(`Cannot find ChartDoc for ${oldSong.title} ${oldChartDoc.level} ${playtype}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, chartDoc.playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`Cannot find GPTConfig for ${game} ${chartDoc.playtype}?`);
|
||||
|
||||
throw new Error(`Screwed`);
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype: chartDoc.playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: {
|
||||
fast: c.scoreData.hitMeta.fast,
|
||||
slow: c.scoreData.hitMeta.slow,
|
||||
maxCombo: c.scoreData.hitMeta.maxCombo,
|
||||
bp: c.scoreData.hitMeta.bp,
|
||||
gauge: c.scoreData.hitMeta.gauge,
|
||||
epg: c.scoreData.hitMeta.epg,
|
||||
egr: c.scoreData.hitMeta.egr,
|
||||
egd: c.scoreData.hitMeta.egd,
|
||||
ebd: c.scoreData.hitMeta.ebd,
|
||||
epr: c.scoreData.hitMeta.epr,
|
||||
lpg: c.scoreData.hitMeta.lpg,
|
||||
lgr: c.scoreData.hitMeta.lgr,
|
||||
lgd: c.scoreData.hitMeta.lgd,
|
||||
lbd: c.scoreData.hitMeta.lbd,
|
||||
lpr: c.scoreData.hitMeta.lpr,
|
||||
},
|
||||
},
|
||||
scoreMeta: {
|
||||
random: c.scoreMeta?.optionsRandom ?? null,
|
||||
inputDevice: c.scoreMeta?.inputDevice ?? null,
|
||||
},
|
||||
};
|
||||
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.bp === -1 || Number.isNaN(base.scoreData.hitMeta.bp)) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
if (Number(base.scoreData.hitMeta.bp) < 0) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.gauge === -1) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
delete base.scoreData.hitMeta.inputDevice;
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateBMSScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "bms" }, true);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await MigrateBMSScores();
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGameConfig, GetGamePTConfig, ScoreDocument } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
const game = c.game;
|
||||
const playtype = c.scoreData.playtype;
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
if (!gameConfig.validPlaytypes.includes(playtype)) {
|
||||
logger.warn(`Ignored game pt ${game}, ${playtype}`);
|
||||
throw new Error(`Ignored game pt ${game}, ${playtype}`);
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`No gptconfig for ${game} ${playtype}?`);
|
||||
throw new Error(`No gptconfig for ${game} ${playtype}?`);
|
||||
}
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
songID: c.songID,
|
||||
difficulty: c.scoreData.difficulty,
|
||||
playtype,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.scoreData.difficulty} ${playtype} `);
|
||||
return null;
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument<"chunithm:Single">, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateCHUNITHMScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "chunithm" }, true);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGamePTConfig, ScoreDocument, StaticConfig } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
if (!StaticConfig.allSupportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
const game = c.game;
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
chartID: c.chartID,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.chartID} ${c.scoreData.difficulty}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, chartDoc.playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`Cannot find GPTConfig for ${game} ${chartDoc.playtype}?`);
|
||||
|
||||
throw new Error(`Screwed`);
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument<"ddr:SP" | "ddr:DP">, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype: chartDoc.playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
try {
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
} catch (err) {
|
||||
// whoops
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateDDRScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "ddr" }, true);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await MigrateDDRScores();
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGameConfig, GetGamePTConfig, ScoreDocument } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
const game = c.game;
|
||||
const playtype = c.scoreData.playtype;
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
if (!gameConfig.validPlaytypes.includes(playtype)) {
|
||||
logger.warn(`Ignored game pt ${game}, ${playtype}`);
|
||||
throw new Error(`Ignored game pt ${game}, ${playtype}`);
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`No gptconfig for ${game} ${playtype}?`);
|
||||
throw new Error(`No gptconfig for ${game} ${playtype}?`);
|
||||
}
|
||||
|
||||
if (c.songID === 1819) {
|
||||
c.songID = 1121; // praludium has two songs in kt1 for some reason (tricoro 6)
|
||||
// this overrides that
|
||||
}
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
songID: c.songID,
|
||||
difficulty: c.scoreData.difficulty,
|
||||
playtype,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.scoreData.difficulty} ${playtype} `);
|
||||
return null;
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument<"iidx:SP" | "iidx:DP">, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {
|
||||
random: c.scoreMeta?.optionsRandom ?? null,
|
||||
gauge: c.scoreMeta?.optionsGauge ?? null,
|
||||
assist: c.scoreMeta?.optionsAssist ?? null,
|
||||
range: c.scoreMeta?.optionsRange ?? null,
|
||||
},
|
||||
};
|
||||
|
||||
if (c.scoreMeta) {
|
||||
if (base.playtype === "DP" && !Array.isArray(base.scoreMeta.random)) {
|
||||
base.scoreMeta.random = null;
|
||||
}
|
||||
|
||||
if (c.scoreData.hitMeta.gauge < 0) {
|
||||
c.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
if (!Array.isArray(c.scoreData.hitMeta.gaugeHistory)) {
|
||||
c.scoreData.hitMeta.gaugeHistory = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error asdf
|
||||
if (base.scoreMeta.range === "") {
|
||||
base.scoreMeta.range = "NONE";
|
||||
}
|
||||
|
||||
if (c.scoreMeta.deadMeasure || c.scoreMeta.deadNote) {
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (base.scoreData.hitMeta.bp === -1 || Number.isNaN(base.scoreData.hitMeta.bp)) {
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
|
||||
if ((base.scoreData.hitMeta.gauge ?? 0) > 200) {
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
if (base.scoreData.hitMeta.gaugeHistory) {
|
||||
base.scoreData.hitMeta.gaugeHistory = base.scoreData.hitMeta.gaugeHistory.map((e) =>
|
||||
(e ?? 0) > 200 ? null : e
|
||||
);
|
||||
}
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateIIDXScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "iidx" }, true);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await MigrateIIDXScores();
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { MigrateBMSScores } from "./bms";
|
||||
import { MigrateCHUNITHMScores } from "./chunithm";
|
||||
import { MigrateIIDXScores } from "./iidx";
|
||||
import { MigrateMusecaScores } from "./museca";
|
||||
import { MigrateSDVXScores } from "./sdvx";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
(async () => {
|
||||
logger.info(`Migrating IIDX Scores.`);
|
||||
|
||||
await MigrateIIDXScores();
|
||||
|
||||
logger.info(`Migrating BMS Scores.`);
|
||||
await MigrateBMSScores();
|
||||
|
||||
logger.info(`Migrating MUSECA Scores.`);
|
||||
|
||||
await MigrateMusecaScores();
|
||||
|
||||
logger.info(`Migrating SDVX Scores.`);
|
||||
|
||||
await MigrateSDVXScores();
|
||||
|
||||
logger.info(`Migrating CHUNITHM Scores.`);
|
||||
|
||||
await MigrateCHUNITHMScores();
|
||||
|
||||
logger.info("Done.");
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGamePTConfig, ScoreDocument, StaticConfig } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
if (!StaticConfig.allSupportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
const game = c.game;
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
chartID: c.chartID,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.chartID} ${c.scoreData.difficulty}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, chartDoc.playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`Cannot find GPTConfig for ${game} ${chartDoc.playtype}?`);
|
||||
|
||||
throw new Error(`Screwed`);
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument<"museca:Single">, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype: chartDoc.playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
try {
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
} catch (err) {
|
||||
// whoops
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateMusecaScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "museca" }, true);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await MigrateMusecaScores();
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGameConfig, GetGamePTConfig, ScoreDocument, StaticConfig } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
function ConditionalAssign(base: any, baseProp: string, other: any, otherProp: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(other, otherProp)) {
|
||||
base[baseProp] = other[otherProp];
|
||||
}
|
||||
}
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
if (!StaticConfig.allSupportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
const game = c.game;
|
||||
const playtype = c.scoreData.playtype;
|
||||
|
||||
if (["popn", "jubeat", "gitadora", "usc"].includes(c.game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gameConfig = GetGameConfig(game);
|
||||
|
||||
if (!gameConfig.validPlaytypes.includes(playtype)) {
|
||||
logger.warn(`Ignored game pt ${game}, ${playtype}`);
|
||||
throw new Error(`Ignored game pt ${game}, ${playtype}`);
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`No gptconfig for ${game} ${playtype}?`);
|
||||
throw new Error(`No gptconfig for ${game} ${playtype}?`);
|
||||
}
|
||||
|
||||
if (game === "iidx" && c.songID === 1819) {
|
||||
c.songID = 1121; // praludium has two songs in kt1 for some reason (tricoro 6)
|
||||
// this overrides that
|
||||
}
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
songID: c.songID,
|
||||
difficulty: c.scoreData.difficulty,
|
||||
playtype,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.scoreData.difficulty} ${playtype} `);
|
||||
return null;
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: c.scoreData.hitMeta,
|
||||
},
|
||||
scoreMeta: {},
|
||||
};
|
||||
|
||||
if (c.scoreMeta) {
|
||||
if (base.game === "iidx") {
|
||||
ConditionalAssign(base.scoreMeta, "random", c.scoreMeta, "optionsRandom");
|
||||
ConditionalAssign(base.scoreMeta, "gauge", c.scoreMeta, "optionsGauge");
|
||||
ConditionalAssign(base.scoreMeta, "assist", c.scoreMeta, "optionsAssist");
|
||||
ConditionalAssign(base.scoreMeta, "range", c.scoreMeta, "optionsRange");
|
||||
ConditionalAssign(base.scoreMeta, "pacemaker", c.scoreMeta, "pacemaker");
|
||||
ConditionalAssign(base.scoreMeta, "pacemakerName", c.scoreMeta, "pacemakerName");
|
||||
ConditionalAssign(base.scoreMeta, "pacemakerTarget", c.scoreMeta, "pacemakerTarget");
|
||||
|
||||
// @ts-expect-error this game is iidx
|
||||
if (base.playtype === "DP" && !Array.isArray(base.scoreMeta.random)) {
|
||||
// @ts-expect-error this game is iidx
|
||||
base.scoreMeta.random = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error asdf
|
||||
if (base.scoreMeta.range === "") {
|
||||
// @ts-expect-error asdf
|
||||
base.scoreMeta.range = "NONE";
|
||||
}
|
||||
} else if (base.game === "bms") {
|
||||
ConditionalAssign(base.scoreMeta, "random", c.scoreMeta, "optionsRandom");
|
||||
ConditionalAssign(base.scoreMeta, "inputDevice", c.scoreData.hitMeta, "inputDevice");
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.bp === -1 || Number.isNaN(base.scoreData.hitMeta.bp)) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.bp = null;
|
||||
}
|
||||
|
||||
if (base.game === "iidx") {
|
||||
// @ts-expect-error nope
|
||||
if (base.scoreData.hitMeta.gauge > 200) {
|
||||
// @ts-expect-error nope
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error nope
|
||||
if (base.scoreData.hitMeta.gaugeHistory) {
|
||||
// @ts-expect-error nope
|
||||
base.scoreData.hitMeta.gaugeHistory = base.scoreData.hitMeta.gaugeHistory.map((e) =>
|
||||
e > 200 ? null : e
|
||||
);
|
||||
}
|
||||
} else if (base.game === "bms") {
|
||||
// @ts-expect-error shut
|
||||
if (base.scoreData.hitMeta.gauge === -1) {
|
||||
// @ts-expect-error shut
|
||||
base.scoreData.hitMeta.gauge = null;
|
||||
}
|
||||
|
||||
// @ts-expect-error shut
|
||||
delete base.scoreData.hitMeta.inputDevice;
|
||||
} else if (base.game === "sdvx" || base.game === "usc") {
|
||||
ConditionalAssign(base.scoreData.judgements, "miss", base.scoreData.judgements, "error");
|
||||
|
||||
//@ts-expect-error yea
|
||||
delete base.scoreData.judgements.error;
|
||||
} else if (base.game === "chunithm") {
|
||||
if (base.scoreData.percent === 101) {
|
||||
base.scoreData.grade = "SSS";
|
||||
}
|
||||
}
|
||||
|
||||
if (base.service === "mm") {
|
||||
base.service = "maimagic";
|
||||
}
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,114 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateScoreID } from "lib/score-import/framework/score-importing/score-id";
|
||||
import { GetGamePTConfig, ScoreDocument, StaticConfig } from "tachi-common";
|
||||
import MigrateRecords from "../migrate";
|
||||
import { oldKTDB } from "../old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<ScoreDocument | null> {
|
||||
if (!StaticConfig.allSupportedGames.includes(c.game)) {
|
||||
logger.warn(`Ignored game ${c.game}`);
|
||||
return null;
|
||||
}
|
||||
const game = c.game;
|
||||
|
||||
// @ts-expect-error implicit any is expected
|
||||
const chartDoc = await db.charts[game].findOne({
|
||||
chartID: c.chartID,
|
||||
});
|
||||
|
||||
if (!chartDoc) {
|
||||
logger.warn(`Cannot find ChartDoc for ${c.songID} ${c.chartID} ${c.scoreData.difficulty}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const gptConfig = GetGamePTConfig(game, chartDoc.playtype);
|
||||
|
||||
if (!gptConfig) {
|
||||
logger.error(`Cannot find GPTConfig for ${game} ${chartDoc.playtype}?`);
|
||||
|
||||
throw new Error(`Screwed`);
|
||||
}
|
||||
|
||||
const base: Omit<ScoreDocument<"sdvx:Single">, "scoreID"> = {
|
||||
userID: c.userID,
|
||||
songID: chartDoc.songID,
|
||||
playtype: chartDoc.playtype,
|
||||
chartID: chartDoc.chartID,
|
||||
game: c.game,
|
||||
timeAdded: c.timeAdded,
|
||||
timeAchieved: Number.isNaN(c.timeAchieved) ? null : c.timeAchieved,
|
||||
comment: c.comment ?? null,
|
||||
highlight: c.highlight ?? false,
|
||||
service: c.service,
|
||||
importType: null,
|
||||
// we'll just recalc calculated data instead of trying to update
|
||||
calculatedData: {},
|
||||
isPrimary: chartDoc.isPrimary,
|
||||
scoreData: {
|
||||
esd: c.scoreData.esd ?? null,
|
||||
grade: c.scoreData.grade,
|
||||
gradeIndex: gptConfig.grades.indexOf(c.scoreData.grade),
|
||||
lamp: c.scoreData.lamp,
|
||||
lampIndex: gptConfig.lamps.indexOf(c.scoreData.lamp),
|
||||
percent: c.scoreData.percent,
|
||||
score: c.scoreData.score,
|
||||
judgements: c.scoreData.hitData,
|
||||
hitMeta: {
|
||||
fast: c.scoreData.hitMeta.fast,
|
||||
slow: c.scoreData.hitMeta.slow,
|
||||
maxCombo: c.scoreData.hitMeta.maxCombo,
|
||||
gauge: c.scoreData.hitMeta.gauge,
|
||||
},
|
||||
},
|
||||
scoreMeta: {
|
||||
inSkillAnalyser: null,
|
||||
},
|
||||
};
|
||||
|
||||
if (c.scoreData.hitData.error !== undefined) {
|
||||
base.scoreData.judgements.miss = c.scoreData.hitData.error;
|
||||
// @ts-expect-error yeah we know
|
||||
delete base.scoreData.judgements.error;
|
||||
}
|
||||
|
||||
const scoreID = CreateScoreID(base.userID, base, base.chartID);
|
||||
|
||||
const exists = await db.scores.findOne({ scoreID });
|
||||
|
||||
if (exists) {
|
||||
logger.warn(`Skipping duplicate score ${base.chartID} ${base.userID}.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const score: ScoreDocument = {
|
||||
...base,
|
||||
scoreID,
|
||||
};
|
||||
|
||||
try {
|
||||
await oldKTDB.get("score-id-lookup").insert({
|
||||
old: c.scoreID,
|
||||
new: scoreID,
|
||||
});
|
||||
} catch (err) {
|
||||
// lol
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
export async function MigrateSDVXScores() {
|
||||
await MigrateRecords(db.scores, "scores", ConvertFn, { game: "sdvx" }, true);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
await MigrateSDVXScores();
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { CreateSessionID } from "lib/score-import/framework/sessions/sessions";
|
||||
import { GetGameConfig, integer, SessionDocument, SessionScoreInfo } from "tachi-common";
|
||||
import { allSupportedGames } from "tachi-common/js/config/static-config";
|
||||
import MigrateRecords from "./migrate";
|
||||
import { oldKTDB } from "./old-db";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
interface LegacySessionScoreFormat {
|
||||
pbInfo: {
|
||||
isGeneralPB: boolean;
|
||||
isGradePB: boolean;
|
||||
isLampPB: boolean;
|
||||
isNewScore: boolean;
|
||||
lampDelta: integer;
|
||||
percentDelta: number;
|
||||
gradeDelta: integer;
|
||||
scoreDelta: number;
|
||||
};
|
||||
scoreID: string;
|
||||
}
|
||||
|
||||
async function ConvertScoreToScoreInfo(
|
||||
score: LegacySessionScoreFormat
|
||||
): Promise<SessionScoreInfo | null> {
|
||||
const scoreIDs = await oldKTDB.get("score-id-lookup").findOne({
|
||||
old: score.scoreID,
|
||||
});
|
||||
|
||||
if (!scoreIDs) {
|
||||
logger.verbose(`No score for ${score.scoreID}? Skipping.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
logger.verbose(`Found score for ${score.scoreID}.`);
|
||||
|
||||
if (score.pbInfo.isNewScore) {
|
||||
return {
|
||||
isNewScore: true,
|
||||
scoreID: scoreIDs.new,
|
||||
};
|
||||
}
|
||||
|
||||
if (score.pbInfo.scoreDelta === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
scoreID: scoreIDs.new,
|
||||
isNewScore: false,
|
||||
gradeDelta: score.pbInfo.gradeDelta,
|
||||
lampDelta: score.pbInfo.lampDelta,
|
||||
percentDelta: score.pbInfo.percentDelta,
|
||||
scoreDelta: score.pbInfo.scoreDelta,
|
||||
};
|
||||
}
|
||||
|
||||
async function ConvertFn(c: any): Promise<SessionDocument | null> {
|
||||
if (!allSupportedGames.includes(c.game)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gameConfig = GetGameConfig(c.game);
|
||||
|
||||
if (!gameConfig.validPlaytypes.includes(c.playtype)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scores = [];
|
||||
|
||||
for (const sc of c.scores) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const conv = await ConvertScoreToScoreInfo(sc);
|
||||
|
||||
if (conv === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scores.push(conv);
|
||||
}
|
||||
|
||||
const sessionDocument: SessionDocument = {
|
||||
name: c.name,
|
||||
desc: c.desc,
|
||||
timeStarted: c.timeStarted,
|
||||
timeEnded: c.timeEnded,
|
||||
timeInserted: c.timestamp,
|
||||
game: c.game,
|
||||
playtype: c.playtype,
|
||||
highlight: !!c.highlight,
|
||||
sessionID: CreateSessionID(),
|
||||
views: 0,
|
||||
userID: c.userID,
|
||||
calculatedData: {},
|
||||
scoreInfo: scores,
|
||||
importType: null,
|
||||
};
|
||||
|
||||
return sessionDocument;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.sessions, "sessions", ConvertFn, {}, true);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"chunithm"> {
|
||||
const newSongDoc: SongDocument<"chunithm"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": c["alt-titles"].filter((e: string) => e !== c.title),
|
||||
"search-titles": c["search-titles"]
|
||||
.map((e: string) => e.toString())
|
||||
.filter((e: string) => e !== c.title),
|
||||
data: {
|
||||
genre: c.genre,
|
||||
},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.chunithm, "songs-chunithm", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"ddr"> {
|
||||
const newSongDoc: SongDocument<"ddr"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": c["alt-titles"] ? c["alt-titles"].filter((e: string) => e !== c.title) : [],
|
||||
"search-titles": c["search-title"]
|
||||
.filter((e: string) => !!e)
|
||||
.map((e: string) => e.toString())
|
||||
.filter((e: string) => e !== c.title),
|
||||
data: {},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.ddr, "songs-ddr", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,27 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"gitadora"> {
|
||||
const newSongDoc: SongDocument<"gitadora"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": c["alt-titles"] ? c["alt-titles"].filter((e: string) => e !== c.title) : [],
|
||||
"search-titles": c["search-titles"]
|
||||
.filter((e: string) => !!e)
|
||||
.map((e: string) => e.toString())
|
||||
.filter((e: string) => e !== c.title),
|
||||
data: {},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.gitadora, "songs-gitadora", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import { gameOrders } from "kamaitachi-common/js/config";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"iidx"> {
|
||||
const newSongDoc: SongDocument<"iidx"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": c["alt-titles"].filter((e: string) => e !== c.title),
|
||||
"search-titles": c["search-titles"]
|
||||
.map((e: string) => e.toString())
|
||||
.filter((e: string) => e !== c.title),
|
||||
data: {
|
||||
genre: c.genre,
|
||||
},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.iidx, "songs-iidx", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,27 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"museca"> {
|
||||
const newSongDoc: SongDocument<"museca"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": [],
|
||||
"search-titles": [],
|
||||
data: {
|
||||
titleJP: c.title_jp,
|
||||
artistJP: c.artist_jp,
|
||||
},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.museca, "songs-museca", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,28 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { SongDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): SongDocument<"sdvx"> {
|
||||
const newSongDoc: SongDocument<"sdvx"> = {
|
||||
title: c.title,
|
||||
artist: c.artist,
|
||||
id: c.id,
|
||||
firstVersion: c.firstAppearance,
|
||||
"alt-titles": c["alt-titles"].filter((e: string) => e !== c.title),
|
||||
"search-titles": c["search-titles"]
|
||||
.map((e: string) => e.toString())
|
||||
.filter((e: string) => e !== c.title),
|
||||
data: {
|
||||
uscEquiv: null,
|
||||
},
|
||||
};
|
||||
|
||||
return newSongDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.songs.sdvx, "songs-sdvx", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,43 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { UpdateUsersGamePlaytypeStats } from "lib/score-import/framework/user-game-stats/update-ugs";
|
||||
import { FormatUserDoc } from "utils/user";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
export async function CreateGameProfiles() {
|
||||
const users = await db.users.find({});
|
||||
|
||||
for (const user of users) {
|
||||
const gpts = await db.scores.aggregate([
|
||||
{
|
||||
$match: {
|
||||
userID: user.id,
|
||||
},
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: {
|
||||
game: "$game",
|
||||
playtype: "$playtype",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
logger.info(`Found ${gpts.length} GPTs for ${FormatUserDoc(user)}`);
|
||||
|
||||
for (const gpt of gpts) {
|
||||
const { game, playtype } = gpt._id;
|
||||
logger.info(`Updating ${FormatUserDoc(user)}'s ${game} ${playtype} stats.`);
|
||||
await UpdateUsersGamePlaytypeStats(game, playtype, user.id, null, logger);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Done.`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
CreateGameProfiles().then(() => process.exit(0));
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function DedupeScoreIDs() {
|
||||
const dups = await db.scores.aggregate(
|
||||
[
|
||||
{
|
||||
$group: {
|
||||
_id: "$scoreID",
|
||||
dups: { $addToSet: "$_id" },
|
||||
count: { $sum: 1 },
|
||||
},
|
||||
},
|
||||
{
|
||||
$match: {
|
||||
count: { $gt: 1 },
|
||||
},
|
||||
},
|
||||
],
|
||||
{ allowDiskUse: true }
|
||||
);
|
||||
|
||||
logger.info(`Found ${dups.length} dups.`);
|
||||
|
||||
for (const dup of dups) {
|
||||
dup.dups.shift();
|
||||
await db.scores.remove({ _id: { $in: dup.dups } });
|
||||
}
|
||||
|
||||
logger.info(`Done.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
DedupeScoreIDs();
|
||||
@@ -0,0 +1,5 @@
|
||||
import { RecalcAllScores } from "utils/calculations/recalc-scores";
|
||||
|
||||
if (require.main === module) {
|
||||
RecalcAllScores();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { RecalcSessions } from "utils/calculations/recalc-sessions";
|
||||
|
||||
if (require.main === module) {
|
||||
RecalcSessions().then(() => process.exit(0));
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { RecalcAllScores, UpdateAllPBs } from "utils/calculations/recalc-scores";
|
||||
import { RecalcSessions } from "utils/calculations/recalc-sessions";
|
||||
import { CreateGameProfiles } from "./create-game-profiles";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
(async () => {
|
||||
await RecalcAllScores();
|
||||
await UpdateAllPBs();
|
||||
await CreateGameProfiles();
|
||||
await RecalcSessions();
|
||||
|
||||
logger.info(`Completely done!`);
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,6 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { UpdateAllPBs } from "utils/calculations/recalc-scores";
|
||||
|
||||
if (require.main === module) {
|
||||
UpdateAllPBs();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { TierlistParent, Game } from "kamaitachi-common";
|
||||
import { gameHuman, validPlaytypes } from "kamaitachi-common/js/config";
|
||||
import db from "../../src/db/db";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): TierlistParent {
|
||||
const tierlistParent: TierlistParent = {
|
||||
createdAt: Date.now(),
|
||||
tierlistID: c.tierlistID,
|
||||
createdBy: 1,
|
||||
description: `The official Kamaitachi Tierlist for ${gameHuman[c.game as Game]}${
|
||||
validPlaytypes[c.game as Game].length > 1 ? ` (${c.playtype})` : ""
|
||||
}.`,
|
||||
game: c.game,
|
||||
playtype: c.playtype,
|
||||
isDefault: true,
|
||||
name: c.title,
|
||||
lastUpdated: Date.now(),
|
||||
permissions: {
|
||||
anyPlayer: {
|
||||
edit: 0,
|
||||
submit: 1,
|
||||
vote: 1,
|
||||
},
|
||||
},
|
||||
config: {
|
||||
autoHumanise: false,
|
||||
flags: ["Individual Difference"],
|
||||
requireState: "clear",
|
||||
},
|
||||
};
|
||||
|
||||
return tierlistParent;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.tierlists, "tierlist", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,26 +1,26 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { UserGoalDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { UserGoalDocument } from "tachi-common";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function ConvertFn(c: any): Promise<UserGoalDocument | null> {
|
||||
// const newUG: UserGoalDocument = {};
|
||||
// const newUG: UserGoalDocument = {};
|
||||
|
||||
let relatedGoal = await db.goals.findOne({ goalID: c.goalID });
|
||||
const relatedGoal = await db.goals.findOne({ goalID: c.goalID });
|
||||
|
||||
if (!relatedGoal) {
|
||||
return null;
|
||||
}
|
||||
if (!relatedGoal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return c;
|
||||
return c;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db["user-goals"], "user-goals", ConvertFn);
|
||||
await MigrateRecords(db["user-goals"], "user-goals", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
process.exit(0);
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import db from "external/mongo/db";
|
||||
import { PrivateUserInfoDocument } from "tachi-common";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): PrivateUserInfoDocument {
|
||||
const privateUserInfoDoc: PrivateUserInfoDocument = {
|
||||
userID: c.id,
|
||||
password: c.password,
|
||||
email: c.email,
|
||||
};
|
||||
|
||||
return privateUserInfoDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db["user-private-information"], "users", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,15 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import db from "external/mongo/db";
|
||||
import { DEFAULT_USER_SETTINGS } from "server/router/api/v1/auth/auth";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any) {
|
||||
return { userID: c.id, preferences: DEFAULT_USER_SETTINGS };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db["user-settings"], "users", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,40 +1,36 @@
|
||||
import { PrivateUserDocument } from "kamaitachi-common";
|
||||
import db from "../../src/db/db";
|
||||
import db from "external/mongo/db";
|
||||
import { PublicUserDocument, UserAuthLevels } from "tachi-common";
|
||||
import MigrateRecords from "./migrate";
|
||||
|
||||
function ConvertFn(c: any): PrivateUserDocument {
|
||||
const newUserDoc: PrivateUserDocument = {
|
||||
username: c.username,
|
||||
usernameLowercase: c.username.toLowerCase(),
|
||||
about: c.about,
|
||||
clan: c.clan,
|
||||
customBanner: c.custombanner,
|
||||
customPfp: c.custompfp,
|
||||
email: c.email,
|
||||
friends: c.friends,
|
||||
id: c.id,
|
||||
lastSeen: c.lastSeen,
|
||||
password: c.password,
|
||||
permissions: {},
|
||||
settings: {
|
||||
invisible: !!c.settings.invisible,
|
||||
nsfwSplashes: !!c.settings.nsfwsplashes,
|
||||
},
|
||||
socialMedia: {
|
||||
discord: c.socialmedia.discord || null,
|
||||
twitter: c.socialmedia.twitter || null,
|
||||
youtube: c.socialmedia.youtube || null,
|
||||
twitch: c.socialmedia.twitch || null,
|
||||
github: c.socialmedia.github || null,
|
||||
steam: c.socialmedia.steam || null,
|
||||
},
|
||||
};
|
||||
function ConvertFn(c: any): PublicUserDocument {
|
||||
const newUserDoc: PublicUserDocument = {
|
||||
username: c.username,
|
||||
usernameLowercase: c.username.toLowerCase(),
|
||||
about: c.about,
|
||||
clan: c.clan,
|
||||
customBannerLocation: null,
|
||||
customPfpLocation: null,
|
||||
id: c.id,
|
||||
lastSeen: c.lastSeen,
|
||||
socialMedia: {
|
||||
discord: c.socialmedia.discord || null,
|
||||
twitter: c.socialmedia.twitter || null,
|
||||
youtube: c.socialmedia.youtube || null,
|
||||
twitch: c.socialmedia.twitch || null,
|
||||
github: c.socialmedia.github || null,
|
||||
steam: c.socialmedia.steam || null,
|
||||
},
|
||||
joinDate: 0,
|
||||
authLevel: UserAuthLevels.USER,
|
||||
status: null,
|
||||
badges: ["beta"],
|
||||
};
|
||||
|
||||
return newUserDoc;
|
||||
return newUserDoc;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await MigrateRecords(db.users, "users", ConvertFn);
|
||||
await MigrateRecords(db.users, "users", ConvertFn);
|
||||
|
||||
process.exit(0);
|
||||
process.exit(0);
|
||||
})();
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import fetch from "../../src/common/fetch";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// note - this program was never run due to WSL2 errors
|
||||
// causing network requests to screech to a halt.
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program.option("-u, --url <URL>", "The base URL to sync from.");
|
||||
program.option("-b, --bearer <token>", "The authorization bearer token to use.");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
async function GetIIDXSongs() {
|
||||
const allSongs = [];
|
||||
|
||||
let url = `https://${options.url}/api/v1/iidx/27/music/`;
|
||||
|
||||
let moreData = true;
|
||||
while (moreData) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const songs = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${options.bearer}`,
|
||||
},
|
||||
}).then((r) => r.json());
|
||||
|
||||
if (songs._links._next) {
|
||||
url = songs._links._next;
|
||||
} else {
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
allSongs.push(...songs._items);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, "./songs.json"), JSON.stringify(allSongs));
|
||||
}
|
||||
|
||||
async function GetCharts() {
|
||||
const songs = JSON.parse(fs.readFileSync(path.join(__dirname, "./songs.json"), "utf-8"));
|
||||
|
||||
logger.info("Parsed songs.json");
|
||||
|
||||
const allCharts: any[] = [];
|
||||
const failed: any[] = [];
|
||||
|
||||
async function DoStuff(song: any) {
|
||||
try {
|
||||
logger.info(
|
||||
`Starting Request https://${options.url}/api/v1/iidx/27/charts/?music_id=${song._id}&omnimix=true.`
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const charts = await fetch(
|
||||
`https://${options.url}/api/v1/iidx/27/charts/?music_id=${song._id}&omnimix=true`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${options.bearer}`,
|
||||
},
|
||||
}
|
||||
).then((r) => r.json());
|
||||
|
||||
logger.info(`Parsed: ${charts._related.music[0].title}`);
|
||||
|
||||
allCharts.push(charts);
|
||||
} catch (err) {
|
||||
logger.error(song);
|
||||
logger.error(err);
|
||||
failed.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
const promises = songs.map((s: any) => DoStuff(s));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, "./charts.json"), JSON.stringify(allCharts));
|
||||
|
||||
logger.info(failed);
|
||||
|
||||
logger.info("done?");
|
||||
}
|
||||
|
||||
GetCharts();
|
||||
@@ -1,86 +0,0 @@
|
||||
import fs from "fs";
|
||||
import { ICollection } from "monk";
|
||||
import path from "path";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import { ChartDocument } from "kamaitachi-common";
|
||||
import { FindSongOnTitle } from "../../src/common/database-lookup/song";
|
||||
import { FindChartWithPTDFVersion } from "../../src/common/database-lookup/chart";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
async function MergeIDs() {
|
||||
const charts = JSON.parse(fs.readFileSync(path.join(__dirname, "./charts.json"), "utf-8"));
|
||||
|
||||
for (const chartData of charts) {
|
||||
const songTitle = chartData._related.music[0].title;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const song = await FindSongOnTitle("iidx", songTitle);
|
||||
|
||||
if (!song) {
|
||||
logger.error(`Could not resolve ${songTitle}?`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const chart of chartData._items) {
|
||||
const playtype: "SP" | "DP" = chart.play_style === "DOUBLE" ? "DP" : "SP";
|
||||
let difficulty = chart.difficulty === "BLACK" ? "LEGGENDARIA" : chart.difficulty;
|
||||
const version = "27-omni";
|
||||
|
||||
// yeah this fails for wacky playtype mismatches -- i don't care.
|
||||
if (
|
||||
["ミッドナイト堕天使", "Y&Co. is dead or alive", "State Of The Art"].includes(
|
||||
songTitle
|
||||
) &&
|
||||
difficulty === "LEGGENDARIA"
|
||||
) {
|
||||
difficulty = "ANOTHER";
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const ktchiChart = await FindChartWithPTDFVersion(
|
||||
"iidx",
|
||||
song.id,
|
||||
playtype,
|
||||
difficulty,
|
||||
version
|
||||
);
|
||||
|
||||
if (!ktchiChart) {
|
||||
logger.error(
|
||||
`Could not find chart ${songTitle} ${playtype} ${difficulty} ${version}.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await ((db.charts.iidx as unknown) as ICollection<
|
||||
ChartDocument<"iidx:SP" | "iidx:DP">
|
||||
>).update(
|
||||
{
|
||||
_id: ktchiChart._id,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
"data.arcChartID": chart._id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const r = await db.charts.iidx.update(
|
||||
{
|
||||
"data.arcChartID": { $exists: false },
|
||||
},
|
||||
{ $set: { "data.arcChartID": null } },
|
||||
{
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
|
||||
logger.info("done.", { r });
|
||||
}
|
||||
|
||||
MergeIDs();
|
||||
@@ -0,0 +1,213 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Command } from "commander";
|
||||
import db from "external/mongo/db";
|
||||
import fs from "fs";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { ServerConfig } from "lib/setup/config";
|
||||
import path from "path";
|
||||
import fetch from "utils/fetch";
|
||||
import { FindChartWithPTDFVersion, FindSDVXChartOnDFVersion } from "utils/queries/charts";
|
||||
import { FindSongOnTitle } from "utils/queries/songs";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program.option("-u, --url <URL>", "The base URL to sync from.");
|
||||
program.option("-b, --bearer <token>", "The authorization bearer token to use.");
|
||||
program.option("-g, --game <game>", "Should be exactly iidx or sdvx.");
|
||||
program.option("-c, --cache", "Use a local cached JSON file, instead of requesting from the api.");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
if (!options.game) {
|
||||
throw new Error(`Expected -g or --game parameter.`);
|
||||
}
|
||||
|
||||
if (!["iidx", "sdvx"].includes(options.game)) {
|
||||
throw new Error(`Expected provided game to be either iidx or sdvx.`);
|
||||
}
|
||||
|
||||
async function GetSongs() {
|
||||
const allSongs = [];
|
||||
|
||||
let url =
|
||||
options.game === "iidx"
|
||||
? `${ServerConfig.ARC_API_URL}/api/v1/iidx/28/music/`
|
||||
: `${ServerConfig.ARC_API_URL}/api/v1/sdvx/5/music/`;
|
||||
|
||||
let moreData = true;
|
||||
while (moreData) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const songs = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${options.bearer}`,
|
||||
},
|
||||
}).then((r) => r.json());
|
||||
|
||||
if (songs._links._next) {
|
||||
url = songs._links._next;
|
||||
} else {
|
||||
moreData = false;
|
||||
}
|
||||
|
||||
allSongs.push(...songs._items);
|
||||
}
|
||||
|
||||
return allSongs;
|
||||
}
|
||||
|
||||
async function GetCharts() {
|
||||
const songs = await GetSongs();
|
||||
|
||||
const allCharts: any[] = [];
|
||||
const failed: any[] = [];
|
||||
|
||||
async function DoStuff(song: any) {
|
||||
const url =
|
||||
options.game === "iidx"
|
||||
? `${ServerConfig.ARC_API_URL}/api/v1/iidx/28/charts/?music_id=${song._id}&omnimix=true`
|
||||
: `${ServerConfig.ARC_API_URL}/api/v1/sdvx/5/charts/?music_id=${song._id}`;
|
||||
|
||||
try {
|
||||
logger.info(`Starting Request ${url}.`);
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const charts = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${options.bearer}`,
|
||||
},
|
||||
}).then((r) => r.json());
|
||||
|
||||
logger.info(`Parsed: ${charts._related.music[0].title}`);
|
||||
|
||||
allCharts.push(charts);
|
||||
} catch (err) {
|
||||
logger.error(err.message, { song, err });
|
||||
failed.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
for (const song of songs) {
|
||||
await DoStuff(song);
|
||||
}
|
||||
|
||||
// fast, but ARCs server cant keep up.
|
||||
// const promises = songs.map((s: any) => DoStuff(s));
|
||||
// await Promise.all(promises);
|
||||
|
||||
logger.info(failed);
|
||||
|
||||
logger.info("Retrieved charts.");
|
||||
return allCharts;
|
||||
}
|
||||
|
||||
const cacheLoc = path.join(__dirname, "cache.json");
|
||||
|
||||
async function MergeIDs() {
|
||||
let charts;
|
||||
|
||||
if (options.cache) {
|
||||
logger.info(`--cache provided, reading cache.`);
|
||||
try {
|
||||
charts = JSON.parse(fs.readFileSync(cacheLoc, "utf-8"));
|
||||
} catch (err) {
|
||||
throw new Error(`No cache, cannot use --cache!`);
|
||||
}
|
||||
} else {
|
||||
logger.info(`Reading from arc.`);
|
||||
|
||||
charts = await GetCharts();
|
||||
}
|
||||
|
||||
fs.writeFileSync(cacheLoc, JSON.stringify(charts));
|
||||
|
||||
for (const chartData of charts) {
|
||||
const songTitle = chartData._related.music[0].title;
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const song = await FindSongOnTitle(options.game, songTitle);
|
||||
|
||||
if (!song) {
|
||||
logger.error(`Could not resolve song ${songTitle}?`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const chart of chartData._items) {
|
||||
if (chart.rating === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const playtype: "SP" | "DP" | "Single" =
|
||||
options.game === "iidx" ? (chart.play_style === "DOUBLE" ? "DP" : "SP") : "Single";
|
||||
|
||||
let difficulty = chart.difficulty === "BLACK" ? "LEGGENDARIA" : chart.difficulty;
|
||||
const version = options.game === "iidx" ? "28-omni" : "vivid";
|
||||
|
||||
// yeah this fails for wacky playtype mismatches -- i don't care.
|
||||
if (
|
||||
["ミッドナイト堕天使", "Y&Co. is dead or alive", "State Of The Art"].includes(
|
||||
songTitle
|
||||
) &&
|
||||
difficulty === "LEGGENDARIA"
|
||||
) {
|
||||
difficulty = "ANOTHER";
|
||||
}
|
||||
|
||||
let tachiChart;
|
||||
if (options.game === "sdvx") {
|
||||
tachiChart = await FindSDVXChartOnDFVersion(
|
||||
song.id,
|
||||
difficulty === "INF" ? "ANY_INF" : difficulty,
|
||||
version as "vivid"
|
||||
);
|
||||
} else {
|
||||
tachiChart = await FindChartWithPTDFVersion(
|
||||
options.game,
|
||||
song.id,
|
||||
playtype,
|
||||
difficulty,
|
||||
version
|
||||
);
|
||||
}
|
||||
|
||||
if (!tachiChart) {
|
||||
logger.error(
|
||||
`Could not find chart ${songTitle} ${playtype} ${difficulty} ${version} (SongID ${song.id}).`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await db.charts[options.game as "sdvx" | "iidx"].update(
|
||||
{
|
||||
chartID: tachiChart.chartID,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
"data.arcChartID": chart._id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await db.charts[options.game as "sdvx" | "iidx"].update(
|
||||
{
|
||||
"data.arcChartID": { $exists: false },
|
||||
},
|
||||
{ $set: { "data.arcChartID": null } },
|
||||
{
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
|
||||
logger.info("done.");
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
MergeIDs();
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,118 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Command } from "commander";
|
||||
import db from "external/mongo/db";
|
||||
import fs from "fs";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import fetch from "node-fetch";
|
||||
import path from "path";
|
||||
import { ChartDocument, Difficulties, IIDXBPIData } from "tachi-common";
|
||||
import { FindChartWithPTDFVersion } from "utils/queries/charts";
|
||||
import { FindSongOnTitle } from "utils/queries/songs";
|
||||
import { RecalcAllScores } from "../__KT_DATABASE_MIGRATION/state-sync/recalc-all-scores";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
program.option("-f, --fetch", "Fetch the latest data from the poyashi repo.");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
const dataLoc = path.join(__dirname, "./output.json");
|
||||
|
||||
const difficultyResolve: Record<string, [string, string]> = {
|
||||
3: ["SP", "HYPER"],
|
||||
4: ["SP", "ANOTHER"],
|
||||
8: ["DP", "HYPER"],
|
||||
9: ["DP", "ANOTHER"],
|
||||
10: ["SP", "LEGGENDARIA"],
|
||||
11: ["DP", "LEGGENDARIA"],
|
||||
};
|
||||
|
||||
async function UpdatePoyashiData() {
|
||||
let data;
|
||||
if (options.fetch) {
|
||||
// lol!
|
||||
logger.info("Fetching data from github...");
|
||||
const rj = await fetch("https://proxy.poyashi.me/?type=bpi").then((r) => r.json());
|
||||
|
||||
logger.info("Fetched data.");
|
||||
fs.writeFileSync(dataLoc, JSON.stringify(rj));
|
||||
logger.info("Saved data to output.json.");
|
||||
|
||||
data = rj;
|
||||
} else {
|
||||
logger.info("Reading data from output.json...");
|
||||
data = JSON.parse(fs.readFileSync(dataLoc, "utf-8"));
|
||||
}
|
||||
|
||||
const realData: IIDXBPIData[] = [];
|
||||
for (const d of data.body) {
|
||||
const res = difficultyResolve[d.difficulty];
|
||||
|
||||
if (!res) {
|
||||
throw new Error(`Unknown difficulty ${d.difficulty}`);
|
||||
}
|
||||
|
||||
const [playtype, diff] = res;
|
||||
|
||||
const tachiSong = await FindSongOnTitle("iidx", d.title);
|
||||
|
||||
if (!tachiSong) {
|
||||
logger.warn(`Cannot find song ${d.title}?`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tachiChart = (await FindChartWithPTDFVersion(
|
||||
"iidx",
|
||||
tachiSong.id,
|
||||
playtype as "SP" | "DP",
|
||||
diff as Difficulties["iidx:DP" | "iidx:SP"],
|
||||
"27"
|
||||
)) as ChartDocument<"iidx:SP" | "iidx:DP">;
|
||||
|
||||
if (!tachiChart) {
|
||||
logger.warn(
|
||||
`Cannot find chart ${tachiSong.title} (${tachiSong.id}) ${playtype}, ${diff}?`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const kavg = Number(d.avg);
|
||||
|
||||
if (kavg < 0) {
|
||||
logger.warn(
|
||||
`${tachiSong.title} (${playtype} ${diff}). Invalid kavg ${d.avg}, Skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d.removed) {
|
||||
logger.info(`Skipping removed chart ${tachiSong.title}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
realData.push({
|
||||
coef: d.coef === -1 ? null : d.coef,
|
||||
kavg: Number(d.avg),
|
||||
wr: Number(d.wr),
|
||||
chartID: tachiChart.chartID,
|
||||
});
|
||||
}
|
||||
|
||||
await db["iidx-bpi-data"].remove({});
|
||||
await db["iidx-bpi-data"].insert(realData);
|
||||
|
||||
logger.info(`Removed all, and inserted ${realData.length} documents.`);
|
||||
|
||||
logger.info(`Triggering IIDX Recalc.`);
|
||||
|
||||
await RecalcAllScores({ game: "iidx" });
|
||||
|
||||
logger.info(`Done.`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
UpdatePoyashiData();
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { execSync } from "child_process";
|
||||
import { Command } from "commander";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import monk from "monk";
|
||||
|
||||
const program = new Command();
|
||||
program
|
||||
.option("--nsFrom <Database to clone from.>")
|
||||
.option("--nsTo <Database to clone and anonymise to.>");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
if (!options.nsTo || !options.nsFrom) {
|
||||
throw new Error(`Expected --nsFrom and --nsTo options.`);
|
||||
}
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
/**
|
||||
* Strips all name and private information from the database.
|
||||
* For use for things like future tachi-db-dumps?
|
||||
*
|
||||
* @param nsTo The database to anonymise. DO NOT PASS THE PRODUCTION DATABASE TO THIS!
|
||||
*/
|
||||
async function AnonymiseDB(nsTo: string) {
|
||||
logger.info(`Connecting to ${nsTo}.`);
|
||||
|
||||
const clonedDB = monk(`127.0.0.1/${nsTo}`);
|
||||
|
||||
logger.info(`Connected to ${nsTo}.`);
|
||||
|
||||
const r1 = await clonedDB.get("user-private-information").update(
|
||||
{},
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
// This is "password" encrypted in bcrypt12.
|
||||
password: "$2b$12$QRFCAxvFoNI2spszFPgt/e.qLy55GvYWlSHioa0AujRbFpChLwHmu",
|
||||
// emails are uniquely indexed. We need to anonymise these in
|
||||
// a way that they dont duplicate.
|
||||
email: { $concat: [{ $toString: "$userID" }, "@example.com"] },
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Stripped private info.`, { r1 });
|
||||
|
||||
const r2 = await clonedDB.get("users").update(
|
||||
{},
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
socialMedia: { $const: {} },
|
||||
},
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
username: { $concat: ["u", { $toString: "$id" }] },
|
||||
usernameLowercase: { $concat: ["u", { $toString: "$id" }] },
|
||||
},
|
||||
},
|
||||
],
|
||||
{
|
||||
multi: true,
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Stripped username info.`, { r2 });
|
||||
|
||||
for (const collection of [
|
||||
"api-clients",
|
||||
"oauth2-auth-codes",
|
||||
"password-reset-codes",
|
||||
"api-tokens",
|
||||
"kai-auth-tokens",
|
||||
"arc-saved-profiles",
|
||||
]) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const r3 = await clonedDB.get("collection").remove({});
|
||||
|
||||
logger.info(`Removed all ${collection} documents.`, { r3 });
|
||||
}
|
||||
|
||||
logger.info(`Done! Closing.`);
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function CloneDB(nsFrom: string, nsTo: string) {
|
||||
logger.info(`Cloning Database. This will take a while.`);
|
||||
execSync(`mongodump --gzip --archive=temp-dump --db=${nsFrom}`);
|
||||
execSync(`mongorestore --archive=temp-dump --gzip --nsFrom='${nsFrom}.*' --nsTo='${nsTo}.*'`);
|
||||
execSync(`rm temp-dump`);
|
||||
logger.info(`Cloned!`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
CloneDB(options.nsFrom, options.nsTo);
|
||||
AnonymiseDB(options.nsTo);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { Command } from "commander";
|
||||
import { SendEmail } from "lib/email/client";
|
||||
import { MainHTMLWrapper } from "lib/email/formats";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
|
||||
const program = new Command();
|
||||
program.option("-e, --email <Email to send to>");
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
if (!options.email) {
|
||||
throw new Error(`Need an --email to send to.`);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
logger.info(`Sending email to ${options.email}.`);
|
||||
await SendEmail(
|
||||
options.email,
|
||||
"Hello World",
|
||||
MainHTMLWrapper("Hello world! This is a test email for doing things."),
|
||||
"Hello world! This is a test email for doing things."
|
||||
);
|
||||
logger.info(`Done.`);
|
||||
process.exit(0);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "external/mongo/db";
|
||||
import fs from "fs";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import path from "path";
|
||||
import { FindSongOnTitleInsensitive } from "utils/queries/songs";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const comicallyLargeJSONData = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "./versions.json"), "utf-8")
|
||||
);
|
||||
|
||||
const DIFF_MAP = {
|
||||
A: "ANOTHER",
|
||||
H: "HYPER",
|
||||
N: "NORMAL",
|
||||
B: "BEGINNER",
|
||||
X: "LEGGENDARIA",
|
||||
} as any;
|
||||
|
||||
function ConvertVersion(c: string): any {
|
||||
if (c.startsWith("AC")) {
|
||||
return c.split("AC")[1];
|
||||
} else if (c.startsWith("OM")) {
|
||||
return `${c.split("OM")[1]}-omni`;
|
||||
} else if (c.startsWith("CS")) {
|
||||
return `${c.split("CS")[1]}-cs`;
|
||||
} else if (c === "INF") {
|
||||
return "inf";
|
||||
} else if (c === "BMUS") {
|
||||
return "bmus";
|
||||
} else {
|
||||
logger.error(`Invalid Chart Version ${c}.`);
|
||||
throw new Error("Invalid Chart Version");
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const data of comicallyLargeJSONData) {
|
||||
const song = await FindSongOnTitleInsensitive("iidx", data["MUSIC NAME"]);
|
||||
|
||||
if (!song) {
|
||||
logger.error(`Can't find song with title ${data["MUSIC NAME"]}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const playtype = data.STYLE === "S" ? "SP" : "DP";
|
||||
|
||||
const difficulty = DIFF_MAP[data.DIFF];
|
||||
|
||||
const relevantVersions = [
|
||||
"OM27",
|
||||
"OM26",
|
||||
"AC27",
|
||||
"AC26",
|
||||
"AC25",
|
||||
"AC24",
|
||||
"AC23",
|
||||
"AC22",
|
||||
"AC21",
|
||||
"AC20",
|
||||
"INF",
|
||||
"CS16",
|
||||
"CS15",
|
||||
"CS14",
|
||||
"CS13",
|
||||
"CS12",
|
||||
"CS11",
|
||||
"CS10",
|
||||
"CS9",
|
||||
"CS8",
|
||||
"CS7",
|
||||
"BMUS",
|
||||
"CS6",
|
||||
"CS5",
|
||||
"CS4",
|
||||
"CS3",
|
||||
]
|
||||
.filter((e) => data[e] === "1")
|
||||
.map(ConvertVersion);
|
||||
|
||||
if (!relevantVersions.includes("27-omni") && !relevantVersions.includes("inf")) {
|
||||
if (relevantVersions.includes("26-omni")) {
|
||||
logger.info(
|
||||
`Song ${song.title} (${playtype} ${difficulty}) was a very recent removal. Consider deprimarifying it. (had ${relevantVersions})`,
|
||||
{ keys: Object.keys(data).filter((e) => data[e] === "1") }
|
||||
);
|
||||
} else {
|
||||
logger.verbose(
|
||||
`Song ${song.title} (${playtype} ${difficulty}) has no relevant versions. (had ${relevantVersions})`,
|
||||
{ keys: Object.keys(data).filter((e) => data[e] === "1") }
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const chart = await db.charts.iidx.findOne(
|
||||
{ songID: song.id, difficulty, playtype },
|
||||
{ projectID: true }
|
||||
);
|
||||
|
||||
if (!chart) {
|
||||
logger.error(`Can't find chart ${song.title} (${playtype} ${difficulty})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
relevantVersions.push(...chart.versions.filter((e) => !relevantVersions.includes(e)));
|
||||
|
||||
await db.charts.iidx.update({ _id: chart._id }, { $set: { versions: relevantVersions } });
|
||||
}
|
||||
|
||||
logger.info("done");
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,172 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import {
|
||||
FormatGame,
|
||||
GetGameConfig,
|
||||
GetGamePTConfig,
|
||||
IDStrings,
|
||||
integer,
|
||||
StaticConfig,
|
||||
} from "tachi-common";
|
||||
import { CreateFolderID } from "utils/folder";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
function StrRange(min: integer, max: integer) {
|
||||
const arr = [];
|
||||
for (let i = min; i <= max; i++) {
|
||||
arr.push(i.toString());
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
// These are the games that have simple, discrete folders.
|
||||
const STATIC_LEVELS: Partial<Record<IDStrings, string[]>> = {
|
||||
"iidx:SP": StrRange(1, 12),
|
||||
"iidx:DP": StrRange(1, 12),
|
||||
"bms:7K": [],
|
||||
"bms:14K": [],
|
||||
"chunithm:Single": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"7+",
|
||||
"8",
|
||||
"8+",
|
||||
"9",
|
||||
"9+",
|
||||
"10",
|
||||
"10+",
|
||||
"11",
|
||||
"11+",
|
||||
"12",
|
||||
"12+",
|
||||
"13",
|
||||
"13+",
|
||||
"14",
|
||||
"14+",
|
||||
],
|
||||
"maimai:Single": [
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"7+",
|
||||
"8",
|
||||
"8+",
|
||||
"9",
|
||||
"9+",
|
||||
"10",
|
||||
"10+",
|
||||
"11",
|
||||
"11+",
|
||||
"12",
|
||||
"12+",
|
||||
"13",
|
||||
"13+",
|
||||
"14",
|
||||
],
|
||||
"museca:Single": StrRange(1, 15),
|
||||
"sdvx:Single": StrRange(1, 20),
|
||||
"ddr:SP": StrRange(1, 20),
|
||||
"ddr:DP": StrRange(1, 20),
|
||||
};
|
||||
|
||||
async function InsertStaticLevelVersionFolders() {
|
||||
// Little known fact: I invented the cyclomatic complexity warning
|
||||
for (const { game, playtype } of GPTYielder()) {
|
||||
const gameConfig = GetGameConfig(game);
|
||||
const gptConfig = GetGamePTConfig(game, playtype);
|
||||
|
||||
for (const version of gptConfig.supportedVersions) {
|
||||
// @ts-expect-error dont worry
|
||||
const staticLevels = STATIC_LEVELS[`${game}:${playtype}`];
|
||||
|
||||
if (!staticLevels) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let inactive = false;
|
||||
|
||||
if (
|
||||
!(
|
||||
gptConfig.currentGameOmniVersion === version ||
|
||||
gptConfig.currentLatestVersion === version ||
|
||||
gptConfig.currentLocalVersion === version
|
||||
)
|
||||
) {
|
||||
inactive = true;
|
||||
}
|
||||
|
||||
await db.tables.insert({
|
||||
tableID: `${game}-${playtype}-${version}-levels`,
|
||||
description: `Levels for ${FormatGame(game, playtype)} in version ${version}.`,
|
||||
title: `${FormatGame(game, playtype)} (${version})`,
|
||||
folders: [],
|
||||
game,
|
||||
playtype,
|
||||
inactive,
|
||||
});
|
||||
|
||||
for (const level of staticLevels) {
|
||||
const query = {
|
||||
level,
|
||||
versions: version,
|
||||
};
|
||||
|
||||
const folderID = CreateFolderID(query, game, playtype);
|
||||
|
||||
await db.folders.insert({
|
||||
folderID,
|
||||
game,
|
||||
playtype,
|
||||
title: `Level ${level} (${version})`,
|
||||
type: "charts",
|
||||
data: query,
|
||||
searchTerms: [],
|
||||
inactive,
|
||||
});
|
||||
|
||||
await db.tables.update(
|
||||
{
|
||||
tableID: `${game}-${playtype}-${version}-levels`,
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
folders: folderID,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Inserted ${gameConfig.name} Level ${level} (${version})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Done.`);
|
||||
}
|
||||
|
||||
function* GPTYielder() {
|
||||
for (const game of StaticConfig.allSupportedGames) {
|
||||
const gameConfig = GetGameConfig(game);
|
||||
for (const playtype of gameConfig.validPlaytypes) {
|
||||
yield { game, playtype };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some games don't use "fixed" levels per se, and as such breaking
|
||||
// the folders up are quite hard. This is for *those* games.
|
||||
async function InsertCustomStuff() {}
|
||||
|
||||
(async () => {
|
||||
await InsertStaticLevelVersionFolders();
|
||||
})();
|
||||
@@ -0,0 +1,3 @@
|
||||
import { InitaliseFolderChartLookup } from "../../src/utils/folder";
|
||||
|
||||
InitaliseFolderChartLookup().then(() => process.exit(1));
|
||||
@@ -1,174 +1,180 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable no-param-reassign */
|
||||
import db from "../../src/db/db";
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import fetch from "node-fetch";
|
||||
import { FindSongOnTitle } from "../../src/common/database-lookup/song";
|
||||
import { FindChartWithPTDF } from "../../src/common/database-lookup/chart";
|
||||
import { Difficulties, TierlistDataDocument } from "kamaitachi-common";
|
||||
|
||||
import { CalculateTierlistDataID } from "../../src/common/tierlist";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import { Difficulties, integer } from "tachi-common";
|
||||
import { FindChartWithPTDF } from "utils/queries/charts";
|
||||
import { FindSongOnTitle } from "utils/queries/songs";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const TIERLIST_ID = "ee9b756e50cff8282091102257b01f423ef855f2";
|
||||
|
||||
async function FetchSP12Data() {
|
||||
const rj = await fetch("https://sp12.iidx.app/api/v1/sheets").then((r) => r.json()); // will throw if somethings wrong, anyway
|
||||
|
||||
if (!rj.sheets) {
|
||||
throw new Error(`No sheets in return from sp12?`);
|
||||
}
|
||||
|
||||
const tdd: TierlistDataDocument<"Individual Difference">[] = [];
|
||||
|
||||
for (const sh of rj.sheets) {
|
||||
let chart;
|
||||
try {
|
||||
chart = await HumanisedTitleLookup(sh.title);
|
||||
} catch (err) {
|
||||
logger.error((err as Error).message);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const key of ["NORMAL CLEAR", "HARD CLEAR", "EX HARD CLEAR"]) {
|
||||
let fakeKey;
|
||||
let val;
|
||||
|
||||
switch (key) {
|
||||
case "NORMAL CLEAR":
|
||||
fakeKey = "n_clear";
|
||||
const v = Math.floor(sh[fakeKey] / 2);
|
||||
|
||||
if (v === 9) {
|
||||
val = 11.8;
|
||||
} else if (v === 8) {
|
||||
val = 12.0;
|
||||
} else if (v === 7) {
|
||||
val = 12.2;
|
||||
} else if (v === 6) {
|
||||
val = 12.4;
|
||||
} else if (v === 5) {
|
||||
val = 12.6;
|
||||
} else if (v < 0) {
|
||||
continue;
|
||||
} else {
|
||||
val = 12.6 + (5 - v) * 0.1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "HARD CLEAR":
|
||||
fakeKey = "hard";
|
||||
const v2 = Math.floor(sh[fakeKey] / 2);
|
||||
|
||||
if (v2 === 9) {
|
||||
val = 11.9;
|
||||
} else if (v2 === 8) {
|
||||
val = 12.1;
|
||||
} else if (v2 === 7) {
|
||||
val = 12.3;
|
||||
} else if (v2 === 6) {
|
||||
val = 12.5;
|
||||
} else if (v2 === 5) {
|
||||
val = 12.7;
|
||||
} else if (v2 < 0) {
|
||||
continue;
|
||||
} else {
|
||||
val = 12.7 + (5 - v2) * 0.1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "EX HARD CLEAR":
|
||||
fakeKey = "exh";
|
||||
|
||||
const v3 = sh[fakeKey];
|
||||
if (v3 < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
val = 12.4 + (12 - v3) * 0.1;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new Error("??");
|
||||
}
|
||||
|
||||
const humanised = sh[`${fakeKey}_string`];
|
||||
|
||||
if (humanised === "難易度未定") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val < 11.8) {
|
||||
logger.info(`Skipping ${sh.title} ${key}, ${humanised}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
val = parseFloat(val.toFixed(2));
|
||||
|
||||
tdd.push({
|
||||
chartID: chart.chartID,
|
||||
type: "lamp",
|
||||
tierlistID: TIERLIST_ID,
|
||||
tierlistDataID: CalculateTierlistDataID(chart.chartID, "lamp", key, TIERLIST_ID),
|
||||
key,
|
||||
data: {
|
||||
humanised,
|
||||
value: val,
|
||||
flags: {
|
||||
"Individual Difference": !!humanised.match(/個人差/u),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Inserting ${tdd.length} documents...`);
|
||||
|
||||
await db["tierlist-data"].insert(tdd);
|
||||
|
||||
logger.info("Done!");
|
||||
|
||||
process.exit(0);
|
||||
interface SP12Data {
|
||||
id: integer;
|
||||
title: string;
|
||||
n_clear: integer;
|
||||
hard: integer;
|
||||
exh: integer;
|
||||
n_clear_string: string;
|
||||
hard_string: string;
|
||||
exh_string: string;
|
||||
version: integer;
|
||||
}
|
||||
|
||||
function isOdd(num: number) {
|
||||
return num % 2 !== 0;
|
||||
async function FetchSP12Data() {
|
||||
const rj: { sheets: SP12Data[] } = await fetch("https://sp12.iidx.app/api/v1/sheets").then(
|
||||
(r) => r.json()
|
||||
); // will throw if somethings wrong, anyway
|
||||
|
||||
if (!rj.sheets) {
|
||||
throw new Error(`No sheets in return from sp12?`);
|
||||
}
|
||||
|
||||
for (const sh of rj.sheets) {
|
||||
let chart;
|
||||
try {
|
||||
chart = await HumanisedTitleLookup(sh.title);
|
||||
} catch (err) {
|
||||
logger.error((err as Error).message);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
logger.warn(`Couldn't find chart with title ${sh.title}`);
|
||||
}
|
||||
|
||||
for (const key of ["n_clear", "hard", "exh"] as const) {
|
||||
let val;
|
||||
|
||||
switch (key) {
|
||||
case "n_clear":
|
||||
const v = Math.floor(sh[key] / 2);
|
||||
|
||||
if (v === 9) {
|
||||
val = 11.8;
|
||||
} else if (v === 8) {
|
||||
val = 12.0;
|
||||
} else if (v === 7) {
|
||||
val = 12.2;
|
||||
} else if (v === 6) {
|
||||
val = 12.4;
|
||||
} else if (v === 5) {
|
||||
val = 12.6;
|
||||
} else if (v < 0) {
|
||||
continue;
|
||||
} else {
|
||||
val = 12.6 + (5 - v) * 0.1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "hard":
|
||||
const v2 = Math.floor(sh[key] / 2);
|
||||
|
||||
if (v2 === 9) {
|
||||
val = 11.9;
|
||||
} else if (v2 === 8) {
|
||||
val = 12.1;
|
||||
} else if (v2 === 7) {
|
||||
val = 12.3;
|
||||
} else if (v2 === 6) {
|
||||
val = 12.5;
|
||||
} else if (v2 === 5) {
|
||||
val = 12.7;
|
||||
} else if (v2 < 0) {
|
||||
continue;
|
||||
} else {
|
||||
val = 12.7 + (5 - v2) * 0.1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "exh":
|
||||
const v3 = sh[key];
|
||||
if (v3 >= 12 || v3 <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
val = 12.4 + (12 - v3) * 0.1;
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new Error("??");
|
||||
}
|
||||
|
||||
const stringVal = sh[`${key}_string` as const];
|
||||
|
||||
if (stringVal === "難易度未定") {
|
||||
continue;
|
||||
}
|
||||
|
||||
val = parseFloat(val.toFixed(2));
|
||||
|
||||
const ktKey = key === "exh" ? "kt-EXHC" : key === "hard" ? "kt-HC" : "kt-NC";
|
||||
|
||||
// If EXH, just save the string version as 12.xx
|
||||
// Else, use the A/S+ thing that people are used to.
|
||||
const text =
|
||||
ktKey === "kt-EXHC"
|
||||
? val.toFixed(2)
|
||||
: `12${stringVal.replace(/(個人差|地力)/u, "")}`;
|
||||
|
||||
await db.charts.iidx.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
[`tierlistInfo.${ktKey}`]: {
|
||||
text,
|
||||
value: val,
|
||||
individualDifference: stringVal.includes("個人差"),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Saved ${sh.title} value ${key} = ${val} (${text}).`);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Done!");
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function HumanisedTitleLookup(originalTitle: string) {
|
||||
let difficulty: Difficulties["iidx:SP"] = "ANOTHER";
|
||||
let difficulty: Difficulties["iidx:SP"] = "ANOTHER";
|
||||
|
||||
let title = originalTitle;
|
||||
let title = originalTitle;
|
||||
|
||||
if (title.match(/(†|†LEGGENDARIA)$/u)) {
|
||||
difficulty = "LEGGENDARIA";
|
||||
title = title.split("†")[0]; // this will break if there is ever a ptp leggendaria
|
||||
} else if (title.match(/\[H\]$/u)) {
|
||||
difficulty = "HYPER";
|
||||
title = title.split("[")[0];
|
||||
} else if (title.match(/\[A\]$/u)) {
|
||||
difficulty = "ANOTHER"; // lmao
|
||||
title = title.split("[")[0];
|
||||
}
|
||||
if (title.match(/(†|†LEGGENDARIA)$/u)) {
|
||||
difficulty = "LEGGENDARIA";
|
||||
title = title.split(/(†|†LEGGENDARIA)$/u)[0];
|
||||
} else if (title.match(/\[H\]$/u)) {
|
||||
difficulty = "HYPER";
|
||||
title = title.split("[")[0];
|
||||
} else if (title.match(/\[A\]$/u)) {
|
||||
difficulty = "ANOTHER"; // lmao
|
||||
title = title.split("[")[0];
|
||||
}
|
||||
|
||||
const song = await FindSongOnTitle("iidx", title);
|
||||
const song = await FindSongOnTitle("iidx", title);
|
||||
|
||||
if (!song) {
|
||||
throw new Error(
|
||||
`Could not resolve song ${title} (${difficulty}) (Original ${originalTitle}).`
|
||||
);
|
||||
}
|
||||
if (!song) {
|
||||
throw new Error(
|
||||
`Could not resolve song ${title} (${difficulty}) (Original ${originalTitle}).`
|
||||
);
|
||||
}
|
||||
|
||||
const chart = await FindChartWithPTDF("iidx", song.id, "SP", difficulty);
|
||||
const chart = await FindChartWithPTDF("iidx", song.id, "SP", difficulty);
|
||||
|
||||
if (!chart) {
|
||||
throw new Error(
|
||||
`Could not resolve chart ${title} ${difficulty} (Original ${originalTitle}).`
|
||||
);
|
||||
}
|
||||
if (!chart) {
|
||||
throw new Error(
|
||||
`Could not resolve chart ${title} ${difficulty} (Original ${originalTitle}).`
|
||||
);
|
||||
}
|
||||
|
||||
return chart;
|
||||
return chart;
|
||||
}
|
||||
|
||||
FetchSP12Data();
|
||||
|
||||
@@ -2,4 +2,4 @@ get the data by going to https://iidx.insane.pe.kr/!/sp10/ and typing copy(table
|
||||
|
||||
parsers here handle the rest.
|
||||
|
||||
the sp8h tierlist *sucks*, so, we're ignoring it.
|
||||
the sp8h, 9 and 10 tierlists *suck*, so, we're ignoring them.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,164 +1,117 @@
|
||||
import db from "../../src/db/db";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "external/mongo/db";
|
||||
import fs from "fs";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import path from "path";
|
||||
import { CalculateTierlistDataID } from "../../src/common/tierlist";
|
||||
import { FindSongOnTitle } from "../../src/common/database-lookup/song";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
import { FindChartWithPTDF } from "../../src/common/database-lookup/chart";
|
||||
import { Difficulties } from "kamaitachi-common";
|
||||
import { Difficulties } from "tachi-common";
|
||||
import { FindChartWithPTDF } from "utils/queries/charts";
|
||||
import { FindSongOnTitle } from "utils/queries/songs";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const TIERLIST_ID = "ee9b756e50cff8282091102257b01f423ef855f2";
|
||||
|
||||
async function parseKr(
|
||||
dir: string,
|
||||
key: "NORMAL CLEAR" | "HARD CLEAR",
|
||||
catVals: { h: string; v: number }[]
|
||||
file: string,
|
||||
mode: "NC" | "HC",
|
||||
catVals: { text: string; value: number; idv: boolean }[]
|
||||
) {
|
||||
const krdata = JSON.parse(fs.readFileSync(path.join(__dirname, "./iidx-pe-kr", dir), "utf-8"));
|
||||
const krdata = JSON.parse(fs.readFileSync(path.join(__dirname, "./iidx-pe-kr", file), "utf-8"));
|
||||
|
||||
const tdd = [];
|
||||
for (let i = 0; i < krdata.categories.length; i++) {
|
||||
const data = krdata.categories[i];
|
||||
if (data.sortindex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < krdata.categories.length; i++) {
|
||||
const data = krdata.categories[i];
|
||||
if (data.sortindex < 0) {
|
||||
continue;
|
||||
}
|
||||
const cv = catVals[i];
|
||||
|
||||
const cv = catVals[i];
|
||||
if (!cv) {
|
||||
throw new Error(`cv krdata mismatch ${krdata.categories.length} ${catVals.length}?`);
|
||||
}
|
||||
|
||||
if (!cv) {
|
||||
throw new Error(`cv krdata mismatch ${krdata.categories.length} ${catVals.length}?`);
|
||||
}
|
||||
for (const item of data.items) {
|
||||
const song = await FindSongOnTitle("iidx", item.data.title.replace(/†$/u, "").trim());
|
||||
|
||||
for (const item of data.items) {
|
||||
const song = await FindSongOnTitle("iidx", item.data.title.replace(/†$/u, "").trim());
|
||||
if (!song) {
|
||||
logger.warn(`Could not find song with title ${item.data.title}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!song) {
|
||||
logger.warn(`Could not find song with title ${item.data.title}`);
|
||||
continue;
|
||||
}
|
||||
const t = item.data.type;
|
||||
|
||||
const t = item.data.type;
|
||||
let diff: Difficulties["iidx:SP"];
|
||||
if (t === "A") {
|
||||
diff = "ANOTHER";
|
||||
} else if (t === "L") {
|
||||
diff = "LEGGENDARIA";
|
||||
} else if (t === "H") {
|
||||
diff = "HYPER";
|
||||
} else if (t === "N") {
|
||||
diff = "NORMAL";
|
||||
} else {
|
||||
logger.warn(`${song.title} Unknown difficulty ${t}.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let diff: Difficulties["iidx:SP"];
|
||||
if (t === "A") {
|
||||
diff = "ANOTHER";
|
||||
} else if (t === "L") {
|
||||
diff = "LEGGENDARIA";
|
||||
} else if (t === "H") {
|
||||
diff = "HYPER";
|
||||
} else if (t === "N") {
|
||||
diff = "NORMAL";
|
||||
} else {
|
||||
logger.warn(`${song.title} Unknown difficulty ${t}.`);
|
||||
continue;
|
||||
}
|
||||
const chart = await FindChartWithPTDF("iidx", song.id, "SP", diff);
|
||||
|
||||
const chart = await FindChartWithPTDF("iidx", song.id, "SP", diff);
|
||||
if (!chart) {
|
||||
logger.warn(`${song.title} ${diff} - Couldn't find chart?`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!chart) {
|
||||
logger.warn(`${song.title} ${diff} - Couldn't find chart?`);
|
||||
continue;
|
||||
}
|
||||
// Update this part of the tierlist, we've got everything.
|
||||
|
||||
tdd.push({
|
||||
chartID: chart.chartID,
|
||||
type: "lamp" as const,
|
||||
tierlistID: TIERLIST_ID,
|
||||
tierlistDataID: CalculateTierlistDataID(chart.chartID, "lamp", key, TIERLIST_ID),
|
||||
key,
|
||||
data: {
|
||||
humanised: cv.h,
|
||||
value: cv.v,
|
||||
flags: {
|
||||
"Individual Difference": !!data.category.match(/個人差/u),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
await db.charts.iidx.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
[`tierlistInfo.kt-${mode}`]: {
|
||||
text: cv.text,
|
||||
value: cv.value,
|
||||
individualDifference: cv.idv,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
logger.info(`Inserting ${tdd.length} documents...`);
|
||||
logger.verbose(`Updated ${chart.chartID} to value ${cv.value}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const x = await db["tierlist-data"].bulkWrite(
|
||||
tdd.map((e) => ({
|
||||
updateOne: {
|
||||
filter: {
|
||||
tierlistDataID: e.tierlistDataID,
|
||||
},
|
||||
update: {
|
||||
$set: e,
|
||||
},
|
||||
upsert: true,
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
logger.info(x);
|
||||
|
||||
logger.info("done...");
|
||||
logger.info(`Finished parsing ${file}`);
|
||||
}
|
||||
|
||||
function h(h: string, v: number) {
|
||||
return { h, v };
|
||||
function h(text: string, value: number, idv = false) {
|
||||
return { text, value, idv };
|
||||
}
|
||||
|
||||
parseKr("sp9H.json", "HARD CLEAR", [
|
||||
h("S+", 10.2),
|
||||
h("S", 10),
|
||||
h("S 個人差", 10),
|
||||
h("A+", 9.9),
|
||||
h("A+ 個人差", 9.9),
|
||||
h("A", 9.8),
|
||||
h("B+", 9.7),
|
||||
h("B", 9.6),
|
||||
h("C+", 9.5),
|
||||
h("C", 9.4),
|
||||
h("D", 9.2),
|
||||
h("E", 9.0),
|
||||
h("F", 8.8),
|
||||
parseKr("sp11N.json", "NC", [
|
||||
h("11S+", 12.2),
|
||||
h("11S", 12),
|
||||
h("11A+", 11.9),
|
||||
h("11A", 11.8),
|
||||
h("11B", 11.6),
|
||||
h("11C", 11.4),
|
||||
h("11D", 11.2),
|
||||
h("11E", 11.0),
|
||||
h("11F", 10.8),
|
||||
]);
|
||||
|
||||
parseKr("sp10H.json", "HARD CLEAR", [
|
||||
h("S", 11),
|
||||
h("A+", 10.9),
|
||||
h("A", 10.8),
|
||||
h("B+", 10.7),
|
||||
h("B", 10.6),
|
||||
h("C+", 10.5),
|
||||
h("C", 10.4),
|
||||
h("D", 10.2),
|
||||
h("E", 10.0),
|
||||
h("F", 9.8),
|
||||
]);
|
||||
|
||||
parseKr("sp11N.json", "NORMAL CLEAR", [
|
||||
h("S+", 12.2),
|
||||
h("S", 12),
|
||||
h("A+", 11.9),
|
||||
h("A", 11.8),
|
||||
h("B", 11.6),
|
||||
h("C", 11.4),
|
||||
h("D", 11.2),
|
||||
h("E", 11.0),
|
||||
h("F", 10.8),
|
||||
]);
|
||||
|
||||
parseKr("sp11H.json", "HARD CLEAR", [
|
||||
h("S+", 12.2),
|
||||
h("S+ 個人差", 12.2),
|
||||
h("S", 12),
|
||||
h("S 個人差", 12),
|
||||
h("A+", 11.9),
|
||||
h("A+ 個人差", 11.9),
|
||||
h("A", 11.8),
|
||||
h("A 個人差", 11.0),
|
||||
h("B+", 11.7),
|
||||
h("B", 11.6),
|
||||
h("C", 11.4),
|
||||
h("D", 11.2),
|
||||
h("E", 11.0),
|
||||
h("F", 10.8),
|
||||
parseKr("sp11H.json", "HC", [
|
||||
h("11S+", 12.2),
|
||||
h("11S+", 12.2, true), // 個人差
|
||||
h("11S", 12),
|
||||
h("11S", 12, true), // 個人差
|
||||
h("11A+", 11.9),
|
||||
h("11A+", 11.9, true), // 個人差
|
||||
h("11A", 11.8),
|
||||
h("11A", 11.0, true), // 個人差
|
||||
h("11B+", 11.7),
|
||||
h("11B", 11.6),
|
||||
h("11C", 11.4),
|
||||
h("11D", 11.2),
|
||||
h("11E", 11.0),
|
||||
h("11F", 10.8),
|
||||
]);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { InitaliseFolderChartLookup } from "../../src/common/folder";
|
||||
|
||||
InitaliseFolderChartLookup();
|
||||
@@ -0,0 +1,5 @@
|
||||
import { InitSequenceDocs } from "external/mongo/sequence-docs";
|
||||
|
||||
if (require.main === module) {
|
||||
InitSequenceDocs().then(() => process.exit(0));
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import db from "../db/db";
|
||||
import CreateLogCtx from "../common/logger";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
(async () => {
|
||||
let users = await db.counters.findOne({
|
||||
counterName: "users",
|
||||
});
|
||||
|
||||
if (users) {
|
||||
throw new Error(`"users" document already exists, exiting.`);
|
||||
}
|
||||
|
||||
await db.counters.insert({
|
||||
counterName: "users",
|
||||
value: 1,
|
||||
});
|
||||
|
||||
logger.info("Successfully initialised counter documents. Exiting.");
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Command } from "commander";
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { Random20Hex } from "utils/misc";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program.option("-c, --code <code>", "The code for this invite.");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
if (!options.code) {
|
||||
options.code = Random20Hex();
|
||||
}
|
||||
|
||||
db.invites
|
||||
.insert({
|
||||
code: options.code,
|
||||
createdBy: 1,
|
||||
consumed: false,
|
||||
createdAt: Date.now(),
|
||||
consumedBy: null,
|
||||
consumedAt: null,
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`Created invite ${options.code}.`);
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "ktblack-server-scripts",
|
||||
"version": "0.0.0",
|
||||
"description": "Scripts and their dependencies for KTBlack. This lets us separate dependencies to avoid unecessary overhead in the CI/CD pipeline.",
|
||||
"main": "",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import deepmerge from "deepmerge";
|
||||
import { ICollection } from "monk";
|
||||
import { ChartDocument, IDStrings } from "tachi-common";
|
||||
import { Random20Hex } from "utils/misc";
|
||||
|
||||
export async function QuickInsert<I extends IDStrings>(
|
||||
base: Omit<ChartDocument<I>, "chartID">,
|
||||
content: Partial<ChartDocument<I>>[],
|
||||
coll: ICollection
|
||||
) {
|
||||
const docs = [];
|
||||
|
||||
for (const c of content) {
|
||||
const doc = deepmerge(base, c);
|
||||
|
||||
doc.chartID = Random20Hex();
|
||||
docs.push(doc);
|
||||
}
|
||||
|
||||
await coll.insert(docs);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import monk from "monk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { Command } from "commander";
|
||||
import CreateLogCtx from "../src/common/logger";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
program.option("-c, --collection <collection>", "The collection to fetch ktblack data from.");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
const collection = options.collection;
|
||||
|
||||
logger.info(`Connecting to ${process.env.MONGO_BASE_URL}/ktblackdb...`);
|
||||
const ktBlackDB = monk(`${process.env.MONGO_BASE_URL}/ktblackdb`);
|
||||
|
||||
(async () => {
|
||||
logger.info(`Fetching data for ${collection}...`);
|
||||
const data = await ktBlackDB.get(collection).find({}, { projection: { _id: 0 } });
|
||||
|
||||
logger.info(`Fetched ${data.length} documents, Writing...`);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, `../src/test-utils/test-data/kamaitachi/ktblack-${collection}.json`),
|
||||
JSON.stringify(data)
|
||||
);
|
||||
|
||||
logger.info("Done!");
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,135 +1,16 @@
|
||||
import monk from "monk";
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { Command } from "commander";
|
||||
import CreateLogCtx from "../src/lib/logger/logger";
|
||||
import { IndexOptions } from "mongodb";
|
||||
import { ValidDatabases } from "kamaitachi-common";
|
||||
import { supportedGames } from "kamaitachi-common/js/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
import { SetIndexes } from "../src/external/mongo/indexes";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program.option("-d, --db <database>", "The database to index.");
|
||||
program.option(
|
||||
"-r, --reset",
|
||||
"Whether to reset all indexes on this database before indexing or not."
|
||||
"-r, --reset",
|
||||
"Whether to reset all indexes on this database before indexing or not."
|
||||
);
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
const db = monk(`${process.env.MONGO_BASE_URL ?? "127.0.0.1"}/${options.db ?? "ktblackdb"}`);
|
||||
|
||||
interface Index {
|
||||
fields: Record<string, unknown>;
|
||||
options?: IndexOptions;
|
||||
}
|
||||
|
||||
function index(fields: Record<string, unknown>, options?: IndexOptions) {
|
||||
return { fields, options };
|
||||
}
|
||||
|
||||
const UNIQUE = { unique: true };
|
||||
|
||||
const staticIndexes: Partial<Record<ValidDatabases, Index[]>> = {
|
||||
scores: [index({ scoreID: 1 }, UNIQUE)],
|
||||
"score-pbs": [
|
||||
index({ chartID: 1, userID: 1 }, UNIQUE),
|
||||
index({ chartID: 1, "scoreData.percent": 1 }),
|
||||
],
|
||||
sessions: [
|
||||
// lol
|
||||
index({ timeStarted: 1, timeEnded: 1, userID: 1, game: 1, playtype: 1 }),
|
||||
],
|
||||
"game-stats": [index({ userID: 1, game: 1, playtype: 1 }, UNIQUE)],
|
||||
"folder-chart-lookup": [index({ chartID: 1, folderID: 1 }, UNIQUE)],
|
||||
"tierlist-data": [
|
||||
index({ tierlistDataID: 1 }, UNIQUE),
|
||||
index({ chartID: 1, tierlistID: 1 }),
|
||||
index({ chartID: 1, tierlistID: 1, type: 1 }),
|
||||
index({ chartID: 1, tierlistID: 1, type: 1, key: 1 }, UNIQUE),
|
||||
],
|
||||
goals: [index({ goalID: 1 }, UNIQUE)],
|
||||
"user-goals": [index({ goalID: 1, userID: 1 }, UNIQUE), index({ goalID: 1 })],
|
||||
milestones: [index({ milestoneID: 1 }, UNIQUE), index({ group: 1, game: 1, playtype: 1 })],
|
||||
"user-milestones": [
|
||||
index({ milestoneID: 1, userID: 1 }, UNIQUE),
|
||||
index({ userID: 1, game: 1, playtype: 1 }),
|
||||
],
|
||||
imports: [index({ importID: 1 }, UNIQUE)],
|
||||
"import-timings": [
|
||||
index({ importID: 1 }, UNIQUE),
|
||||
index({ timestamp: 1 }),
|
||||
index({ total: 1 }),
|
||||
],
|
||||
// @todo #96 Add more indexes to the users collection.
|
||||
users: [index({ id: 1 }, UNIQUE)],
|
||||
tierlists: [
|
||||
index({ tierlistID: 1 }, UNIQUE),
|
||||
index({ game: 1, playtype: 1, isDefault: 1 }, UNIQUE),
|
||||
],
|
||||
folders: [
|
||||
index({ folderID: 1 }, UNIQUE),
|
||||
index({ game: 1, playtype: 1 }),
|
||||
index({ game: 1, playtype: 1, table: 1 }),
|
||||
index({ game: 1, playtype: 1, table: 1, tableIndex: 1 }),
|
||||
],
|
||||
"kai-auth-tokens": [index({ userID: 1, service: 1 }, UNIQUE)],
|
||||
"charts-iidx": [
|
||||
index(
|
||||
{ "data.arcChartID": 1 },
|
||||
{ unique: true, partialFilterExpression: { "data.arcChartID": { $type: "string" } } }
|
||||
),
|
||||
index({ "data.hashSHA256": 1 }),
|
||||
],
|
||||
};
|
||||
|
||||
const indexes: Partial<Record<ValidDatabases, Index[]>> = staticIndexes;
|
||||
|
||||
for (const game of supportedGames) {
|
||||
if (indexes[`charts-${game}` as "charts-iidx"]) {
|
||||
indexes[`charts-${game}` as "charts-iidx"]!.push(
|
||||
index({ chartID: 1 }, UNIQUE),
|
||||
index(
|
||||
{ songID: 1, difficulty: 1, playtype: 1, isPrimary: 1 },
|
||||
{ unique: true, partialFilterExpression: { isPrimary: { $eq: true } } }
|
||||
)
|
||||
);
|
||||
} else {
|
||||
indexes[`charts-${game}` as "charts-iidx"] = [
|
||||
index({ chartID: 1 }, UNIQUE),
|
||||
index({ songID: 1, difficulty: 1, playtype: 1, isPrimary: 1 }, UNIQUE),
|
||||
];
|
||||
}
|
||||
|
||||
if (indexes[`songs-${game}` as "songs-iidx"]) {
|
||||
indexes[`songs-${game}` as "songs-iidx"]!.push(
|
||||
index({ id: 1 }, UNIQUE),
|
||||
index({ title: 1 })
|
||||
);
|
||||
} else {
|
||||
indexes[`songs-${game}` as "songs-iidx"] = [index({ id: 1 }, UNIQUE), index({ title: 1 })];
|
||||
}
|
||||
}
|
||||
|
||||
(async () => {
|
||||
logger.info(`Starting indexing for ${options.db ?? "ktblackdb"}...`);
|
||||
|
||||
for (const collection in indexes) {
|
||||
if (options.reset) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await db.get(collection).dropIndexes();
|
||||
}
|
||||
|
||||
// @ts-expect-error dru(n)kts
|
||||
for (const index of indexes[collection]) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const r = await db.get(collection).createIndex(index.fields, index.options);
|
||||
|
||||
logger.info(r);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Done.");
|
||||
process.exit(0);
|
||||
})();
|
||||
SetIndexes(options.db, options.reset).then(() => process.exit(0));
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import db from "../../src/db/db";
|
||||
|
||||
db.charts.iidx
|
||||
.find({})
|
||||
.each(async (c, { pause, resume }) => {
|
||||
pause();
|
||||
|
||||
await db.charts.iidx.update(
|
||||
{ _id: c._id },
|
||||
{
|
||||
$set: {
|
||||
"flags.2dxtra": false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
resume();
|
||||
})
|
||||
.then(() => console.log("done"));
|
||||
@@ -0,0 +1,16 @@
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
(async () => {
|
||||
logger.info(`Setting tableFolders.`);
|
||||
const r = await db.charts.bms.update(
|
||||
{},
|
||||
{ $set: { "data.tableFolders": [] } },
|
||||
{ multi: true }
|
||||
);
|
||||
logger.info(`Done!`, { r });
|
||||
|
||||
process.exit(0);
|
||||
})();
|
||||
@@ -1,24 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { PRUDENCE_CHART_SCHEMAS } from "../../src/db/schemas";
|
||||
import p from "prudence";
|
||||
import db from "../../src/db/db";
|
||||
import CreateLogCtx from "../../src/common/logger";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
let data = JSON.parse(fs.readFileSync(path.join(__dirname, "./hcnverdata.json"), "utf-8"));
|
||||
|
||||
for (const d of data) {
|
||||
let r = p(d, PRUDENCE_CHART_SCHEMAS.iidx);
|
||||
if (r) {
|
||||
logger.error(r.userVal);
|
||||
logger.error(r);
|
||||
throw r;
|
||||
}
|
||||
}
|
||||
|
||||
db.charts.iidx.insert(data).then(() => {
|
||||
logger.info("Done.");
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import db from "external/mongo/db";
|
||||
import CreateLogCtx from "lib/logger/logger";
|
||||
import { TachiConfig } from "lib/setup/config";
|
||||
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
for (const game of TachiConfig.GAMES) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const res = await db.charts[game].update(
|
||||
{ tierlistInfo: { $exists: false } },
|
||||
{ $set: { tierlistInfo: {} } },
|
||||
{ multi: true }
|
||||
);
|
||||
|
||||
logger.info(`Added tierlistInfo to ${game}'s charts.`, { res });
|
||||
}
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import db from "external/mongo/db";
|
||||
import { rootLogger } from "lib/logger/logger";
|
||||
import { ChartDocument, Difficulties } from "tachi-common";
|
||||
import { FindIIDXChartOnInGameID } from "utils/queries/charts";
|
||||
|
||||
async function ConvertChart(chart: ChartDocument<"iidx:SP" | "iidx:DP">) {
|
||||
// We need to get the related official song. The best way to do this is
|
||||
// probably with song IDs.
|
||||
|
||||
const originalChart = await FindIIDXChartOnInGameID(
|
||||
chart.data.inGameID,
|
||||
chart.playtype,
|
||||
chart.difficulty
|
||||
);
|
||||
|
||||
if (!originalChart) {
|
||||
rootLogger.error(`Can't find original chart.`, { chart });
|
||||
throw new Error(`Can't find original chart.`);
|
||||
}
|
||||
|
||||
return {
|
||||
difficulty: `${chart.data["2dxtraSet"]!} ${chart.difficulty}` as Difficulties[
|
||||
| "iidx:SP"
|
||||
| "iidx:DP"],
|
||||
songID: originalChart!.songID,
|
||||
};
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
(async () => {
|
||||
const modCharts = (await db.charts.iidx.find({
|
||||
"data.2dxtraSet": { $ne: null },
|
||||
difficulty: { $in: ["BEGINNER", "NORMAL", "HYPER", "ANOTHER", "LEGGENDARIA"] },
|
||||
})) as ChartDocument<"iidx:SP" | "iidx:DP">[];
|
||||
|
||||
rootLogger.info(`Modifying ${modCharts.length} charts.`);
|
||||
|
||||
for (const chart of modCharts) {
|
||||
const setDoc = await ConvertChart(chart);
|
||||
|
||||
await db.charts.iidx.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
{ $set: setDoc }
|
||||
);
|
||||
|
||||
// We're also going to have to port over old scores.
|
||||
|
||||
await db.scores.update(
|
||||
{
|
||||
chartID: chart.chartID,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
songID: setDoc.songID,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
rootLogger.info(`Removing ${modCharts.length} songs.`);
|
||||
|
||||
await db.songs.iidx.remove({
|
||||
id: { $in: modCharts.map((e) => e.songID) },
|
||||
});
|
||||
|
||||
rootLogger.info(`Done.`);
|
||||
})();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Command } from "commander";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const program = new Command();
|
||||
program.option("-d, --dir <dir>");
|
||||
|
||||
program.parse(process.argv);
|
||||
const options = program.opts();
|
||||
|
||||
if (!options.dir) {
|
||||
throw new Error(`No dir provided, yet one is necessary.`);
|
||||
}
|
||||
|
||||
// @warn THIS SCRIPT IS OUTDATED!
|
||||
// Tachi now uses /users/${userID}/banner instead of
|
||||
// /users/banner/${userID}
|
||||
|
||||
fs.mkdirSync("users/banner", { recursive: true });
|
||||
fs.mkdirSync("users/pfp", { recursive: true });
|
||||
|
||||
for (const file of fs.readdirSync(options.dir)) {
|
||||
const dfile = path.join(options.dir, file);
|
||||
if (file.endsWith("-banner.png")) {
|
||||
fs.copyFileSync(dfile, `users/banner/${file.split("-banner.png")[0]}`);
|
||||
} else {
|
||||
fs.copyFileSync(dfile, `users/pfp/${file.split("-pfp.png")[0]}`);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/* eslint-disable no-console */
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
console.log(bcrypt.hashSync("password", 12));
|
||||
@@ -0,0 +1,44 @@
|
||||
import db from "external/mongo/db";
|
||||
|
||||
(async () => {
|
||||
const folders = await db.folders.find({}, { sort: { tableIndex: 1 } });
|
||||
|
||||
for (const folder of folders) {
|
||||
const { game, playtype } = folder;
|
||||
|
||||
const tableID = ((folder as any).table as string)
|
||||
.toLowerCase()
|
||||
.replace(/ /gu, "_")
|
||||
.replace(/\(/gu, "")
|
||||
.replace(/\)/gu, "");
|
||||
|
||||
console.log(game, playtype, tableID, folder.title);
|
||||
|
||||
const existingTable = await db.tables.findOne({ game, playtype, tableID });
|
||||
|
||||
if (!existingTable) {
|
||||
await db.tables.insert({
|
||||
tableID,
|
||||
game,
|
||||
playtype,
|
||||
description: "todo",
|
||||
folders: [folder.folderID],
|
||||
// @ts-expect-error yea
|
||||
title: folder.table,
|
||||
});
|
||||
} else {
|
||||
await db.tables.update(
|
||||
{
|
||||
game,
|
||||
playtype,
|
||||
tableID,
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
folders: folder.folderID,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user