mirror of
https://github.com/zkldi/Tachi.git
synced 2026-09-22 23:18:05 +03:00
@@ -471,7 +471,6 @@
|
||||
"@types/react-router-dom": "5.1.7",
|
||||
"@types/sql.js": "^1.4.11",
|
||||
"@types/supertest": "2.0.11",
|
||||
"@types/tap": "^15.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.27.0",
|
||||
"@typescript-eslint/parser": "^4.27.0",
|
||||
"@typescript-eslint/utils": "^8.24.1",
|
||||
@@ -528,12 +527,10 @@
|
||||
"node-fetch": "2.6.7",
|
||||
"node-html-parser": "^5.3.3",
|
||||
"node-mocks-http": "1.11.0",
|
||||
"node-sass": "9.0.0",
|
||||
"nodemailer": "7.0.11",
|
||||
"pg": "^8.13.3",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"prettier": "3.2.5",
|
||||
"pretty-bytes": "5.6.0",
|
||||
"prom-client": "15.1.3",
|
||||
"prudence": "0.10.0",
|
||||
@@ -553,7 +550,6 @@
|
||||
"sql.js": "^1.14.1",
|
||||
"supertest": "6.2.2",
|
||||
"sync-fetch": "^0.3.1",
|
||||
"tap": "^15.1.6",
|
||||
"typescript": "^4.3.4",
|
||||
"typescript-eslint": "^8.24.1",
|
||||
"vite": "^8.0.0",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[private]
|
||||
interactive:
|
||||
-@cd ../ && just
|
||||
|
||||
# Run just the docs on http://localhost:3001.
|
||||
start:
|
||||
mkdocs serve -a 0.0.0.0:3001
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 zkldi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,17 +0,0 @@
|
||||
# Tachi-Docs
|
||||
|
||||
This is the documentation for Tachi.
|
||||
|
||||
Our docs are hosted under [https://docs.tachi.ac](https://docs.tachi.ac)
|
||||
|
||||
# Setup
|
||||
|
||||
If you're in a horribly hellish python environment like me (Sorry Sera, you know its true) make sure you use `pip3`
|
||||
|
||||
- `pip install mkdocs`
|
||||
- `pip install mkdocs-material`
|
||||
- `mkdocs serve`
|
||||
|
||||
# Contributing
|
||||
|
||||
Fork the repository and PR your changes to `main`.
|
||||
@@ -1,61 +0,0 @@
|
||||
# API Authorisation
|
||||
|
||||
Certain endpoints on the Tachi API require permissions.
|
||||
This page covers how to present your permissions to the server, and what those permissions are.
|
||||
|
||||
*****
|
||||
|
||||
## Authorising Requests
|
||||
|
||||
There are two ways to authorise a request. The first one involves API keys.
|
||||
|
||||
### Token Authentication
|
||||
|
||||
To use authentication with a request, you should set a HTTP header of:
|
||||
|
||||
```
|
||||
Authorization: Bearer API_KEY
|
||||
```
|
||||
|
||||
Where API_KEY is the API key you wish to use.
|
||||
|
||||
### Self-Key Authentication
|
||||
|
||||
The other way to authorise a request is with your session cookie. This **MUST NOT** be used by code, and is instead a way for logged-in users to interact with the API as themselves.
|
||||
|
||||
To use authentication in this way, simply make a request with your `Kamaitachi_SESSION` or
|
||||
`Bokutachi_SESSION` cookie.
|
||||
|
||||
The reason for this second authentication method is so that, when a user logs in, they can use
|
||||
the cookie they were set to also interact with the API.
|
||||
|
||||
This type of authentication is referred to as "Self-Key" or "Session-Key" authentication, and it grants special
|
||||
permissions over API Tokens, such as being able to change your password.
|
||||
|
||||
## Getting Tokens
|
||||
|
||||
You should make a Tachi API Client. With that, you can use [our OAuth2 Flow](../codebase/infrastructure/oauth2.md),
|
||||
or our [Client File Flow](../codebase/infrastructure/file-flow.md).
|
||||
|
||||
## Permissions
|
||||
|
||||
An API key does not implicitly have permission to do anything on a users behalf for security reasons.
|
||||
Some endpoints require specific permissions, such as a `score_submit` permission for submitting scores.
|
||||
|
||||
!!! warning
|
||||
API keys **can not** have their permissions altered once set, a new key must be generated.
|
||||
|
||||
!!! info
|
||||
Cookie-based authentication always has *all* permissions for the user.
|
||||
|
||||
### Table Of Permissions
|
||||
|
||||
The table of permissions is as follows.
|
||||
|
||||
| Permission | Description |
|
||||
| :: | :: |
|
||||
| `submit_score` | Perform requests that could submit scores for the user. |
|
||||
| `customise_profile` | Perform requests that could modify user info, like their status or about me. |
|
||||
| `customise_session` | Perform requests that could modify the users sessions, such as changing their names. |
|
||||
| `customise_score` | Perform requests that could modify a users scores, such as adding a comment. |
|
||||
| `delete_score` | Perform requests that could delete scores for that user. |
|
||||
@@ -1,101 +0,0 @@
|
||||
# API Overview
|
||||
|
||||
Tachi exposes a public API which allows programs to interact with Tachi.
|
||||
|
||||
This means you could make your own applications that work off of Tachi's
|
||||
datasets.
|
||||
|
||||
!!! warning
|
||||
This documentation assumes some basic programming knowledge, such as how to make
|
||||
HTTP requests, and how to parse JSON.
|
||||
|
||||
Depending on what variant of Tachi you want to interact with, the API is hosted
|
||||
on `https://kamai.tachi.ac/api/v1` or `https://boku.tachi.ac/api/v1`.
|
||||
|
||||
!!! note
|
||||
Some API endpoints are only available on Kamaitachi or Bokutachi. If an
|
||||
endpoint has this restriction, it will be documented on that endpoints'
|
||||
page.
|
||||
|
||||
---
|
||||
|
||||
## Abuse
|
||||
|
||||
The Tachi API is provided under the assumption that it will be used to make
|
||||
cool things, and used responsibly.
|
||||
|
||||
Abuse of this API will result in your ability to use it being banned.
|
||||
|
||||
The API has a rate limit of 500 requests every minute. This is a very
|
||||
generous rate limit, and you should not even be close to hitting it.
|
||||
|
||||
If you are in a scenario where you might be hitting even 100 requests a minute consistently,
|
||||
send an email to `zk@tachi.ac` and let me know what you're up to. Otherwise, I might revoke your tokens for abuse.
|
||||
|
||||
## License
|
||||
|
||||
[Tachi-Server](https://github.com/zkldi/Tachi/tree/main/server) (Where the API is written) is licensed under the [AGPLv3](https://www.gnu.org/licenses/agpl-3.0.en.html).
|
||||
|
||||
To quote GitHub:
|
||||
|
||||
!!! quote
|
||||
Permissions of this strongest copyleft license are conditioned on making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights. When a modified version is used to provide a service over a network, the complete source code of the modified version must be made available.
|
||||
|
||||
This is not legal advice.
|
||||
|
||||
## Requests
|
||||
|
||||
Unless otherwise mentioned, all non-GET request properties should be sent to the API in `application/json` form.
|
||||
|
||||
GET request properties should be sent to the API in the Query String.
|
||||
|
||||
Your authentication tokens should be sent in the HTTP `Authorization` header, using `Bearer token` form.
|
||||
|
||||
You should provide this token for every endpoint. Even though not all endpoints
|
||||
require authentication tokens, they are still used for API analytics.
|
||||
|
||||
## Response
|
||||
|
||||
Unless otherwise mentioned, all responses to the API are in `application/json` form.
|
||||
|
||||
The API has two schemas for JSON responses.
|
||||
|
||||
### Success Response
|
||||
|
||||
As the name implies, the Success Response is returned on a successful request.
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------: | :----------------: | :-------------------------------------------------------------------------------------------: |
|
||||
| `success` | true | Always true for a successful response. |
|
||||
| `description` | String | Information about what happened with the request. |
|
||||
| `body` | Endpoint Dependent | Any data that the endpoint needs to return, such as a user's document from a profile request. |
|
||||
|
||||
The HTTP Status Code for any Success Response will always be of 2XX form.
|
||||
|
||||
### Failed Response
|
||||
|
||||
As the name implies, the Failed Response is returned when a request fails.
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------: | :----: | :-------------------------------------------------: |
|
||||
| `success` | false | Always false for a failed response. |
|
||||
| `description` | String | Information about what went wrong with the request. |
|
||||
|
||||
The HTTP Status Code for any Failed Response will always be of either 4XX or 5XX form.
|
||||
|
||||
!!! warning
|
||||
The `description` property is **NOT** intended for program usage. You should **NEVER** depend
|
||||
on the output of `description`, as it may be changed at any time for any reason.
|
||||
|
||||
!!! note
|
||||
Any API request can fail for any reason. You should always account for the
|
||||
case where the request fails.
|
||||
|
||||
## Footnote
|
||||
|
||||
That should be everything. If you have any questions about the API, you can
|
||||
contact me at `zk@tachi.ac`. You can also write an issue on the
|
||||
[Issue Tracker](https://github.com/zkldi/Tachi). I'll get around to either.
|
||||
|
||||
It's entirely possible that I might've made a typo or wrote a poor explaination
|
||||
of something, so please reach out!
|
||||
@@ -1,224 +0,0 @@
|
||||
# Admin Endpoints
|
||||
|
||||
These endpoints are for adminstrator use. As such, they all
|
||||
require an `authLevel` of at least 3. For more information, see the [UserDocument](../../schemas/user.md).
|
||||
|
||||
*****
|
||||
|
||||
## Change Server Log Level
|
||||
|
||||
`POST /api/v1/admin/change-log-level`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `logLevel` | "crit" \| "severe" \| "warn" \| "info" \| "verbose" \| "debug" | The log level to change to. |
|
||||
| `duration` | Number, Optional | How long to keep this change for in minutes. If not set, defaults to 60 minutes. |
|
||||
| `noReset` | Boolean, Optional | If true, do not ever reset this log level change. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/admin/change-log-level
|
||||
|
||||
{
|
||||
duration: 5,
|
||||
logLevel: "verbose"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
*****
|
||||
|
||||
## Delete any Score
|
||||
|
||||
This performs all the necessary checks to remove a score document aswell.
|
||||
|
||||
`POST /api/v1/admin/delete-score`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `scoreID` | String | The scoreID to delete. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
*****
|
||||
|
||||
## Re-run PB processing for every scored user+chart (synchronous).
|
||||
|
||||
`POST /api/v1/admin/recalc-pbs`
|
||||
|
||||
Inserts every distinct `(user_id, chart_id)` from the **`score`** table into **`pb_dirty`**, then **drains** `pb_dirty` and downstream **`session_dirty`** / **`game_profile_dirty`** queues until nothing remains (same batching as the background worker, but the HTTP request waits until idle). Intended when PBs may be out of sync (e.g. after a bad migration). There is **no request body** and no filter-always all distinct pairs that appear on scores.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
None (send `{}` if your client requires a body).
|
||||
|
||||
### Response
|
||||
|
||||
Empty object (standard success wrapper with `body`).
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /api/v1/admin/recalc-pbs
|
||||
```
|
||||
|
||||
```js
|
||||
{}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Destroy a users GPT Profile and forces a leaderboard recalc.
|
||||
|
||||
`POST /api/v1/admin/destroy-ugpt`
|
||||
|
||||
!!! warning
|
||||
This is intended to completely remove a users GPT profile.
|
||||
You should use this **only** if a user has irrevocably screwed
|
||||
their account. Preferably early on!
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `userID` | Integer | The user part of the UGPT. |
|
||||
| `game` | Game | The game part of the UGPT. |
|
||||
| `playtype` | Playtype | The PT part of the UGPT. Must be for the above game. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/admin/destroy-ugpt
|
||||
```
|
||||
|
||||
```js
|
||||
{
|
||||
"userID": 1,
|
||||
"game": "iidx",
|
||||
"playtype": "DP"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
*****
|
||||
|
||||
## Destroy a chart and all of its scores.
|
||||
|
||||
`POST /api/v1/admin/destroy-chart`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `chartID` | String | The chartID you wish to destroy. |
|
||||
| `game` | Game | The game this chart belongs to (Necessary for lookups). |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/admin/destroy-chart
|
||||
```
|
||||
```js
|
||||
{
|
||||
"chartID": "SomeChartID",
|
||||
"game": "iidx"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
*****
|
||||
|
||||
## Re-derive all scores site-wide (synchronous).
|
||||
|
||||
`POST /api/v1/admin/recalc`
|
||||
|
||||
Enqueues **every chart** into **`score_rederive`**, then **drains** `score_rederive` and downstream **`pb_dirty`**, **`session_dirty`**, and **`game_profile_dirty`** queues until nothing remains (the request waits until idle). Re-runs `scoreDeriver` and `scoreCalcs` for every score. There is **no request body** and no filter-always all charts.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Admin
|
||||
|
||||
### Parameters
|
||||
|
||||
None (send an empty JSON object `{}` if your client requires a body).
|
||||
|
||||
### Response
|
||||
|
||||
Empty object (standard success wrapper with `body`).
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /api/v1/admin/recalc
|
||||
```
|
||||
|
||||
```js
|
||||
{}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{}
|
||||
```
|
||||
@@ -1,92 +0,0 @@
|
||||
# API Token Management
|
||||
|
||||
These endpoints relate to managing a users created API Tokens.
|
||||
These tokens are likely to be generated from an OAuth2 integration, but in the future users may be able to create their own API Keys manually.
|
||||
|
||||
!!! note
|
||||
All of the below endpoints require [Self Key](../auth.md) level authentication. You cannot interact with these endpoints with Bearer auth.
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve all API Tokens
|
||||
|
||||
`GET /api/v1/users/:userID/api-tokens`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | APIKeyDocument | An array of APIKeyDocuments that belong to this user. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/1/api-tokens
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
[{
|
||||
"identifier": "Fervidex Token",
|
||||
"token": "foobar",
|
||||
"permissions": {"submit_score": true},
|
||||
"userID": 1,
|
||||
"fromAPIClient": "FERVIDEX_OA2_CLIENT_ID"
|
||||
}]
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Delete a specific token.
|
||||
|
||||
`DELETE /api/v1/users/:userID/api-token/:token`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
DELETE /api/v1/users/1/api-token/foobar
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Create an API Token
|
||||
|
||||
`POST /api/v1/users/:userID/api-tokens/create`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `permissions` | Array<String> | An array of permissions you wish the key to have. |
|
||||
| `clientID` | String | Alternatively, you can pass the clientID of an OAuth2 Client. This will select permissions based on what that client wants. |
|
||||
| `identifier` | String | A humanised identifier for what the string was from. Necessary if using `permissions`. Filled out for you if using `clientID`.
|
||||
|
||||
!!! info
|
||||
If using ClientID for permissions, the clientID will be pinned to the
|
||||
token you've created as `fromAPIClient`. You can only have one
|
||||
API Token per OAuth2 client, and will get a 409 if you repeat the request.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | APITokenDocument | The API Token you created. |
|
||||
@@ -1,283 +0,0 @@
|
||||
# Internal Authentication
|
||||
|
||||
These endpoints relate to internal authentication methods. Read the warning below.
|
||||
|
||||
!!! danger
|
||||
This is **NOT** for external use. You should **NEVER**
|
||||
be requesting a username and password from a user.
|
||||
|
||||
Furthermore, interacting with this programmatically
|
||||
is near-impossible because you need to complete a CAPTCHA.
|
||||
|
||||
Nevertheless, This is documented for completeness' sake.
|
||||
|
||||
!!! warning
|
||||
All of these endpoints are aggressively rate limited. If you fire a bot at this, you will probably get
|
||||
your IP blacklisted. Don't do that.
|
||||
|
||||
---
|
||||
|
||||
## Login with username and password.
|
||||
|
||||
`POST /api/v1/auth/login`
|
||||
|
||||
Logs a user in and returns a session cookie.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :----: | :---------------------------------------------------------------------------------------: |
|
||||
| `username` | String | The user's username. This is compared case-insensitively. |
|
||||
| `!password` | String | The user's password. |
|
||||
| `captcha` | String | The hCaptcha response token from the client widget; the server verifies it with hCaptcha. |
|
||||
|
||||
!!! info
|
||||
The `!` prefix is special in that anything with it is assumed to be private and is **always**
|
||||
ignored by our request logger.
|
||||
|
||||
Without it, we would log passwords!
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----: | :--------------------------------------: |
|
||||
| `userID` | Integer | The ID of the user you authenticated as. |
|
||||
|
||||
| HTTP Header | Description |
|
||||
| :----------: | :--------------------------------------------------: |
|
||||
| `Set-Cookie` | Contains a session cookie for future authentication. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /api/v1/auth/login
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "zkldi",
|
||||
"!password": "my_password",
|
||||
"captcha": "herebedragons"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"userID": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Register a new account.
|
||||
|
||||
`POST /api/v1/auth/register`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :----------------------------: | :------------------------------------: | :------------------------------------------------------------------------------------------------------------------: |
|
||||
| `username` | String | A string between 3 and 20 characters. The first character must be A-Z, _ or -. The other 19 may be A-Z, 0-9, _ or -. |
|
||||
| `!password` | String | An 8 character or longer string. |
|
||||
| `!email` | String | Valid email. The `!` prefix keeps this field out of server request logs. |
|
||||
| `inviteCode` (Kamaitachi Only) | String (Undefined/Unused on Bokutachi) | If on Kamaitachi, this is the user's invitation code. |
|
||||
| `captcha` | String | The hCaptcha response token from the client widget; the server verifies it with hCaptcha. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----------------------------------: | :-------------------------------------: |
|
||||
| `<body>` | [UserDocument](../../schemas/user.md) | The newly-created user's User Document. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /api/v1/auth/register
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "newGuy",
|
||||
"!password": "my_password",
|
||||
"captcha": "herebedragons",
|
||||
"!email": "test@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"username": "newGuy",
|
||||
"usernameLowercase": "newguy",
|
||||
"socialMedia": {
|
||||
"discord": null,
|
||||
"twitter": null,
|
||||
"github": null,
|
||||
"steam": null,
|
||||
"youtube": null,
|
||||
"twitch": null
|
||||
},
|
||||
"joinDate": 1639628634978,
|
||||
"lastSeen": 1639628634978,
|
||||
"about": "I'm a fairly nondescript person.",
|
||||
"status": null,
|
||||
"customPfpLocation": null,
|
||||
"customBannerLocation": null,
|
||||
"clan": null,
|
||||
"badges": [],
|
||||
"authLevel": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verify an email from the code that was sent to it.
|
||||
|
||||
`POST /api/v1/auth/verify-email`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :------------------------------------------: |
|
||||
| `code` | String | The Code that was sent to the users mailbox. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
{
|
||||
"code": "abcdef1234567890"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
---
|
||||
|
||||
## Resend a verification email to the requesting user's email address.
|
||||
|
||||
`POST /api/v1/auth/resend-verify-email`
|
||||
|
||||
**Requires Self-Key Level Auth**
|
||||
|
||||
### Parameters
|
||||
|
||||
None, This endpoint reads the requesting user from their session.
|
||||
|
||||
### Response
|
||||
|
||||
Empty object.
|
||||
|
||||
### Example
|
||||
|
||||
N/A
|
||||
|
||||
---
|
||||
|
||||
## Log Out.
|
||||
|
||||
`POST /api/v1/auth/logout`
|
||||
|
||||
Destroys the current session associated with this cookie.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
POST /api/v1/auth/logout
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Nothing.
|
||||
|
||||
---
|
||||
|
||||
## Create a password reset code and send it to the provided email.
|
||||
|
||||
`POST /api/v1/auth/forgot-password`
|
||||
|
||||
!!! note
|
||||
This endpoint sends the password reset code pretty-printed to the email, and is **NOT**
|
||||
returned as part of the HTTP request.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :-------------------------------------------------------------------------------------------------------------: |
|
||||
| `!email` | String | A user's email. If the email does not correspond to any accounts, 202 is returned anyway as a security measure. Request logs omit this field. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object. The endpoint immediately returns 202 to avoid giving away information about registered emails.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```js
|
||||
{
|
||||
"!email": "zkldiv@gmail.com"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Although the request body returns nothing, `zkldiv@gmail.com` will have recieved an email with
|
||||
a URL containing the password reset code.
|
||||
|
||||
---
|
||||
|
||||
## Reset a user's password with a password reset code.
|
||||
|
||||
`POST /api/v1/auth/reset-password`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :----: | :----------------------------------------------------------------: |
|
||||
| `code` | String | A password reset code. This is provided in a password reset email. |
|
||||
| `!password` | String | The password to change to. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```js
|
||||
{
|
||||
"code": "1234567890abcdef",
|
||||
"!password": "zkldi_so_cool",
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
Empty Object.
|
||||
@@ -1,254 +0,0 @@
|
||||
# API Client Management
|
||||
|
||||
These endpoints relate to managing your Tachi API clients, such as creating new ones or deleting them.
|
||||
|
||||
For a detailed explaination on how to use the OAuth2 flow, you can check [Using OAuth2 With Tachi](../../codebase/infrastructure/oauth2.md).
|
||||
|
||||
*****
|
||||
|
||||
!!! warning
|
||||
All endpoints on this list require [Self Key](./user-integrations.md) level authentication.
|
||||
|
||||
That is to say, it cannot be performed using Bearer tokens, and must be done by the user themselves.
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve all clients you have created.
|
||||
|
||||
`GET /api/v1/clients`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<OAuth2ClientDoc> | All of the OAuth2 clients you have created. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/clients
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
[{
|
||||
"name": "My Client",
|
||||
"clientID": "blah",
|
||||
"clientSecret": "secret!!",
|
||||
"requestPermissions": ["customise_profile"],
|
||||
"author": 1,
|
||||
"redirectUri": "https://example.com/callback",
|
||||
},
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Create a new OAuth2 Client
|
||||
|
||||
`POST /api/v1/clients/create`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `name` | String | A string between 3 and 80 characters. The name for this client. |
|
||||
| `redirectUri` | Optional String (Valid URI) | Must be a HTTP/HTTPS URL, This is where users will be sent to after clicking Yes on the prompt. |
|
||||
| `webhookUri` | Optional String (Valid URI) | Must be a HTTP/HTTPS URL. Registers this URL as a URL that wants webhook events to be sent to it. Read more about webhooks [here](../webhooks/main.md). |
|
||||
| `apiKeyFormat` | Optional String | If present, this sets an expected format for the API Key in [Client File Flow](../../codebase/infrastructure/file-flow.md). Must contain %%TACHI_KEY%%. |
|
||||
| `apiKeyFilename` | Optional String | If present, this sets a filename fir [Client File Flow](../../codebase/infrastructure/file-flow.md). |
|
||||
| `permissions` | Array<Permissions> | An array of permissions this client requests. You can check all permissions [here](../auth.md). |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | OAuth2ClientDoc | The OAuth2Client you just created. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/clients/create
|
||||
|
||||
{
|
||||
"name": "My Client",
|
||||
"redirectUri": "https://example.com/callback",
|
||||
"permissions": ["customise_profile"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Client",
|
||||
"redirectUri": "https://example.com/callback",
|
||||
"webhookUri": null,
|
||||
"requestedPermissions": ["customise_profile"],
|
||||
"clientID": "foobar",
|
||||
"clientSecret": "secret_val",
|
||||
"apiKeyFormat": null,
|
||||
"apiKeyFilename": null
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve information about a client
|
||||
|
||||
`GET /api/v1/clients/:clientID`
|
||||
|
||||
This is used to display information about this client to the user, when they are deciding on whether to authenticate it.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | OAuth2ClientDoc without clientSecret | The client document at this ID. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/clients/some_client_id
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"name": "My Client",
|
||||
"redirectUri": "https://example.com/callback",
|
||||
"webhookUri": null,
|
||||
"requestedPermissions": ["customise_profile"],
|
||||
"clientID": "foobar",
|
||||
"clientSecret": "secret_val",
|
||||
"apiKeyFormat": null,
|
||||
"apiKeyFilename": null
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Modify existing client
|
||||
|
||||
`PATCH /api/v1/clients/:clientID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be the owner of this client.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `name` | String | A string between 3 and 80 characters. |
|
||||
| `webhookUri` | String | A new webhookUri. |
|
||||
| `redirectUri` | String | A new redirectUri. |
|
||||
| `apiKeyFormat` | String | A new apiKeyFormat. |
|
||||
| `apiKeyFilename` | String | A new apiKeyFilename. |
|
||||
|
||||
|
||||
!!! note
|
||||
If you need to change permissions, you must make another client.
|
||||
|
||||
Also, all the above properties are optional. If not present, they will not be changed.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | OAuth2ClientDoc | The new client document with the patched changes applied. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
PATCH /api/v1/clients/some_client_id
|
||||
|
||||
{
|
||||
"name": "new name!"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "new name",
|
||||
"clientID": "some_client_id",
|
||||
// ... same props as in previous responses
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Reset your client's secret.
|
||||
|
||||
`POST /api/v1/clients/:clientID/reset-secret`
|
||||
|
||||
!!! warning
|
||||
This does **NOT** reset api keys created by this client as per OAuth2 spec.
|
||||
|
||||
If you need that functionality, you will need to delete
|
||||
your client.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be the owner of this client.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | OAuth2ClientDoc | The new client document with the new secret. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/clients/some_client_id/reset-secret
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"name": "some client",
|
||||
"clientID": "some_client_id",
|
||||
"clientSecret": "FRESHLY_GENERATED_SECRET!",
|
||||
// ... more props
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Delete your client.
|
||||
|
||||
`DELETE /api/v1/clients/:clientID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be the owner of this client.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
@@ -1,33 +0,0 @@
|
||||
# Config Endpoints
|
||||
|
||||
These endpoints display certain properties about the Tachi [config file](../../codebase/setup/config.md).
|
||||
|
||||
*****
|
||||
|
||||
## Return the value of the BEATORAJA_QUEUE_SIZE
|
||||
|
||||
`GET /api/v1/config/beatoraja-queue-size`
|
||||
|
||||
**Bokutachi Only**
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Integer | The value in `conf.json5` for BEATORAJA_QUEUE_SIZE. |
|
||||
|
||||
|
||||
## Return the value of the USC_QUEUE_SIZE
|
||||
|
||||
`GET /api/v1/config/usc-queue-size`
|
||||
|
||||
**Bokutachi Only**
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Integer | The value in `conf.json5` for USC_QUEUE_SIZE. |
|
||||
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# Example Endpoint
|
||||
|
||||
_Endpoints will be formatted like this. - These are not real endpoints!_
|
||||
|
||||
---
|
||||
|
||||
## Greet a user.
|
||||
|
||||
`GET /api/v1/greet`
|
||||
|
||||
This endpoint greets the user.
|
||||
|
||||
### Permissions
|
||||
|
||||
_If permissions are required, they will be listed here._
|
||||
|
||||
- example_permission
|
||||
|
||||
### Parameters
|
||||
|
||||
_Parameters are required unless explicitly stated to be optional._
|
||||
|
||||
!!! note
|
||||
As mentioned in API Overview, GET parameters are to be sent in the query string,
|
||||
and all other methods are to have their content in the request body as
|
||||
`application/json`.
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-------------------: | :------: | :---------------------------------------: |
|
||||
| `name` | String | The name of the user to greet. |
|
||||
| `birthday` (optional) | Presence | Whether it is the user's birthday or not. |
|
||||
|
||||
Not providing required parameters will result in a 400 error.
|
||||
|
||||
### Response
|
||||
|
||||
_Parameters are always present unless stated to be conditional/optional._
|
||||
|
||||
!!! info
|
||||
The below properties correspond to keys in the `body`
|
||||
property of a request.
|
||||
|
||||
This means that the below table corresponds to
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"description": "Greeted user.",
|
||||
"body": {
|
||||
"greeting": "Hello, zkldi!",
|
||||
"wasBirthday": false,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------: | :-----: | :-----------------------------------------: |
|
||||
| `greeting` | String | A greeting for the user. |
|
||||
| `wasBirthday` | Boolean | Whether today is the users birthday or not. |
|
||||
|
||||
!!! info
|
||||
Since the above table corresponds to keys in the `body`
|
||||
property of a request, the special property name
|
||||
`<body>` refers to the body itself.
|
||||
|
||||
For example:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | String | The greeting. |
|
||||
|
||||
Corresponds to:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"description": "Greeted user.",
|
||||
"body": "Hello, zkldi
|
||||
}
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /greet?name=zkldi
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"greeting": "Hello, zkldi
|
||||
"wasBirthday": false
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
The example response is implicitly the `body` key of the API response.
|
||||
That is to say that, the real response for this request is:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"description": "Greeted user.",
|
||||
"body": {
|
||||
"greeting": "Hello, zkldi
|
||||
"wasBirthday": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is omitted, because it's redundant all of the time. -- That is to say,
|
||||
You should never depend on parsing the content of `description`.
|
||||
@@ -1,78 +0,0 @@
|
||||
# Game Endpoints
|
||||
|
||||
These endpoints cover all games and specific games. For
|
||||
specific playtypes, see [Game:Playtype Endpoints](./gpt.md).
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve all supported games.
|
||||
|
||||
`GET /api/v1/games`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `supportedGames` | String[] | The list of games this service supports. |
|
||||
| `configs` | Record<Game, [GameConfig](../../codebase/implementation-details/game-configuration.md)> | Contains a mapping of every supported game to its configuration. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/games
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"supportedGames": ["iidx", "bms"],
|
||||
"configs": {
|
||||
"iidx": {
|
||||
"name": "beatmania IIDX",
|
||||
// ...
|
||||
},
|
||||
"bms": {
|
||||
"name": "BMS",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve a specific games' configuration.
|
||||
|
||||
`GET /api/v1/games/:game`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | GameConfig | The configuration for this game. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/games/iidx
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "beatmania IIDX",
|
||||
"validPlaytypes": ["SP", "DP"],
|
||||
}
|
||||
```
|
||||
@@ -1,369 +0,0 @@
|
||||
# GPT-Target Endpoints
|
||||
|
||||
These endpoints deal with [targets](../../api/terminology.md) for a Game + Playtype. These are things like searching goals or quests, or retrieving information about a specific ID.
|
||||
|
||||
For user-specific target endpoints, such as subscriptions, see [UGPT-Target Endpoints](./ugpt-targets.md).
|
||||
|
||||
---
|
||||
|
||||
## Retrieve this game's recently achieved targets
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/recently-achieved`
|
||||
|
||||
!!! info
|
||||
This endpoint returns the 100 most recently achieved goal subscriptions, and 50 most recently achieved quest subscriptions.
|
||||
|
||||
A target is not considered recently achieved if it was [instantly achieved](../../codebase/implementation-details/goals-quests.md#instant-indirect-achievements).
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :---------------------------: | :-------------------------------------------------------: |
|
||||
| `goals` | Array<GoalDocument> | The goal documents that were recently achieved. |
|
||||
| `quests` | Array<QuestDocument> | The quest documents that were recently achieved. |
|
||||
| `goalSubs` | Array<GoalSubDocument> | User subscriptions to goals that were recently achieved. |
|
||||
| `questSubs` | Array<QuestSubDocument> | User subscriptions to quests that were recently achieved. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/targets/recently-achieved
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
goals: [{
|
||||
name: "HARD CLEAR 5.1.1 Another",
|
||||
goalID: "foo"
|
||||
// ... other goal props
|
||||
}],
|
||||
quests: [{
|
||||
name: "Go Beyond Diamond 1",
|
||||
questID: "bar",
|
||||
// ... other quest props
|
||||
}],
|
||||
goalSubs: [{
|
||||
userID: 1,
|
||||
goalID: "foo",
|
||||
achieved: true,
|
||||
// ... other goalsub props
|
||||
}],
|
||||
questSubs: [{
|
||||
userID: 3,
|
||||
questID: "bar",
|
||||
achieved: true,
|
||||
// ... other quest sub props
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve this game's recently interacted-with targets
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/recently-raised`
|
||||
|
||||
!!! info
|
||||
This endpoint returns the 100 most recently interacted-with goal subscriptions, and 50 most recently interacted-with quest subscriptions.
|
||||
|
||||
A recently interacted with target subscription is one where `progress` or `outOf` has changed recently.
|
||||
|
||||
!!! warn
|
||||
This endpoint excludes achieved targets -- targets still get interacted with when achieved, which means a user with a lot of targets will just flood this endpoint with redundant updates on larger imports.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :---------------------------: | :--------------------------------------------------------------: |
|
||||
| `goals` | Array<GoalDocument> | The goal documents that were recently achieved. |
|
||||
| `quests` | Array<QuestDocument> | The quest documents that were recently achieved. |
|
||||
| `goalSubs` | Array<GoalSubDocument> | User subscriptions to goals that were recently interacted with. |
|
||||
| `questSubs` | Array<QuestSubDocument> | User subscriptions to quests that were recently interacted with. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/targets/recently-raised
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
goals: [{
|
||||
name: "HARD CLEAR 5.1.1 Another",
|
||||
goalID: "foo"
|
||||
// ... other goal props
|
||||
}],
|
||||
quests: [{
|
||||
name: "Go Beyond Diamond 1",
|
||||
questID: "bar",
|
||||
// ... other quest props
|
||||
}],
|
||||
goalSubs: [{
|
||||
userID: 1,
|
||||
goalID: "foo",
|
||||
achieved: false,
|
||||
lastInteraction: 1649438990417,
|
||||
// ... other goalsub props
|
||||
}],
|
||||
questSubs: [{
|
||||
userID: 3,
|
||||
questID: "bar",
|
||||
achieved: false,
|
||||
lastInteraction: 1649438990415,
|
||||
// ... other quest sub props
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get the most popular goals for this GPT.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/goals/popular`
|
||||
|
||||
### Parameters
|
||||
|
||||
N/A
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------: | :------------------------------------------------------------------------------------------------------------------: |
|
||||
| `<body>` | Array<GoalDocument & `__subscriptions` > | An array of the 100 most popular goals for this GPT, where `__subscriptions` is how many subscriptions the goal has. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/:gameGroup/:playtype/targets/goals/popular
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: "HARD CLEAR foo",
|
||||
// ...
|
||||
},
|
||||
{
|
||||
name: "AAA foo",
|
||||
// ...
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve information about a specific goal and its subscribers.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/goals/:goalID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------------: | :--------------------------: | :---------------------------------------: |
|
||||
| `goal` | GoalDocument | The goal document at this ID. |
|
||||
| `goalSubs` | Array<GoalSubDocument> | All of the subscriptions to this goal. |
|
||||
| `users` | Array<UserDocument> | All of the users subscribed to this goal. |
|
||||
| `parentQuests` | Array<QuestDocument> | All of the quests that include this goal. |
|
||||
|
||||
---
|
||||
|
||||
## Evaluate a goal upon a user.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/goals/:goalID/evaluate-for`
|
||||
|
||||
!!! note
|
||||
This endpoint is notably in a bit of a strange position. It can't go under UGPT because
|
||||
`UGPT/goals/:goalID` is for goal subscriptions, and overloading the endpoint to be
|
||||
something like "return the goal subscription or evaluate it if doesn't exist" is ugly.
|
||||
|
||||
As such, it ends up here, but is generally a bit awkward.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :---------------------------------: |
|
||||
| `userID` | String | The user to evaluate this goal for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------------------: | :----------: | :-----------------------------------------------------: |
|
||||
| `goal` | GoalDocument | The goal document that was evaluated. |
|
||||
| `user` | UserDocument | The user that this goal was evaluated for. |
|
||||
| `results.achieved` | Boolean | Whether this user would have this goal achieved or not. |
|
||||
| `results.progress` | Integer | What this user's progress would be on this goal. |
|
||||
| `results.progressHuman` | String | A user friendly format for this user's goal progress. |
|
||||
| `results.outOf` | Integer | What this goal was out of. |
|
||||
| `results.outOfHuman` | String | A user friendly format for what this goal was out of. |
|
||||
|
||||
!!! info
|
||||
For more info on `progress`/`outOf`, see [Goals](../../codebase/implementation-details/goals-quests.md#evaluating-a-users-progress).
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/targets/goals/some_goal_id/evaluate-for?userID=zkldi
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
user: {
|
||||
username: "zkldi
|
||||
id: 1,
|
||||
// ...
|
||||
},
|
||||
goal: {
|
||||
goalID: "some_goal_id",
|
||||
name: "FULL COMBO some chart"
|
||||
// ...
|
||||
},
|
||||
result: {
|
||||
achieved: false,
|
||||
progress: 5,
|
||||
progressHuman: "EX HARD CLEAR",
|
||||
outOf: 6,
|
||||
outOfHuman: "FULL COMBO"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Search quests for this GPT.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/quests`
|
||||
|
||||
!!! note
|
||||
You might notice that there's no equivalent endpoint for goals.
|
||||
|
||||
Searching goals for a GPT isn't very interesting, since they can be created by anyone at any time. The only reason goals are stored separately to subscriptions are for deduplication purposes and quests.
|
||||
|
||||
As such, searching goals for a GPT is pointless, since technically it should search the set of all possible goals.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :----------------------: |
|
||||
| `search` | String | The query to search for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------: | :--------------------------------------------------: |
|
||||
| `<body>` | Array<QuestDocument> | All of the quests that matched this search criteria. |
|
||||
|
||||
---
|
||||
|
||||
## Retrieve information about a specific quest, and who is subscribed to it.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/quests/:questID`
|
||||
|
||||
### Parameters
|
||||
|
||||
N/A
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :----------------: | :----------------------------: | :-------------------------------------------------: |
|
||||
| `quest` | QuestDocument | The quest with this questID. |
|
||||
| `questSubs` | Array<QuestSubDocument> | All of the subscriptions to this quest. |
|
||||
| `users` | Array<UserDocument> | All of the user's with subscriptions to this quest. |
|
||||
| `goals` | Array<GoalDocument> | All of the goals in this quest. |
|
||||
| `parentQuestlines` | Array<QuestlineDocument> | Any questlines that contain this quest. |
|
||||
|
||||
---
|
||||
|
||||
## Evaluate a quest for a user, even if they aren't subscribed to it.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/quests/:questID/evaluate-for`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :--------------------------------------------: |
|
||||
| `userID` | String | The user you wish to evaluate this quest upon. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------: | :------------------------------: | :-------------------------------------------------------------------------------: |
|
||||
| `goals` | Array<GoalDocument> | All of the goals in this quest. |
|
||||
| `goalResults` | Array<EvaluatedGoalResult> | This user's progress on each individual goal in this quest. |
|
||||
| `achieved` | Boolean | Whether this user has this quest achieved or not. |
|
||||
| `progress` | Integer | How many goals this user has achieved in this quest. |
|
||||
| `outOf` | Integer | How many goals need to be achieved in this quest for it to be marked as achieved. |
|
||||
|
||||
#### EvaluatedGoalResult
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-------------: | :------------: | :--------------------------------------------------------------------------------------: |
|
||||
| `goalID` | String | The goal ID that these results are for. |
|
||||
| `achieved` | Boolean | Whether this goal was achieved or not. |
|
||||
| `progress` | Number \| Null | How much progress this user made on this goal. Null if no progress was made. |
|
||||
| `outOf` | Number | What `progress` needs to be greater than or equal to for this goal to count as achieved. |
|
||||
| `progressHuman` | String | A humanised, pretty-printed progress indicator for this goal. |
|
||||
| `outOfHuman` | String | A humanised, pretty-printed outOf indicator for this goal. |
|
||||
|
||||
---
|
||||
|
||||
## Search Questlines
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/questlines`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :----------------------------------: |
|
||||
| `search` | String | A name of a questline to search for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----------------------------: | :------------------------------------------------------------: |
|
||||
| `<body>` | Array<QuestlineDocument> | An array of QuestlineDocuments, based on the search parameter. |
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a questline with a specific ID.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/targets/questlines/:questlineID`
|
||||
|
||||
### Parameters
|
||||
|
||||
N/A
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :------------------------: | :-------------------------------------------------: |
|
||||
| `questline` | QuestlineDocument | The questline document at this ID. |
|
||||
| `quests` | Array<QuestDocument> | All of the quest documents that belong to this set. |
|
||||
@@ -1,621 +0,0 @@
|
||||
# Game:Playtype Endpoints
|
||||
|
||||
These endpoints are for games + their playtypes.
|
||||
To find out what games are supported by a service
|
||||
programmatically, you should see [Game Endpoints](./games.md).
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Game:Playtype Configuration.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----------: | :----------------------------------------------: |
|
||||
| `config` | GamePTConfig | The configuration file for this game + playtype. |
|
||||
|
||||
!!! warning
|
||||
A GamePTConfig is different to a GameConfig! Read more
|
||||
[here](../../codebase/implementation-details/game-configuration.md).
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"config": {
|
||||
"idString": "iidx:SP",
|
||||
|
||||
"percentMax": 100,
|
||||
|
||||
"defaultScoreRatingAlg": "ktRating",
|
||||
"defaultSessionRatingAlg": "ktRating",
|
||||
"defaultProfileRatingAlg": "ktRating"
|
||||
|
||||
// ... more props - a lot more props
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve the player leaderboard.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/leaderboard`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--------------: | :----: | :----------------------------------------------------------------------------------------: |
|
||||
| `alg` (Optional) | String | If present, specifies an alternative algorithm to sort players on, instead of the default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------: | :------------------------------------------------: | :------------------------------------------------: |
|
||||
| `gameStats` | Array<GameStats> | The sorted statistics for the leaderboards. |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | All of the related users for the above statistics. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/leaderboard
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"gameStats": [
|
||||
{
|
||||
"userID": 1,
|
||||
"ratings": {
|
||||
"ktRating": 4
|
||||
// ...
|
||||
}
|
||||
// ...
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "zkldi"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a song and its charts.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/songs/:songID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :---------------------------------------: | :-----------------------------------------------------------: |
|
||||
| `song` | [SongDocument](../../schemas/song.md) | The requested song document. |
|
||||
| `charts` | [ChartDocument](../../schemas/chart.md)[] | All of the charts that belong to this song for this playtype. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/songs/1
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"song": {
|
||||
"id": 1,
|
||||
"title": "5.1.1."
|
||||
},
|
||||
"charts": [
|
||||
{
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"difficulty": "HYPER"
|
||||
// ...
|
||||
},
|
||||
{
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"difficulty": "ANOTHER"
|
||||
// ...
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get popular charts for this game + playtype.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/charts`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------------: | :----: | :-------------------------: |
|
||||
| `search` (Optional) | String | A song title to search for. |
|
||||
|
||||
!!! note
|
||||
If no search parameter is set, then the most popular
|
||||
100 charts for this game are returned.
|
||||
|
||||
If a search parameter is set, then the most popular
|
||||
charts that match the search criteria will be returned,
|
||||
in that order.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :---------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------: |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md) with `__playcount`> | The chart documents that matched this search, or the most popular 100 charts for this game. |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The associated song documents for the charts. |
|
||||
|
||||
!!! info
|
||||
The `__playcount` property is patched onto the chart
|
||||
documents returned. This indicates the amount of unique
|
||||
players that have played this chart.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/charts?search=AA
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"songs": [
|
||||
{
|
||||
"title": "AA",
|
||||
"id": 3
|
||||
// ...
|
||||
},
|
||||
{
|
||||
"title": "AA -rebuild-",
|
||||
"id": 133
|
||||
// ...
|
||||
}
|
||||
],
|
||||
"charts": [
|
||||
{
|
||||
"songID": 3,
|
||||
"difficulty": "ANOTHER",
|
||||
"__playcount": 1049
|
||||
// ...
|
||||
},
|
||||
{
|
||||
"songID": 133,
|
||||
"difficulty": "ANOTHER",
|
||||
"__playcount": 120
|
||||
}
|
||||
//...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a chart at a specific ID.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/charts/:chartID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-------------------------------------: | :-----------------------------: |
|
||||
| `song` | [SongDocument](../../schemas/song.md) | The parent song for this chart. |
|
||||
| `chart` | [ChartDocument](../../schemas/chart.md) | The requested chart document. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/charts/some_chart_id
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"song": {
|
||||
"id": 123,
|
||||
"title": "BLOCKS"
|
||||
// ...
|
||||
},
|
||||
"chart": {
|
||||
"chartID": "some_chart_id",
|
||||
"songID": 123,
|
||||
"playtype": "SP"
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve leaderboards for this chart.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/charts/:chartID/pbs`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------------------: | :---------------------------------------------------------------------: | :---------: |
|
||||
| `startRanking` (Optional) | Specify a start point to return 100 pbs from. Defaults to 1. Inclusive. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----------------------------: | :---------------------------------: |
|
||||
| `pbs` | Array<PBDocument> | The array of pbs sorted by ranking. |
|
||||
| `users` | The users these PBs belong to. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/charts/some_chart/pbs
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"pbs": [{
|
||||
"chartID": "some_chart",
|
||||
"userID": 1,
|
||||
"rankingData": {
|
||||
"rank": 1,
|
||||
"outOf": 100,
|
||||
},
|
||||
// ...
|
||||
},
|
||||
//...
|
||||
],
|
||||
"users": [{
|
||||
"id": 1,
|
||||
"username": "zkldi
|
||||
// ...
|
||||
},
|
||||
// ...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Search for a user's PB on this chart.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/charts/:chartID/pbs/search`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :-------------------------------------: |
|
||||
| `search` | String | The user whose PB you're searching for. |
|
||||
|
||||
### Response
|
||||
|
||||
Same as `/api/v1/games/:gameGroup/:playtype/charts/:chartID/pbs`.
|
||||
|
||||
### Example
|
||||
|
||||
See Above.
|
||||
|
||||
---
|
||||
|
||||
## Search a GPT's folders.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/folders`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :------------------------------------: |
|
||||
| `search` | String | A string to search for a given folder. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-------------------------: | :-----------------------------------: |
|
||||
| `<body>` | Array<FolderDocument> | The folders that matched this search. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/folders?search=12
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: "beatmania IIDX Level 12",
|
||||
// ...
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve information on a specific folderID
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/folders/:folderID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The related song documents for this folder. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The related chart documents for this folder. |
|
||||
| `folder` | FolderDocument | The folder document at this ID. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/iidx/SP/folders/some_folder_id
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
songs: [{
|
||||
id: 123,
|
||||
title: "Epic Song",
|
||||
artist: "foo bar",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
chartID: "foo_bar",
|
||||
songID: 123,
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}],
|
||||
folder: {
|
||||
name: "beatmania IIDX Level 12",
|
||||
folderID: "some_folder_id",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Return all the tables for this game
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/tables`
|
||||
|
||||
!!! note
|
||||
Unlike the folders endpoint, this one doesn't have a search parameter. This is because we expect
|
||||
the total table count to stay rather small.
|
||||
|
||||
If this changes in the future, this might become a paginated search like endpoint.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------: | :-----------------------: |
|
||||
| `tables` | Array<TableDocument> | Every table for this GPT. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/bms/7K/tables
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
tables: [
|
||||
{
|
||||
name: "Insane",
|
||||
// ...
|
||||
},
|
||||
{
|
||||
name: "Overjoy",
|
||||
// ...
|
||||
},
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve folder documents for a specific table.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/tables/:tableID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-------: | :-------------------------: | :--------------------------------: |
|
||||
| `folders` | Array<FolderDocument> | All of the folders for this table. |
|
||||
| `table` | TableDocument | The table document at this ID. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/bms/7K/tableID/insane
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
folders: [
|
||||
// insane lv1, insane lv2 -- insane lv 25, so on.
|
||||
],
|
||||
table: {
|
||||
tableID: "insane",
|
||||
name: "Insane",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve the PB leaderboard for this Game.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/pb-leaderboard`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------: | :------------------------------------------------------------------------------------: |
|
||||
| `alg` | String | An alternative algorithm to use instead of the GPTs default. |
|
||||
| `limit` | Optional Integer | Optionally, provide a number between 1 and 50 to change the amount of scores returned. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :------------------------------------------: |
|
||||
| `pbs` | Array<PBDocument> | The array of pbs part of the PB leaderboard. |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The array of songs part of the PBs. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts part of the PBs. |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | The array of users part of the PBs. |
|
||||
|
||||
---
|
||||
|
||||
## Get the distribution of players for a provided class.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/player-distribution`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :------------------------------------------------------------------------------------: |
|
||||
| `class` | String | Must be one of the GPTs supported classes, This specifies what distribution to return. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-------------------------------: | :---------------------------------------------------------------------------: |
|
||||
| `<body>` | Record<ClassValue, integer> | Returns a record of the class value against the amount of people who have it. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/games/bms/7K/player-distribution?class=stslDan
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"12": 1,
|
||||
"11": 5,
|
||||
"10": 7,
|
||||
"9": 6,
|
||||
"8": 4,
|
||||
// Note that keys will be empty if nobody has them.
|
||||
"5": 30,
|
||||
// ... so on
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
You can find the humanised conversions for these classes in the gameConfig for this GPT.
|
||||
|
||||
See [tachi/common](https://github.com/zkldi/Tachi/tree/main/common) for more information.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve recent class updates from all users on this game.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/player-distribution`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------: | :-----------------------------------------------------------------------------------------------------: |
|
||||
| `limit` | Optional Integer | Optionally, An integer between 1 and 50 can be provided to limit the amount of returns. Defaults to 10. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-------: | :------------------------------------------------: | :------------------------------------------: |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | Array of the users who achieved the courses. |
|
||||
| `classes` | Array<ClassAchievementDocument> | Data about the recently achieved classes. |
|
||||
|
||||
---
|
||||
|
||||
## Retrieve the most recent highlighted scores for this GPT.
|
||||
|
||||
`GET /api/v1/games/:gameGroup/:playtype/scores/highlighted`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------: | :------------------------------------------------------------------------------------------------: |
|
||||
| `limit` | Optional Integer | Optionally, provide an integer between 1 and 100 to return this amount of scores. Defaults to 100. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :---------------------------: |
|
||||
| `scores` | Array<[ScoreDocument](../../schemas/score.md)> | The highlighted scores. |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | The users who own the scores. |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The songs the scores are on. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The charts the scores are on. |
|
||||
@@ -1,293 +0,0 @@
|
||||
# Import Scores
|
||||
|
||||
*****
|
||||
|
||||
## Import scores from a file.
|
||||
|
||||
```POST /api/v1/import/file```
|
||||
|
||||
Perform a score import that depends on a file, such as a .csv import.
|
||||
|
||||
### Permissions
|
||||
|
||||
- `submit_score`
|
||||
|
||||
### Parameters
|
||||
|
||||
This endpoint expects data in `multipart/form-data` form.
|
||||
This is because we're handling file imports, and is the
|
||||
appropriate way to send files.
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `importType` | String | The ImportType this import is for. This only accepts `file/` [ImportTypes](../../codebase/import/import-types.md). |
|
||||
| `scoreData` | File | The file to import scores from. |
|
||||
|
||||
| HTTP Header | Description |
|
||||
| :: | :: |
|
||||
| `X-User-Intent` (optional) | If this header is "true", the request is assumed to have been sent with 'User Intent'. For more on this, see [Import Types](../../codebase/import/import-types.md) |
|
||||
|
||||
!!! info
|
||||
It's the responsibility of the API user to use the X-User-Intent
|
||||
header properly. It should only be used when the user
|
||||
has explicitly requested this import (i.e. not sent by an automated script).
|
||||
|
||||
### Response
|
||||
|
||||
**Implementation Dependent**.
|
||||
There are two reponse scenarios for this endpoint, depending on whether the server uses an external score processor or not.
|
||||
|
||||
#### External Score Processor Response
|
||||
|
||||
If an external score processor is being used, **202** is returned as a status code, and you are given the following:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `url` | String | A URL to poll for information about this import, while it's being processed. |
|
||||
| `importID` | String | The ID of the import currently being processed. |
|
||||
|
||||
#### Internal Score Processor Response
|
||||
|
||||
If score processing is not done externally, the following is returned:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | ImportDocument | The import document created as a result of this import. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/import/file
|
||||
```
|
||||
|
||||
```
|
||||
// this is not actually a multipart http request example
|
||||
// as those are huge.
|
||||
|
||||
importType="file/eamusement-iidx-csv"
|
||||
scoreData=<file data>
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"importType": "file/eamusement-iidx-csv",
|
||||
"idStrings": [
|
||||
"iidx:SP"
|
||||
],
|
||||
"scoreIDs": [
|
||||
"R6fad5f4947454d8238b45d6a1255d63be4da1130bf91fe9d05df29765a148da8"
|
||||
],
|
||||
"errors": [],
|
||||
"importID": "bea63277b54a5846bab1fd3a6ce54bfd41276857",
|
||||
"timeFinished": 1623352263023,
|
||||
"timeStarted": 1623352260445,
|
||||
"createdSessions": [
|
||||
{
|
||||
"sessionID": "Qb336d6b1cc0930747f161769a13238a41dce0004",
|
||||
"type": "Created"
|
||||
}
|
||||
],
|
||||
"userID": 1,
|
||||
"classDeltas": [],
|
||||
"goalInfo": [],
|
||||
"questInfo": [],
|
||||
"userIntent": false, // if X-User-Intent was set, this would be true.
|
||||
}
|
||||
```
|
||||
|
||||
**OR**
|
||||
|
||||
```js
|
||||
{
|
||||
url: "https://boku.tachi.ac/api/v1/imports/SOME_IMPORT_ID/poll-status",
|
||||
importID: "SOME_IMPORT_ID"
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Synchronise scores up with an API.
|
||||
|
||||
`POST /api/v1/import/from-api`
|
||||
|
||||
This endpoint requests scores from an API to sync up with the requesting user's profile. This can
|
||||
be performed programmatically, as long as the key has `submit_score` permissions.
|
||||
|
||||
!!! note
|
||||
The user must configure integration with these services up.
|
||||
|
||||
### Permissions
|
||||
|
||||
- `submit_score`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `importType` | Any Supported API Import Type | The importType this synchronisation is for. |
|
||||
|
||||
### Response
|
||||
|
||||
**Implementation Dependent**.
|
||||
There are two reponse scenarios for this endpoint, depending on whether the server uses an external score processor or not.
|
||||
|
||||
#### External Score Processor Response
|
||||
|
||||
If an external score processor is being used, **202** is returned as a status code, and you are given the following:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `url` | String | A URL to poll for information about this import, while it's being processed. |
|
||||
| `importID` | String | The ID of the import currently being processed. |
|
||||
|
||||
#### Internal Score Processor Response
|
||||
|
||||
If score processing is not done externally, the following is returned:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | ImportDocument | The import document created as a result of this import. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/import/from-api
|
||||
|
||||
{
|
||||
importType: "api/flo-iidx"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Response
|
||||
|
||||
See previous example.
|
||||
|
||||
*****
|
||||
|
||||
## Force Tachi to reprocess your orphanned scores.
|
||||
|
||||
`POST /api/v1/import/orphans`
|
||||
|
||||
|
||||
This endpoint goes through all of the requesting user's [Orphanned Scores](../../codebase/import/orphans.md) and attempts to find them a parent song & chart.
|
||||
|
||||
!!! note
|
||||
Scores automatically attempt de-orphaning every day at 1 AM UTC on Kamaitachi and Bokutachi,
|
||||
this endpoint just allows you to force a deorphaning, should you wish to.
|
||||
|
||||
### Permissions
|
||||
|
||||
- submit_score
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `processed` | Integer | The amount of orphans processed. |
|
||||
| `failed` | Integer | The amount of orphans that did not find a parent chart, and were kept as orphans. |
|
||||
| `success` | Integer | The amount of orphans that successfully found a parent chart, and were turned into real scores. |
|
||||
| `removed` | Integer | The amount of orphans removed -- They found a parent chart, but were rejected by the converter for being invalid scores, such as having unsupported options or impossible score values. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
N/A
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"processed": 100,
|
||||
"failed": 95,
|
||||
"success": 2,
|
||||
"removed": 3
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## List your orphaned scores
|
||||
|
||||
`GET /api/v1/import/orphans`
|
||||
|
||||
Returns rows from `orphan_score` for the authenticated user (scores that failed with SongOrChartNotFound and were persisted for later matching). Newest rows are returned first.
|
||||
|
||||
### Permissions
|
||||
|
||||
- submit_score
|
||||
|
||||
### Query parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `limit` | Integer (optional) | Page size, 1–100. Default `50`. |
|
||||
| `after` | String (optional) | Keyset cursor: the `rowID` from the last orphan in the previous page. Omit on the first request. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `orphans` | Array | Each object includes `orphanID`, `rowID`, `importType`, `gameGroup`, `timeInserted` (ms), `message` (nullable), `summary` (nullable, best-effort hint from stored data). |
|
||||
| `hasMore` | Boolean | Whether another page exists after this one. |
|
||||
|
||||
*****
|
||||
|
||||
## Get one orphaned score (full payload)
|
||||
|
||||
`GET /api/v1/import/orphans/:orphanID`
|
||||
|
||||
Returns a single `orphan_score` row for the authenticated user, including raw `data` and `context` JSON (for debugging unmatched imports). `404` if the row does not exist or belongs to another user.
|
||||
|
||||
### Permissions
|
||||
|
||||
- submit_score
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `orphanID` | Path | The orphan’s `orphanID` (same as in `GET /import/orphans` or import errors). |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `orphanID` | String | Stable orphan identifier. |
|
||||
| `importType` | String | Import type that produced the orphan. |
|
||||
| `gameGroup` | String | Game group. |
|
||||
| `timeInserted` | Number | Unix time in ms when the row was stored. |
|
||||
| `message` | String or null | Stored error / context message. |
|
||||
| `data` | Object | Raw import datapoint (shape depends on `importType`). |
|
||||
| `context` | Object | Raw import context. |
|
||||
|
||||
*****
|
||||
|
||||
## Delete one orphaned score
|
||||
|
||||
`DELETE /api/v1/import/orphans/:orphanID`
|
||||
|
||||
Removes a single `orphan_score` row **only if** it belongs to the authenticated user. Use this when you no longer want Tachi to keep or retry a given orphan (for example after a bad import).
|
||||
|
||||
### Permissions
|
||||
|
||||
- submit_score
|
||||
|
||||
### Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `orphanID` | Path | The orphan’s `orphanID` (for example from an import error or from `GET /import/orphans`). |
|
||||
|
||||
### Response
|
||||
|
||||
Empty body on success (`success: true`). `404` if no such orphan exists for this user.
|
||||
@@ -1,124 +0,0 @@
|
||||
# Import Document Endpoints
|
||||
|
||||
!!! note
|
||||
This should not be confused with [Import Endpoints](./import.md). Those are for
|
||||
importing scores, whereas these endpoints are for Import Document interaction.
|
||||
|
||||
*****
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve an import document and information about it.
|
||||
|
||||
`GET /api/v1/imports/:importID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `scores` | Array<ScoreDocument> | All of the scores imported from this import. |
|
||||
| `songs` | Array<SongDocument> | All of the songs related to the scores in this import. |
|
||||
| `charts` | Array<ChartDocument> | All of the charts related to the scores in this import. |
|
||||
| `sessions` | Array<SessionDocument> | All of the sessions created as a result of this import. **Note that this does not include sessions modified by this import!** |
|
||||
| `import` | ImportDocument | The Import document you requested. |
|
||||
| `user` | UserDocument | The user document for the person who made this import. |
|
||||
|
||||
*****
|
||||
|
||||
## Revert an import.
|
||||
|
||||
`POST /api/v1/imports/:importID/revert`
|
||||
|
||||
!!! info
|
||||
This endpoint is intended to *undo* a faulty import. For example, if you were using
|
||||
a batch-manual script that somehow went haywire. Normal users should *not* need to
|
||||
use this, but it is on the UI regardless incase they cause catastrophic failure.
|
||||
|
||||
!!! warning
|
||||
Reverting an import is equivalent to undoing all of the scores that were imported as
|
||||
a result of the import. This, however, does not necessitate that classes will be
|
||||
reverted, such as if the import also declared you as kaiden -- that currently requires
|
||||
manual moderator intervention.
|
||||
|
||||
### Permissions
|
||||
|
||||
- delete_score
|
||||
- Must be the owner of this import (Or a server administrator).
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
None. (Empty Object)
|
||||
|
||||
*****
|
||||
|
||||
## Poll an ongoing import
|
||||
|
||||
`GET /api/v1/imports/:importID/poll-status`
|
||||
|
||||
!!! question
|
||||
The reason we can't directly respond with import info is that `tachi-server` **may**
|
||||
use a feature called SCORE_IMPORT_WORKERS. This dedicates score processing to separate
|
||||
processes which communicate back with any parent server. This means that the result of
|
||||
an import processed on one server may be returned by another.
|
||||
|
||||
This feature is enabled on our instances of `tachi-server` -- Boku and Kamai, which
|
||||
means you will have to poll this endpoint.
|
||||
|
||||
!!! info
|
||||
You are intended to poll this endpoint every one second or so. Do it until
|
||||
`body.importStatus` is "completed".
|
||||
|
||||
!!! tip
|
||||
`body.progress.description` is human-friendly output, you can render it to
|
||||
a client on every ping in the case where `body.importStatus` is "ongoing".
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `importStatus` | "completed" \| "ongoing" | If this is equal to completed, the import is finished and the importDocument is returned under `import` . If this is "ongoing", a `progress` key will display information and progress though the import |
|
||||
| `progress` | `{ description: string }` \| Not Present | If `importStatus` is "ongoing", this will contain a string `description` of where in the import process this import is. |
|
||||
| `import` | ImportDocument \| Not Present | If `importStatus` is "completed", this will contain the import document that was just inserted into the database. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/imports/my_import_id/poll-status
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
importStatus: "ongoing",
|
||||
progress: {
|
||||
description: "Imported 1832 Scores..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively,
|
||||
|
||||
```js
|
||||
{
|
||||
importStatus: "completed",
|
||||
import: {
|
||||
importID: "my_import_id",
|
||||
scoreIDs: ["foo", "bar"],
|
||||
// ... more import props
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,101 +0,0 @@
|
||||
# OAuth2 Integration
|
||||
|
||||
Tachi provides an OAuth2 API and some other things
|
||||
so that you can create your own clients and retrieve
|
||||
user api keys safely.
|
||||
|
||||
For a detailed explaination on how to use the OAuth2 flow, you can check [Using OAuth2 With Tachi](../../codebase/infrastructure/oauth2.md)
|
||||
|
||||
*****
|
||||
|
||||
## Convert Auth Code to API Key
|
||||
|
||||
`POST /api/v1/oauth/token`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `client_id` | String | Your clients ID. |
|
||||
| `client_secret` | String | Your clients secret. |
|
||||
| `grant_type` | "authorization_code" | This is the only form of grant_type we currently support. |
|
||||
| `redirect_uri` | String | The redirect URI your client uses. This must match the one in your client. |
|
||||
| `code` | String | The intermediate auth code to convert up into an API Key. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | APIKeyDocument | The APIKeyDocument created for your service to use. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/oauth/token
|
||||
---
|
||||
{
|
||||
"client_id": "my_client_id",
|
||||
"client_secret": "some_secret_value!",
|
||||
"grant_type": "authorization_code",
|
||||
"redirect_uri": "https://example.com/callback",
|
||||
"code": "intermediate_code"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"userID": 1,
|
||||
"token": "fdbufasbfuarf",
|
||||
"identifier": "Your_Service Token",
|
||||
"permissions": {
|
||||
"customise_profile": true
|
||||
},
|
||||
"fromAPIClient": "my_client_id"
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Create an intermediate Code
|
||||
|
||||
`POST /api/v1/create-code`
|
||||
|
||||
!!! info
|
||||
This infers the current user from the session cookie.
|
||||
|
||||
This is *not* meant to be called by external code, and
|
||||
is instead something for `tachi-client` to use on the oauth confirmation screen.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `code` | String | The intermediate code. |
|
||||
| `userID` | Integer | The user this code belongs to. |
|
||||
| `createdOn` | Number | The time in unix milliseconds that this code was created. |
|
||||
|
||||
!!! warning
|
||||
These codes expire around 30 minutes from their creation.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
POST /api/v1/oauth/create-code
|
||||
```
|
||||
|
||||
#### Response
|
||||
```json
|
||||
{
|
||||
"code": "foobarbarhsdufh",
|
||||
"userID": 1,
|
||||
"createdAt": 111111111111
|
||||
}
|
||||
```
|
||||
@@ -1,127 +0,0 @@
|
||||
# Score Endpoints
|
||||
|
||||
!!! note
|
||||
Scores are *not* personal bests. For more information
|
||||
on the distinction, see [PBs and Scores](../../wiki/pbs-scores.md).
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve specific score.
|
||||
|
||||
`GET /api/v1/scores/:scoreID`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `getRelated` | Presence | If present, also return the song and chart for this score document. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | [ScoreDocument](../../schemas/score.md) | The score document with this scoreID. |
|
||||
| `song` (Conditional) | [SongDocument](../../schemas/song.md) | If `getRelated` is set, then this is the song the score belongs to. |
|
||||
| `chart` (Conditional) | [ChartDocument](../../schemas/chart.md) | Same as above, but for the chart document. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/scores/Re7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"score": {
|
||||
"scoreID": "Re7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b",
|
||||
"songID": 1,
|
||||
"chartID": "some_chart_ID"
|
||||
},
|
||||
"song": {
|
||||
"id": 1,
|
||||
"title": "5.1.1."
|
||||
},
|
||||
"chart": {
|
||||
"chartID": "some_chartID",
|
||||
"songID": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Modify a score document.
|
||||
|
||||
`PATCH /api/v1/scores/:scoreID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_score
|
||||
- Must be the owner of this score.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `comment` (Optional) | Null or String | A string between 1 and 120 characters, or null. If null, the score will have its comment unset. If not, the comment for this score will be set to its contents. If the key is not present, no change will be made. |
|
||||
| `highlight` (Optional) | Boolean | Whether this score was a highlight or not. If this field is not present, no change will be made to the highlight status. |
|
||||
|
||||
!!! info
|
||||
Although all of these fields are optional, providing none
|
||||
of them is a 400 failure.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | [ScoreDocument](../../schemas/score.md) | The new score document.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
PATCH /api/v1/scores/Re7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"comment": "new comment"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"scoreID": "Re7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b",
|
||||
"comment": "new comment",
|
||||
"highlighted": false,
|
||||
// etc..
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Delete a score.
|
||||
|
||||
`DELETE /api/v1/scores/:scoreID`
|
||||
|
||||
!!! info
|
||||
Deleting a score will result in profile recalculations and PB updates.
|
||||
|
||||
### Permissions
|
||||
|
||||
- delete_score
|
||||
- Must be the owner of this score (Or a server administrator).
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
None. (Empty Object)
|
||||
@@ -1,52 +0,0 @@
|
||||
# Search Endpoints
|
||||
|
||||
*****
|
||||
|
||||
## Search Everything.
|
||||
|
||||
`GET /api/v1/search`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `search` | String | What to search for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | The array of users whose usernames look like the search criterion. |
|
||||
| `songs` | ([SongDocument](../../schemas/song.md) With [__textScore](../../codebase/implementation-details/search.md) and `game`.)[] | An array of songs from all games, with `__textScore` and `game` properties attached. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/search?search=freedom
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
users: [{
|
||||
username: "FreedomDiver",
|
||||
// ...
|
||||
}],
|
||||
songs: [{
|
||||
title: "FREEDOM",
|
||||
__textScore: 2,
|
||||
game: "iidx",
|
||||
// ...
|
||||
}, {
|
||||
title: "FREEDOM DiVE",
|
||||
__textScore: 1,
|
||||
game: "bms",
|
||||
// ...
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
!!! info
|
||||
For more details on how searching works, see [Search Implementation](../../codebase/implementation-details/search.md).
|
||||
@@ -1,118 +0,0 @@
|
||||
# Session Endpoints
|
||||
|
||||
---
|
||||
|
||||
## Get a specific session
|
||||
|
||||
`GET /api/v1/sessions/:sessionID`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-------: | :--------------------------------------------------: | :-------------------------------------------: |
|
||||
| `session` | [SessionDocument](../../schemas/session.md) | The session document at this ID. |
|
||||
| `scores` | Array<[ScoreDocument](../../schemas/score.md)> | The score documents involved in this session. |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The songs these score documents belong to. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The charts these score documents belong to. |
|
||||
| `user` | [UserDocument](../../schemas/user.md) | The user that made this session. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/sessions/Qe7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
user: {
|
||||
id: 1,
|
||||
username: "zkldi",
|
||||
// ...
|
||||
},
|
||||
session: {
|
||||
sessionID: "Qe7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b",
|
||||
scores: [{
|
||||
scoreID: "foo",
|
||||
// ...
|
||||
}],
|
||||
name: "my session",
|
||||
// ...
|
||||
},
|
||||
scores: [{
|
||||
scoreID: "foo",
|
||||
songID: 1,
|
||||
chartID: "foo_chartID",
|
||||
}],
|
||||
songs: [{
|
||||
id: 1,
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
chartID: "foo_chartID",
|
||||
songID: 1
|
||||
// ...
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modify a session
|
||||
|
||||
`PATCH /api/v1/sessions/:sessionID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_session
|
||||
- Must be the owner of this session.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--------------------: | :-----: | :---------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| `name` (optional) | String | A new name for this session. This must be between 3 and 80 characters. If not present, no update will be made to the session name. |
|
||||
| `desc` (optional) | String | A new description for this session. This must be between 3 and 120 characters. If not present, no update to the description will be made. |
|
||||
| `highlight` (optional) | boolean | Whether this session is highlighted or not. If not present, no change will be made to the highlighted status. |
|
||||
|
||||
!!! info
|
||||
Although all these fields are optional, making a request
|
||||
without any of them is a 400 error.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----------------------------------------: | :--------------------------------------------: |
|
||||
| `<body>` | [SessionDocument](../../schemas/session.md) | The new session document, after modifications. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
PATCH /api/v1/sessions/Qe7b00261b1d3ba8e5c9ee4e76e77ea9f07d9493b
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "new session name"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "new session name",
|
||||
"desc": "old session desc",
|
||||
"highlighted": false
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -1,76 +0,0 @@
|
||||
# Status Checks
|
||||
|
||||
These endpoints are generally for programmers checking their code works.
|
||||
They can also be used to check the status of the server.
|
||||
|
||||
*****
|
||||
|
||||
## Check server status.
|
||||
|
||||
```GET /api/v1/status```
|
||||
|
||||
This endpoint is a status check for the API and the consumer,
|
||||
It's a good way of sanity checking whether your code works.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `echo` | String | A string to echo. This is useful for checking whether parameters are sending over properly. |
|
||||
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `serverTime` | Integer | The current time of the server in Unix Milliseconds. |
|
||||
| `startTime` | Integer | The time this server was booted in Unix Milliseconds. |
|
||||
| `whoami` | Integer \| null | The userID you are authenticated as. If you are not authenticated, this is null. |
|
||||
| `version` | String | The current version of Tachi-Server running. |
|
||||
| `permissions` | Array<string> | The permissions this request had. |
|
||||
| `echo` (Conditional) | String | If an `echo` parameter was provided, this is that exact parameter. |
|
||||
|
||||
### Example
|
||||
|
||||
`GET /api/v1/status?echo=helloworld`
|
||||
|
||||
```json
|
||||
{
|
||||
"serverTime": 1623331110661,
|
||||
"version": "v2.0.0 (Mysterons)",
|
||||
"permissions": ["score_submit", "example_permission"],
|
||||
"echo": "helloworld"
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Check server status with POST.
|
||||
|
||||
```POST /api/v1/status```
|
||||
|
||||
This endpoint is identical to `GET /status`, but it responds to POST requests,
|
||||
and takes the `echo` parameter from the request body.
|
||||
|
||||
This is a good way to check whether your code sends JSON
|
||||
bodies properly.
|
||||
|
||||
### Example
|
||||
|
||||
```json
|
||||
POST /api/v1/status
|
||||
```
|
||||
```
|
||||
{
|
||||
"echo": "hello world"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"serverTime": 1623331110662,
|
||||
"version": "v2.0.0 (Mysterons)",
|
||||
"permissions": ["score_submit", "example_permission"],
|
||||
"echo": "hello world"
|
||||
}
|
||||
```
|
||||
@@ -1,156 +0,0 @@
|
||||
# UGPT Stat Showcase
|
||||
|
||||
These endpoints are related to the Statistic Showcase feature.
|
||||
|
||||
*****
|
||||
|
||||
## Evaluate this users set stats
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/showcase`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `projectUser` | Optional, userID | If provided, will *project* another users showcase onto this user, evaluating the same user against the projectedUser's stats. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<StatShowcaseResults> | |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/1/games/iidx/SP/showcase
|
||||
```
|
||||
|
||||
#### Response
|
||||
```js
|
||||
[{
|
||||
stat: {
|
||||
|
||||
},
|
||||
value: {
|
||||
value: 123,
|
||||
},
|
||||
related: {
|
||||
song: {
|
||||
title: "FREEDOM DIVE",
|
||||
// ...
|
||||
},
|
||||
chart: {
|
||||
// some chart stuff..
|
||||
},
|
||||
// folders: [] if this is a folder(s) stat, then folders are displayed here.
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Replace a user's stat showcase.
|
||||
|
||||
`PATCH /api/v1/users/:userID/games/:gameGroup/:playtype/showcase`
|
||||
|
||||
### Permissions
|
||||
|
||||
- `customise_profile`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<StatDocument> | An array of up to 6 stat documents. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `<body>` | Array<StatDocument> | The newly updated stat documents. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
PATCH /api/v1/users/1/games/iidx/SP/showcase
|
||||
|
||||
[
|
||||
{
|
||||
mode: "chart",
|
||||
chartID: "some_chart_id",
|
||||
property: "percent"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Response
|
||||
```js
|
||||
[
|
||||
{
|
||||
mode: "chart",
|
||||
chartID: "some_chart_id",
|
||||
property: "percent"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Evaluate a custom stat on this user.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/showcase/custom`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `mode` | "folder" \| "chart" | Whether the stat to evaluate is on a folder or a chart. |
|
||||
| `property` | "grade" \| "lamp" \| "score" \| "percent" or "playcount" if mode is chart. | What property to evaluate on the given criteria. |
|
||||
| `chartID` | string, if mode === "chart" | If mode is chart, this should contain the relevant chartID. |
|
||||
| `folderID` | string, if mode === "folder" | If mode is folder, this should contain the relevant folderID. |
|
||||
| `gte` | number, if mode === "folder" | If mode is folder, this must contain the value the property must be greater than, i.e. lamp >= 6, or percent >= 90 |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `stat` | StatDocument | The stat you evaluated. |
|
||||
| `result` | {value: number \| null, outOf?: number } | Contains `value`, which contains the stat's value, or NULL if the mode is chart and the user has not played this chart. If mode is folder, `outOf` contains the total amount of charts in that folder. |
|
||||
| `related` | {song, chart} or {folders} | If mode is chart, contains the pertinent song and chart. If mode is folder, contains the pertinent folder documents.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/1/games/iidx/SP/showcase/custom?mode=chart&property=percent&chartID=some_chart_id
|
||||
```
|
||||
|
||||
#### Response
|
||||
```js
|
||||
{
|
||||
stat: {
|
||||
mode: "chart",
|
||||
property: "percent",
|
||||
chartID: "some_chart_id",
|
||||
},
|
||||
result: {
|
||||
value: 99.12
|
||||
},
|
||||
related: {
|
||||
song: {
|
||||
id: 123,
|
||||
title: "AA",
|
||||
artist: "DJ.Amuro",
|
||||
// ...
|
||||
},
|
||||
chart: {
|
||||
songID: 123,
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,148 +0,0 @@
|
||||
# UGPT-Target Endpoints
|
||||
|
||||
These endpoints deal with [targets](../../api/terminology.md) for a User, Game and Playtype. These are things like subscribing to a new quest, or reading info about progress on a specific goal.
|
||||
|
||||
For user-specific target endpoints, such as subscriptions, see [UGPT-Target Endpoints](./ugpt-targets.md).
|
||||
|
||||
*****
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve a user's recently achieved targets.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/targets/recently-achieved`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `goalSubs` | Array<GoalSubDocument> | The goal subscriptions that were recently achieved by this user. |
|
||||
| `goals` | Array<GoalDocument> | The goal documents that were recently achieved (if any). |
|
||||
| `questSubs` | Array<QuestSubDocument> | The quest subscriptions that were recently achieved by this user. |
|
||||
| `quest` | Array<QuestDocument> | The quest documents that were recently achieved (if any). |
|
||||
| `user` | UserDocument | The user you requested this information about. |
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve a user's recently raised targets.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/targets/recently-raised`
|
||||
|
||||
!!! info
|
||||
Recently raised means that the user recently increased their `progress` on the target. It explicitly excludes achieved goals -- only things that they are getting closer to.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `goalSubs` | Array<GoalSubDocument> | The goal subscriptions that were recently achieved by this user. |
|
||||
| `goals` | Array<GoalDocument> | The goal documents that were recently achieved (if any). |
|
||||
| `questSubs` | Array<QuestSubDocument> | The quest subscriptions that were recently achieved by this user. |
|
||||
| `quest` | Array<QuestDocument> | The quest documents that were recently achieved (if any). |
|
||||
| `user` | UserDocument | The user you requested this information about. |
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve this user's quest subscriptions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/targets/quests`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `quests` | Array<QuestDocument> | All of the quests this user is subscribed to. |
|
||||
| `questSubs` | Array<QuestSubDocument> | All of this user's quest subscriptions. |
|
||||
|
||||
*****
|
||||
|
||||
## Get a user's progress on a specific quest they are subscribed to.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/targets/quests/:questID`
|
||||
|
||||
!!! info
|
||||
If you're looking to evaluate a quest against a user, without knowing whether they're
|
||||
subscribed to it or not, you want [GPT/Targets/Quests/Evaluate-For](gpt-targets.md#evaluate-a-quest-for-a-user-even-if-they-arent-subscribed-to-it).
|
||||
|
||||
This endpoint is admittedly in a very strange position, where it overlaps with that `evaluate-for` endpoint quite significantly, but this endpoint is also intended to return the user's `questSub`, so I thought it best to split this into two endpoints.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `questSub` | QuestSubDocument | The user's subscription to this quest. |
|
||||
| `quest` | QuestDocument | The quest document in question. |
|
||||
| `goals` | Array<GoalDocument> | The goals involved in this quest. |
|
||||
| `results` | Array<EvaluatedGoalResult> | The user's progress on each goal in this quest. |
|
||||
|
||||
#### EvaluatedGoalResult
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `goalID` | String | The goal ID that these results are for. |
|
||||
| `achieved` | Boolean | Whether this goal was achieved or not. |
|
||||
| `progress` | Number \| Null | How much progress this user made on this goal. Null if no progress was made. |
|
||||
| `outOf` | Number | What `progress` needs to be greater than or equal to for this goal to count as achieved. |
|
||||
| `progressHuman` | String | A humanised, pretty-printed progress indicator for this goal. |
|
||||
| `outOfHuman` | String | A humanised, pretty-printed outOf indicator for this goal. |
|
||||
|
||||
*****
|
||||
|
||||
## Subscribe to a quest.
|
||||
|
||||
`PUT /api/v1/users/:userID/games/:gameGroup/:playtype/targets/quests/:questID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- `manage_targets`
|
||||
- Must be the requesting user.
|
||||
|
||||
### Parameters
|
||||
|
||||
None. (All relevant info is in the URL.)
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `goals` | Array<GpalDocument> | The goals involved in this quest. |
|
||||
| `quest` | QuestDocument | The quest the user just subscribed to. |
|
||||
| `goalResults` | Array<EvaluatedGoalResults> | The user's progress on each individual goal in this quest. |
|
||||
| `questSub` | QuestSubscriptionDocument | The quest subscription this user just created |
|
||||
|
||||
*****
|
||||
|
||||
## Unsubscribe from a quest.
|
||||
|
||||
`DELETE /api/v1/users/:userID/games/:gameGroup/:playtype/targets/quests/:questID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- `manage_targets`
|
||||
- Must be the requesting user.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `quest` | QuestDocument | The quest you just unsubscribed from. |
|
||||
@@ -1,901 +0,0 @@
|
||||
# Individual User on Specific Game
|
||||
|
||||
This endpoints are for specific users information on specific game + playtype combinations.
|
||||
|
||||
This scenario appears frequently, and is typically shortened to UGPT.
|
||||
|
||||
---
|
||||
|
||||
## Get information about a user's plays on a game + playtype.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :---------------: | :------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------: |
|
||||
| `gameStats` | UserGameStatsDocument | The User's GameStats for this game + playtype. |
|
||||
| `firstScore` | [ScoreDocument](../../schemas/score.md) or Null | The user's first score for this game + playtype. This is null if the user has no scores with timestamps. |
|
||||
| `mostRecentScore` | [ScoreDocument](../../schemas/score.md) or Null | The user's most recent score. This is null if the user has no scores with timestamps. |
|
||||
| `totalScores` | Integer | The total amount of scores this user has. |
|
||||
| `rankingData` | Record<Rating Algorithm, { ranking: integer, outOf: integer }> | The position of this player on the default leaderboards for this game, and how many players it is out of. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldi/games/iidx/SP
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
gameStats: {
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
ktRating: 15
|
||||
},
|
||||
classes: {
|
||||
dan: 14
|
||||
}
|
||||
},
|
||||
firstScore: null,
|
||||
mostRecentScore: null,
|
||||
totalScores: 5,
|
||||
rankingData: {
|
||||
ktRating: {
|
||||
ranking: 3,
|
||||
outOf: 18
|
||||
}
|
||||
ktLampRating: {
|
||||
ranking: 2,
|
||||
outOf: 18
|
||||
},
|
||||
BPI: {
|
||||
ranking: 5,
|
||||
outOf: 18
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Search a user's personal bests.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/pbs`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :---------------------------------------------------------------------------------------------: |
|
||||
| `search` | String | Limits the returned scores to those where the corresponding song is most similar to this query. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :------------------------------------------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The array of songs this search returned. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts this search returned. |
|
||||
| `pbs` | Array<PBDocument> | The array of personal bests this search returned. This is limited to 30 returns. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/pbs?search=Verfl
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pbs: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Get a user's best 100 personal bests.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/pbs/best`
|
||||
|
||||
This returns the users' best 100 personal bests according
|
||||
to the [Default Rating Algorithm](../../codebase/implementation-details/game-configuration) for this game.
|
||||
|
||||
This is returned in descending sorted order.
|
||||
|
||||
The query parameter `alg` can be used to specify a
|
||||
different rating algorithm to sort under.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :-----------------------------------------------------------: |
|
||||
| `alg` | String | An overriding rating algorithm to use instead of the default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :-----------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The array of songs this search returned. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts this search returned. |
|
||||
| `pbs` | Array<PBDocument> | The array of personal bests this search returned. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/pbs/best?alg=BPI
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pbs: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
// ...
|
||||
},
|
||||
calculatedData: {
|
||||
ktRating: 15,
|
||||
BPI: 4.4
|
||||
}
|
||||
}, {
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
// ...
|
||||
},
|
||||
calculatedData: {
|
||||
ktRating: 19,
|
||||
BPI: 4.2
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}, {
|
||||
title: "AA",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 14,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Returns all of a users personal bests.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/pbs/all`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :---------------------------: |
|
||||
| `pbs` | Array<PBDocument> | All of the users PB Documents |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | All of the relevant songs. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | All of the relevant charts. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/pbs/all
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pbs: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get A User's PB for a given chart.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/pbs/:chartID`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--------------: | :------: | :------------------------------------------------------------------------------------: |
|
||||
| `getComposition` | Presence | If present, the individual ScoreDocuments that composed this PB will also be returned. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--------------------: | :--------------------------------------------------: | :----------------------------------------------------------------------------------------------------------: |
|
||||
| `pb` | PBDocument | The user's PB for this chart. |
|
||||
| `chart` | [ChartDocument](../../schemas/chart.md) | The chart this PB is on. |
|
||||
| `scores` (Conditional) | Array<[ScoreDocument](../../schemas/score.md)> | If `getComposition` is present, then this field contains the array of score documents that composed this PB. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/1/games/iidx/SP/pbs/some_chart_id
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
pb: {
|
||||
chartID: "some_chart_id",
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
},
|
||||
chart: {
|
||||
chartID: "some_chart_id"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Search a user's individual scores.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/scores`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :---------------------------------------------------------------------------------------------: |
|
||||
| `search` | String | Limits the returned scores to those where the corresponding song is most similar to this query. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-------------------------------------------------------------------: | :----------------------------------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md) with \_\_textScore> | The array of songs this search returned. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts this search returned. |
|
||||
| `scores` | Array<[ScoreDocument](../../schemas/score.md)> | The array of scores this search returned. This is limited to 30 returns. |
|
||||
|
||||
!!! info
|
||||
All `songs` returned also have the `__textScore`
|
||||
property. This property describes how close the query
|
||||
was to the actual text, and is mostly internal.
|
||||
|
||||
You can read more into the details of this at [Search Implementation](../../codebase/implementation-details/search.md)
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/scores?search=Verfl
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
scores: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get a user's most recent 100 scores.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/scores/recent`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :----------------------------------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The array of songs this search returned. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts this search returned. |
|
||||
| `scores` | Array<[ScoreDocument](../../schemas/score.md)> | The array of scores this search returned. This is limited to 30 returns. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/scores/recent
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
scores: [{
|
||||
userID: 1,
|
||||
scoreData: {
|
||||
score: 123,
|
||||
// ...
|
||||
}
|
||||
}],
|
||||
songs: [{
|
||||
title: "Verflucht",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 123,
|
||||
playtype: "SP",
|
||||
difficulty: "HYPER",
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Search a user's sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/sessions`
|
||||
|
||||
Searches the names of sessions from a given user. This
|
||||
does not search session descriptions, nor does it search
|
||||
song titles of played songs inside sessions.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :-----------------------------: |
|
||||
| `search` | String | The session name to search for. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------------------------------------: | :--------------------------------------------: |
|
||||
| `<body>` | Array<[SessionDocument](../../schemas/session.md)> | The array of sessions that matched this query. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/sessions?search=epic%20session
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
name: "My Epic Session!!!",
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
// ...
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
## Get a user's best 100 sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/sessions/best`
|
||||
|
||||
Retrieves a user's best 100 sessions according to the
|
||||
game + playtypes default algorithm. The algorithm can
|
||||
be overrode with the `alg` query string parameter.
|
||||
|
||||
These are returned in descending order.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :--------------: | :----: | :------------------------------------------------------: |
|
||||
| `alg` (Optional) | String | The name of the algorithm to use instead of the default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------------------------------------: | :-----------------------------------: |
|
||||
| `<body>` | Array<[SessionDocument](../../schemas/session.md)> | The array of the users best sessions. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/sessions/best
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
!!! info
|
||||
The default rating algorithm for IIDX:SP is `ktRating`.
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
calculatedData: {
|
||||
ktRating: 14,
|
||||
bpi: 3,
|
||||
},
|
||||
// ... more properties
|
||||
},
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
calculatedData: {
|
||||
ktRating: 13.2,
|
||||
bpi: 4,
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
## Get a user's most recent 100 sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/sessions/recent`
|
||||
|
||||
Retrieves a user's most recent 100 sessions for this game.
|
||||
|
||||
These are returned in descending order according to `timeEnded`.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------------------------------------: | :------------------------------: |
|
||||
| `<body>` | Array<[SessionDocument](../../schemas/session.md)> | The array of the users sessions. |
|
||||
|
||||
---
|
||||
|
||||
## Get a user's most recent session.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/sessions/last`
|
||||
|
||||
!!! info
|
||||
This endpoint will return 404 if the user has never had a
|
||||
session for this game.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----------------------------------------: | :-----------------------------: |
|
||||
| `<body>` | [SessionDocument](../../schemas/session.md) | The user's most recent session. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/sessions/last
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"name": "foo",
|
||||
"desc": "My most recent session",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Get a user's most recent 100 highlighted sessions.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/sessions/highlighted`
|
||||
|
||||
Retrieves a user's most recent 100 highlighted sessions for this game.
|
||||
|
||||
These are returned in descending order according to `timeEnded`.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------------------------------------: | :------------------------------------------: |
|
||||
| `<body>` | Array<[SessionDocument](../../schemas/session.md)> | The array of the users highlighted sessions. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/sessions/highlighted
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
calculatedData: {
|
||||
ktRating: 14,
|
||||
bpi: 3
|
||||
}
|
||||
highlight: true,
|
||||
// ... more props
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get a user's most played charts.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/most-played`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :--------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| `songs` | Array<[SongDocument](../../schemas/song.md)> | The array of songs related to the pbs. |
|
||||
| `charts` | Array<[ChartDocument](../../schemas/chart.md)> | The array of charts related to the pbs. |
|
||||
| `pbs` | Array<(PBDocument & {\_\_playcount: integer})> | An array of PB documents with the `__playcount` property attached. This property dictates how many times the user has played this chart. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/most-played
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
songs: [{
|
||||
id: 1,
|
||||
title: "5.1.1.",
|
||||
// ...
|
||||
}, {
|
||||
id: 2,
|
||||
title: "GAMBOL",
|
||||
// ...
|
||||
}],
|
||||
charts: [{
|
||||
songID: 1,
|
||||
difficulty: "ANOTHER",
|
||||
// ...
|
||||
}, {
|
||||
songID: 2,
|
||||
difficulty: "LEGGENDARIA",
|
||||
// ...
|
||||
}, {
|
||||
songID: 1,
|
||||
difficulty: "HYPER",
|
||||
}],
|
||||
pbs: [{
|
||||
chartID: "something",
|
||||
__playcount: 5,
|
||||
// ...
|
||||
}, {
|
||||
chartID: "something_else",
|
||||
__playcount: 2,
|
||||
// ...
|
||||
}, {
|
||||
chartID: "something_more",
|
||||
__playcount: 1,
|
||||
// ...
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a leaderboard around a user.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/leaderboard-adjacent`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :---------------: | :-----------------------------------------------------------------------------------------------------------------: |
|
||||
| `alg` | String (Optional) | Optionally, you can provide an override algorithm to use for the leaderboards instead of the game+playtype default. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :----------------: | :------------------------------------------------: | :-------------------------------------------------: |
|
||||
| `above` | Array<UserGameStats> | Up to 5 users' game stats better than this user. |
|
||||
| `below` | Array<UserGameStats> | Same as above, but below the user. |
|
||||
| `users` | Array<[UserDocument](../../schemas/user.md)> | The user documents related to the above statistics. |
|
||||
| `thisUsersStats` | UserGameStats | The requested user's stats for this GPT. |
|
||||
| `thisUsersRanking` | {outOf: integer, ranking: integer} | The requested user's ranking for this GPT. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldimes/iidx/SP/leaderboard-adjacent
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
above: [{
|
||||
userID: 2,
|
||||
ratings: {
|
||||
ktRating: 9
|
||||
},
|
||||
classes: {
|
||||
dan: 10
|
||||
},
|
||||
// ...
|
||||
}],
|
||||
below: [{
|
||||
userID: 3,
|
||||
ratings: {
|
||||
ktRating: 1
|
||||
},
|
||||
classes: {
|
||||
dan: 5
|
||||
},
|
||||
// ...
|
||||
}],
|
||||
users: [{
|
||||
userID: 2,
|
||||
username: "sptmgtm",
|
||||
// ...
|
||||
}, {
|
||||
userID: 3,
|
||||
username: "neil.c",
|
||||
// ...
|
||||
}],
|
||||
thisUsersStats: {
|
||||
userID: 1,
|
||||
ratings: {
|
||||
ktRating: 5
|
||||
},
|
||||
classes: {
|
||||
dan: 5
|
||||
}
|
||||
},
|
||||
thisUsersRanking: {
|
||||
ranking: 2,
|
||||
outOf: 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve this user's GPT stat history.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/history`
|
||||
|
||||
Every day, a user's game stats are snapshotted and saved. This returns the recent ones.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------: | :-------------------------------------------------------------------------------------------------------------------: |
|
||||
| `<body>` | Array<UserGameStatsSnapshot> | The most recent (up to) 90 UGS Snapshots, where the first element is the most recent one, and the last is the oldest. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/1/games/iidx/SP/history
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
classes: {
|
||||
dan: 13,
|
||||
},
|
||||
ratings: {
|
||||
BPI: 5,
|
||||
ktRating: 3,
|
||||
ktLampRating: 1,
|
||||
},
|
||||
timestamp: 12312323123123, // most recent
|
||||
playcount: 500,
|
||||
ranking: 14,
|
||||
},
|
||||
// and so on..
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve this user's GPT settings.
|
||||
|
||||
`GET /api/v1/users/:userID/games/:gameGroup/:playtype/settings`
|
||||
|
||||
!!! warning
|
||||
Unlike most other applications, your settings are completely public. GPT Settings only concern
|
||||
cosmetic things, like what rating algorithms to prefer.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------: | :----------------------------------: |
|
||||
| `<body>` | UGPTSettingsDocument | The settings document for this user. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/1/games/iidx/SP/settings
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
preferredScoreAlg: null,
|
||||
preferredSessionAlg: "BPI",
|
||||
preferredProfileAlg: null,
|
||||
stats: []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modify your UGPT settings.
|
||||
|
||||
`PATCH /api/v1/users/:userID/games/:gameGroup/:playtype/settings`
|
||||
|
||||
!!! note
|
||||
Although `stats` are part of your settings, they are not modifiable under these endpoints,
|
||||
instead you should use [UGPT Showcase Endpoints](./ugpt-showcase.md).
|
||||
|
||||
### Permissions
|
||||
|
||||
- `customise_profile`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| `<body>` | Partial UGPTSettingsDocument | A UGPTSettingsDocument where all properties are optional. Properties not present will not be modified. Note that `stats` are not modifiable here. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------: | :---------------------------: |
|
||||
| `<body>` | UGPTSettingsDocument | The new UGPTSettingsDocument. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
PATCH /api/v1/users/1/games/iidx/SP/settings
|
||||
|
||||
{
|
||||
preferredScoreAlg: "BPI"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
|
||||
{
|
||||
preferredScoreAlg: "BPI",
|
||||
preferredSessionAlg: "ktRating",
|
||||
preferredProfileAlg: null,
|
||||
stats: []
|
||||
}
|
||||
```
|
||||
@@ -1,110 +0,0 @@
|
||||
# User Integrations
|
||||
|
||||
These endpoints are related to a users integrations with external services.
|
||||
|
||||
*****
|
||||
|
||||
## Retrieve whether this user is authenticated with this kaiType.
|
||||
|
||||
`GET /api/v1/users/:userID/integrations/kai/:kaiType`
|
||||
|
||||
**Kamaitachi Only**
|
||||
|
||||
!!! note
|
||||
A KaiType is either "flo", "min", or "eag". Since these three services
|
||||
share backends, they all use the same authentication mechanisms, and share
|
||||
endpoints like this.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Self-key: This request must be made using Cookie authentication, which means it cannot be used with API keys.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `authStatus` | boolean | True if the user is authenticated with this kaiType, false if they are not. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```
|
||||
GET /api/v1/users/1/integrations/kai/flo
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
authStatus: false
|
||||
}
|
||||
```
|
||||
|
||||
*****
|
||||
|
||||
## Revoke this user's authentication with this kaiType
|
||||
|
||||
`DELETE /api/v1/users/:userID/integrations/kai/:kaiType`
|
||||
|
||||
|
||||
**Kamaitachi Only**
|
||||
|
||||
!!! note
|
||||
A KaiType is either "flo", "min", or "eag". Since these three services
|
||||
share backends, they all use the same authentication mechanisms, and share
|
||||
endpoints like this.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Self-key: This request must be made using Cookie authentication, which means it cannot be used with API keys.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
Empty Object.
|
||||
|
||||
*****
|
||||
|
||||
## Update a user's access_token and refresh_token from an intermediate code.
|
||||
|
||||
`POST /api/v1/users/:userID/integrations/kai/:kaiType/oauth2callback`
|
||||
|
||||
**Kamaitachi Only**
|
||||
|
||||
!!! info
|
||||
This is used as part of an [OAuth2 Flow](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2).
|
||||
|
||||
The client controls the callback link after authentication with the service, and then POSTs the returned `code` to us.
|
||||
This part of the flow actually updates the user.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Self-key
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `code` | String | The intermediate code to use to get the access_token and refresh_token. |
|
||||
|
||||
### Response
|
||||
|
||||
Empty object for body. 200 on success, not 200 on failure - status code depending on error.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
```js
|
||||
{
|
||||
code: "This_Is_An_1nT3rMeDIate_Code"
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{}
|
||||
```
|
||||
@@ -1,483 +0,0 @@
|
||||
# Users
|
||||
|
||||
These endpoints are related to users in general.
|
||||
|
||||
---
|
||||
|
||||
## List Users
|
||||
|
||||
`GET /api/v1/users`
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :-----------------: | :------: | :----------------------------------------------------------------------------------------------------: |
|
||||
| `online` (Optional) | Presence | If present, this limits the returned users to those that are currently online. |
|
||||
| `search` (Optional) | String | If present, this endpoint will only return users where this string is contained within their username. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :------------------------------------------------: | :------------------------------------: |
|
||||
| `<body>` | Array<[UserDocument](../../schemas/user.md)> | The array of up to 100 users returned. |
|
||||
|
||||
!!! note
|
||||
Users are guaranteeably returned in order of when they were `lastSeen`.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
id: 1,
|
||||
username: "zkldi",
|
||||
// ... continued
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve user with ID
|
||||
|
||||
`GET /api/v1/users/:userID`
|
||||
|
||||
!!! note
|
||||
The :userID param has some special functionality,
|
||||
and any time you see it in these docs, that
|
||||
functionality is supported.
|
||||
|
||||
You may pass the integer userID for this user - 1.
|
||||
You may also pass the username - zkldihis is also case-insensitive, so you could pass zklzkldi
|
||||
You may also pass the special string - `me` - which
|
||||
will select whatever user you are authenticated as.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----------------------------------: | :---------------------------------------: |
|
||||
| `<body>` | [UserDocument](../../schemas/user.md) | The user this ID/username corresponds to. |
|
||||
|
||||
### Example
|
||||
|
||||
!!! note
|
||||
`zk` is the username for the user with userID 1.
|
||||
|
||||
it's also the username of the person writing these
|
||||
docs. Hi!
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldi
|
||||
OR
|
||||
GET /api/v1/users/1
|
||||
OR
|
||||
GET /api/v1/users/zkldit's case insensitive!)
|
||||
OR
|
||||
GET /api/v1/users/me IF authenticated as userID 1.
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
id: 1,
|
||||
username: "zkldi
|
||||
// ... so on
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Modify this user document.
|
||||
|
||||
`PATCH /api/v1/users/:userID`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Self-Key level authentication as this user.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :----------------------------------------------------------: | :------------: | :---------------------------------------------------------------------------: |
|
||||
| `about` | String | An about me. This is rendered as markdown. |
|
||||
| `status` | String \| Null | The users status. If null, this will be unset. |
|
||||
| `discord`, `twitter`, `github`, `steam`, `youtube`, `twitch` | String \| Null | Information about this users social media. If null, this field will be unset. |
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-----------------------------------: | :--------------------------------------------------: |
|
||||
| `<body>` | [UserDocument](../../schemas/user.md) | The user document with all of those changes applied. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```js
|
||||
{
|
||||
"about": "#Hello!**I'm zkldi,
|
||||
"status": "I'm cool!",
|
||||
"twitter": null,
|
||||
"steam": "zkldi
|
||||
}
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
{
|
||||
"id": 1,
|
||||
"username": "zkldi
|
||||
"usernameLowercase": "zkldi
|
||||
"socialMedia": {
|
||||
"twitter": null,
|
||||
"steam": "zkldi
|
||||
// this property was already here, and not modified by the request.
|
||||
"discord": "chatbpd",
|
||||
},
|
||||
"about": "#Hello!**I'm zkldi,
|
||||
"status": "I'm cool!",
|
||||
// and other user props...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Retrieve per-game profiles for a user.
|
||||
|
||||
`GET /api/v1/users/:userID/game-profiles`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :--------------------------------------------------: | :-----------------------------------------: |
|
||||
| `<body>` | Array<UserGameStatsDocument & \_\_rankingData> | The array of per-game profile documents (ratings and classes) this user has. |
|
||||
|
||||
!!! info
|
||||
For UI reasons, the UserGameStatsDocuments here have an additional `__rankingData` property, which contains leaderboard ranking information for this user.
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
GET /api/v1/users/zkldime-stats
|
||||
OR
|
||||
GET /api/v1/users/1/game-profiles
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
userID: 1,
|
||||
game: "iidx",
|
||||
playtype: "SP",
|
||||
ratings: {
|
||||
ktRating: 15,
|
||||
},
|
||||
classes: {
|
||||
dan: 14,
|
||||
},
|
||||
__rankingData: {
|
||||
ktRating: {
|
||||
ranking: 15,
|
||||
outOf: 74,
|
||||
},
|
||||
BPI: {
|
||||
ranking: 12,
|
||||
outOf: 74,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
userID: 1,
|
||||
game: "gitadora",
|
||||
playtype: "Dora",
|
||||
ratings: {
|
||||
skill: 1404,
|
||||
},
|
||||
classes: {
|
||||
skillColour: 1,
|
||||
},
|
||||
__rankingData: {
|
||||
skill: {
|
||||
ranking: 199,
|
||||
outOf: 202,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
!!! info
|
||||
In the event a user has played no games, this will
|
||||
return an empty array.
|
||||
|
||||
---
|
||||
|
||||
## Change Profile Picture
|
||||
|
||||
`PUT /api/v1/users/:userID/pfp`
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_profile
|
||||
- Must be the owner of this profile.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :---------: | :-----------------------------: |
|
||||
| `pfp` | JPG, or PNG | The new profile picture to set. |
|
||||
|
||||
!!! note
|
||||
This endpoint expects multipart form data.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :--------------------------------------------------------: |
|
||||
| `get` | String | This contains the URL to then GET the new profile picture. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
PUT /api/v1/users/1/pfp
|
||||
```
|
||||
|
||||
```
|
||||
// this is not a real multipart request, as those things
|
||||
// are huge!
|
||||
pfp=<somefiledata>
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"get": "/api/v1/users/1/pfp"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get a user's profile picture.
|
||||
|
||||
`GET /api/v1/users/:userID/pfp`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
**Not JSON**. This returns the actual JPG or PNG stored for
|
||||
this user.
|
||||
|
||||
### Example
|
||||
|
||||
N/A
|
||||
|
||||
---
|
||||
|
||||
## Unset your profile picture.
|
||||
|
||||
`DELETE /api/v1/users/:userID/pfp`
|
||||
|
||||
!!! note
|
||||
If you do not have a profile picture set, this will be
|
||||
a 404 error.
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_profile
|
||||
- Must be the owner of this profile.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
None.
|
||||
|
||||
### Example
|
||||
|
||||
Self-explanatory.
|
||||
|
||||
---
|
||||
|
||||
## Change Profile Banner
|
||||
|
||||
`PUT /api/v1/users/:userID/banner`
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_profile
|
||||
- Must be the owner of this profile.
|
||||
|
||||
### Parameters
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :---------: | :----------------------------: |
|
||||
| `banner` | JPG, or PNG | The new profile banner to set. |
|
||||
|
||||
!!! note
|
||||
This endpoint expects multipart form data.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :----: | :-------------------------------------------------------: |
|
||||
| `get` | String | This contains the URL to then GET the new profile banner. |
|
||||
|
||||
### Example
|
||||
|
||||
#### Request
|
||||
|
||||
```
|
||||
PUT /api/v1/users/1/banner
|
||||
```
|
||||
|
||||
```
|
||||
// this is not a real multipart request, as those things
|
||||
// are huge!
|
||||
banner=<somefiledata>
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"get": "/api/v1/users/1/banner"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get a user's profile banner.
|
||||
|
||||
`GET /api/v1/users/:userID/banner`
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
Not JSON. This returns the actual JPG or PNG stored for
|
||||
this user.
|
||||
|
||||
### Example
|
||||
|
||||
N/A
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Unset your profile banner.
|
||||
|
||||
`DELETE /api/v1/users/:userID/banner`
|
||||
|
||||
!!! note
|
||||
If you do not have a profile banner set, this is
|
||||
a 404 error.
|
||||
|
||||
### Permissions
|
||||
|
||||
- customise_profile
|
||||
- Must be the owner of this profile.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
## Retrieve your notifications
|
||||
|
||||
`GET /api/v1/users/:userID/notifications`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be a session-request from the user who owns these notifications.
|
||||
|
||||
!!! note
|
||||
All of the notification endpoints must be accessed by session-level authentication
|
||||
from the right requesting user; viz. no api keys can access these endpoints, and
|
||||
nobody can read another players notifications.
|
||||
|
||||
This isn't really for any security reasons, but more for privacy reasons. It feels
|
||||
wrong to be able to let others read others notifications.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
| Property | Type | Description |
|
||||
| :------: | :-------------------------------: | :----------------------------------------------------------------------------------: |
|
||||
| `<body>` | Array<NotificationDocument> | An array of all of this users notifications, sorted by most recently recieved first. |
|
||||
|
||||
---
|
||||
|
||||
## Mark all of your notifications as read.
|
||||
|
||||
`POST /api/v1/users/:userID/notifications/mark-all-read`
|
||||
|
||||
!!! info
|
||||
This endpoints marks all of a users notifications as read, and is intended for a UI
|
||||
to invoke this request when they open their inbox.
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be a session-level request from the user who owns these notifications.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
None. (Empty Object)
|
||||
|
||||
---
|
||||
|
||||
## Clear all notifications from your inbox.
|
||||
|
||||
`POST /api/v1/users/:userID/notifications/delete-all`
|
||||
|
||||
### Permissions
|
||||
|
||||
- Must be a session-level request from the user who owns these notifications.
|
||||
|
||||
### Parameters
|
||||
|
||||
None.
|
||||
|
||||
### Response
|
||||
|
||||
None. (Empty Object)
|
||||
@@ -1,20 +0,0 @@
|
||||
# Notable Terminology
|
||||
|
||||
Similar to the userland terminology document - Tachi has some internal terminology that you should
|
||||
be familiar with.
|
||||
|
||||
## GPT
|
||||
|
||||
Refers to "Game + Playtype" - a combination of a game and its playtype (i.e. IIDX SP).
|
||||
|
||||
## UGPT
|
||||
|
||||
Refers to "User on Game + Playtype" - A user's "something" on a game and that playtype.
|
||||
|
||||
## Targets
|
||||
|
||||
Targets are a bucket term for things a user might set as targets to aim for. This term subsumes the following features:
|
||||
|
||||
- Goals
|
||||
- Quests
|
||||
- Questlines
|
||||
@@ -1,25 +0,0 @@
|
||||
# Event: class-update/v1
|
||||
|
||||
The content is as follows:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `userID` | Integer | The user ID who had a class update. |
|
||||
| `set` | Game Class Set | The name of the class set that was updated, such as `genocideDan` or `vfClass`. |
|
||||
| `game` | Game | The game this class update was for. |
|
||||
| `playtype` | Playtype | The playtype this class update was for. |
|
||||
| `old` | Null \| String | The old value for this class. If null, the user had no class here before. |
|
||||
| `new` | String | The new value for this class. |
|
||||
|
||||
## Example
|
||||
|
||||
```json
|
||||
{
|
||||
"userID": 1,
|
||||
"set": "dan",
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"old": null,
|
||||
"new": "CHUUDEN"
|
||||
}
|
||||
```
|
||||
@@ -1,33 +0,0 @@
|
||||
# About Webhooks
|
||||
|
||||
Tachi supports webhooks. You can set a `webhookUri` as part of your
|
||||
[Tachi API Client](../routes/clients.md).
|
||||
|
||||
## Usage and Security
|
||||
|
||||
When a given event happens on Tachi, your webhookUri will receive a POST
|
||||
request with some content and the type of event.
|
||||
|
||||
You **MUST** validate that this request was from Tachi! Otherwise, anyone
|
||||
could post fake data to your webhook URI and potentially compromise it.
|
||||
|
||||
To secure your webhook implementation, Tachi will send an Authorization header with `Bearer CLIENT_SECRET`. You should check that that value aligns with your client secret. If it doesn't, someone might be trying to perform an attack!
|
||||
|
||||
## Data Format
|
||||
|
||||
Data is sent as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "EVENT_TYPE",
|
||||
"content": {} // Content specific to that EVENT_TYPE!
|
||||
}
|
||||
```
|
||||
|
||||
The current Events are:
|
||||
|
||||
| Type | Description |
|
||||
| :: | :: |
|
||||
| `class-update/v1` | Fires whenever a user has had a class update positively, such as going from 9th Dan to 10th Dan. |
|
||||
| `goal-achieved/v1` | Fires whenever a user has achieved a goal. |
|
||||
| `quest-achieved/v1` | Fires whenever a user has achieved a quest. |
|
||||
@@ -1,22 +0,0 @@
|
||||
# Direct Manual
|
||||
|
||||
Direct Manual is a way of POSTing BATCH-MANUAL content straight to the server.
|
||||
|
||||
In short, this lets you implement your own IRs, with absolutely no input
|
||||
from me.
|
||||
|
||||
## Flow
|
||||
|
||||
You need to send a POST request to `/ir/direct-manual/import`, with the body
|
||||
of the request being a batch manual document.
|
||||
|
||||
You can set the `X-User-Intent` Header to true if the import was done with
|
||||
user-intent (i.e. they clicked a button to fire this request, rather than it
|
||||
being automated in the background).
|
||||
|
||||
!!! warning
|
||||
The response from `/ir/direct-manual/import` *MAY* be deferred if the `tachi-server` instance uses score import workers. If this happens **202** will be returned as the status code. You **MUST** then poll the import status to find out what happened to it (such as it failing.)
|
||||
|
||||
## That's it!
|
||||
|
||||
Seriously -- that's it.
|
||||
@@ -1,210 +0,0 @@
|
||||
# What is BATCH-MANUAL?
|
||||
|
||||
BATCH-MANUAL is a JSON format that Tachi accepts.
|
||||
This format can be submitted as [a file](../../api/routes/import.md#import-scores-from-a-file)
|
||||
using the `file/batch-manual` [Import Type](../import/import-types.md), or it can be submitted as a
|
||||
[HTTP request body](./direct-manual.md).
|
||||
|
||||
*****
|
||||
|
||||
## Motivation
|
||||
|
||||
Instead of Tachi writing new support for every kind of
|
||||
possible export, and bothering other service providers
|
||||
to write exports, we could write a generic format we accept
|
||||
and then users with a bit of scripting knowledge can import
|
||||
their own scores.
|
||||
|
||||
This has the additional advantage of allowing extremely
|
||||
obscure imports, and reduces the workload on Tachi.
|
||||
|
||||
## Format
|
||||
|
||||
The format is incredibly simple JSON.
|
||||
|
||||
It is comprised of two base keys, `meta` and `scores`.
|
||||
|
||||
!!! note
|
||||
These keys were originally called `head` and `body` in Kamaitachi. You will have to update
|
||||
existing batch-manual code.
|
||||
|
||||
### Meta
|
||||
|
||||
The `meta` key contains metadata, and looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"service": "foobar"
|
||||
}
|
||||
```
|
||||
|
||||
The fields have the following values:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `game` | Any Game Identifier | The game this import is for. |
|
||||
| `playtype` | Any Playtype for the above game. | The playtype this import is for. |
|
||||
| `service` | String | A humanised string to explain where these scores are from. This must be between 2 and 15 characters. |
|
||||
| `version` (Optional) | String | Optionally, you can specify a version of the game this import is for. This should be used when conflicting versions of songs exist, or when this import is rather old. Most of the time, this does not need to be present. |
|
||||
|
||||
### Scores
|
||||
|
||||
The `scores` property is an array of Batch Manual Scores. An example
|
||||
score is as follows:
|
||||
|
||||
```json
|
||||
{
|
||||
"score": 500,
|
||||
"lamp": "HARD CLEAR",
|
||||
"matchType": "songTitle",
|
||||
"identifier": "5.1.1.",
|
||||
"difficulty": "ANOTHER",
|
||||
"timeAchieved": 1624324467489
|
||||
}
|
||||
```
|
||||
|
||||
The properties are described as this:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Number | The score for this, well, score. This should use the default scoring algorithm for this game. |
|
||||
| `lamp` | Lamp | The lamp for this score. This should be one of the lamps as described in the config for your game + playtype. |
|
||||
| `percent` (Conditional) | Number | Only appears for `jubeat`. This should be set to the Percent for this score. In jubeat's case, this is your Music Rate. |
|
||||
| `matchType` | "songTitle" \| "ddrSongHash" \| "tachiSongID" \| "bmsChartHash" \| "inGameID" \| "uscChartHash" | This determines how `identifier` will be used to match your scores' chart with Tachi's database of songs and charts. |
|
||||
| `identifier` | String | A string that Tachi uses to identify what chart this is for. How this is used depends on the `matchType`. |
|
||||
| `difficulty` (Conditional) | String | If `matchType` is "tachiSongID", "inGameID", "ddrSongHash" or "songTitle", this field must be present, and describe the difficulty of the chart this score is for. |
|
||||
| `artist` (Conditional) | String | If `matchType` is "songTitle", this field can be present, and describe the artist name for less equivocal matching. This field is optional for legacy purposes. |
|
||||
| `timeAchieved` (Optional) | integer \| null | This is *when* the score was achieved in unix milliseconds. This should be provided if possible, as Tachi uses it for a LOT of features. |
|
||||
| `comment` (Optional) | string \| null | A comment from the user about this score. |
|
||||
| `judgements` (Optional) | Record<Game Judgement, integer> | This should be a record of the judgements for your game + playtype, and the integer indicating how often they occurred. |
|
||||
| `optional` (Optional) | See [Game Specific Optional Metrics](../../schemas/score.md#game-specific) | Any optional metrics you wish to provide for this game. |
|
||||
| `scoreMeta` (Optional) | See [Game Specific Score Meta](../../schemas/score.md#game-specific) | This can be a partial record of various `scoreMeta` props for this game. |
|
||||
|
||||
!!! warning
|
||||
`identifier` should always be a string. Even if it's something like a numeric ID! Tachi will handle this.
|
||||
|
||||
!!! warning
|
||||
`timeAchieved` is in **UNIX MILLISECONDS**. Most programming languages use unix seconds. You might have to
|
||||
multiply your timestamps by 1000.
|
||||
|
||||
#### Match Type
|
||||
|
||||
There are many match types, and they all use identifier
|
||||
in a different way.
|
||||
|
||||
- songTitle
|
||||
|
||||
As the name implies, this searches for a song whose title
|
||||
is exactly `identifier`. **THIS IS NOT FUZZY MATCHING**,
|
||||
and is by far the least reliable way to send scores to
|
||||
Tachi. This is kept for compatibility purposes with poor quality APIs.
|
||||
|
||||
This match type *necessitates* that `difficulty` be defined
|
||||
and set to a valid difficulty for this game + playtype.
|
||||
|
||||
This match type can be augmented with a secondary identifier `artist`
|
||||
which resolves title clashes:
|
||||
|
||||
```json
|
||||
{
|
||||
"score": 500,
|
||||
"lamp": "HARD CLEAR",
|
||||
"matchType": "songTitle",
|
||||
"identifier": "5.1.1.",
|
||||
"artist": "dj nagureo",
|
||||
"difficulty": "ANOTHER",
|
||||
"timeAchieved": 1624324467489
|
||||
}
|
||||
```
|
||||
|
||||
- tachiSongID
|
||||
|
||||
This uses `identifier` as if it were an integer, to match
|
||||
songs based on the `id` field of a tachi song.
|
||||
|
||||
This match type *necessitates* that `difficulty` be defined
|
||||
and set to a valid difficulty for this game + playtype.
|
||||
|
||||
- bmsChartHash
|
||||
|
||||
As the name implies, this looks for the chart hash BMS
|
||||
uses. This can be either the MD5 hash or the SHA256 hash,
|
||||
both will match.
|
||||
|
||||
This match type can only be used for BMS.
|
||||
|
||||
- uscChartHash
|
||||
|
||||
This looks for the chart SHA1 that USC uses. As expected, this
|
||||
can only be used for USC.
|
||||
|
||||
- inGameID
|
||||
|
||||
This uses the in-game-ID for this **SONG**. You, therefore,
|
||||
**MUST** specify the difficulty for this chart as well.
|
||||
|
||||
This is supported for the following games:
|
||||
|
||||
- IIDX
|
||||
- Pop'n Music
|
||||
- Jubeat
|
||||
- CHUNITHM
|
||||
- GITADORA
|
||||
- maimai
|
||||
- maimai DX
|
||||
- WACCA
|
||||
- MUSECA
|
||||
|
||||
- sdvxInGameID
|
||||
|
||||
This uses the in-game-ID for this SDVX song. You must specify
|
||||
the difficulty for this chart as well.
|
||||
|
||||
The reason SDVX gets its own special `matchType` is because this
|
||||
matchType supports `difficulty: "ANY_INF"`. This special difficulty
|
||||
means that it will check for any of `INF/GRV/HVN/VVD/XCD` for this song.
|
||||
|
||||
This is useful for services that store all of those as the same difficulty.
|
||||
|
||||
- ddrSongHash
|
||||
|
||||
This is a 32-character hash used by the official DDR e-amusement website to
|
||||
identify a song; you must specify the difficulty for this chart as well.
|
||||
|
||||
## Example
|
||||
|
||||
A final example of a simple BATCH MANUAL format
|
||||
looks like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"service": "My Service"
|
||||
},
|
||||
"scores": [{
|
||||
"score": 500,
|
||||
"lamp": "HARD CLEAR",
|
||||
"matchType": "songTitle",
|
||||
"identifier": "5.1.1.",
|
||||
"artist": "dj nagureo",
|
||||
"difficulty": "ANOTHER"
|
||||
}, {
|
||||
"score": 123,
|
||||
"lamp": "FAILED",
|
||||
"matchType": "tachiSongID",
|
||||
"identifier": "1",
|
||||
"difficulty": "HYPER",
|
||||
"comment": "This score sucked!",
|
||||
"optional": {
|
||||
"bp": 5
|
||||
},
|
||||
"scoreMeta": {
|
||||
"random": "MIRROR"
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
@@ -1,7 +0,0 @@
|
||||
# About
|
||||
|
||||
As you'd expect, this section contains implementation details. Although the entire
|
||||
codebase reference is technically about implementation details, this part is
|
||||
for miscellaneous implementation details that wouldn't fit anywhere else.
|
||||
|
||||
*****
|
||||
@@ -1,22 +0,0 @@
|
||||
# Game Configuration
|
||||
|
||||
Tachi has two sets of game configurations.
|
||||
|
||||
The first is on the game level, which contains things
|
||||
like the humanised name for the game (i.e. `iidx -> beatmania IIDX`).
|
||||
|
||||
The second is for each individual game + playtype
|
||||
combination, which contains things like the list of
|
||||
lamps for the game.
|
||||
|
||||
!!! help
|
||||
This page is unfinished.
|
||||
|
||||
It would be really appreciated if someone formatted
|
||||
the configurations for every game! For the time being,
|
||||
the below documentation is just the raw configuration
|
||||
for each game. Please see [Contributing to Tachi](../../contributing).
|
||||
|
||||
---
|
||||
|
||||
This documentation is unfinished. It is probably easier for you to [just read the config.ts file](https://github.com/zkldi/Tachi/tree/main/common/src/config/config.ts)
|
||||
@@ -1,18 +0,0 @@
|
||||
# Goal ID implementation
|
||||
|
||||
Goal IDs exist to dedupe goals when a user creates
|
||||
a new goal. For example, if a user wants to create a
|
||||
HARD CLEAR Mei goal, but one already exists, we should
|
||||
not insert two [Goal Documents](../../schemas/goal.md)
|
||||
representing the same thing.
|
||||
|
||||
---
|
||||
|
||||
## Hashing
|
||||
|
||||
To create the Goal ID, we perform a [JSON stable hash](https://github.com/zkldi/fast-json-stable-hash) on the
|
||||
`game`, `playtype`, `charts` and `criteria` of this field.
|
||||
|
||||
This enforces that goals are unique on those fields.
|
||||
|
||||
The above hash is then returned, prefixed with `G`.
|
||||
@@ -1,115 +0,0 @@
|
||||
# Goals, Quests, Questlines
|
||||
|
||||
This page covers pretty much everything you need to know about interacting with goals
|
||||
and quests.
|
||||
|
||||
Although these features are fairly straightforward on the surface, their implementation is a bit complex at points. We'll get to it.
|
||||
|
||||
## What is a Goal?
|
||||
|
||||
A goal is a set of instructions that are evaluated for a player if they are subscribed to it (and it is relevant to their current import, see [Getting Relevant Goals](../import/goals.md)).
|
||||
|
||||
!!! example
|
||||
An example goal would be something like "AAA 100 charts in the X Folder".
|
||||
|
||||
Users must subscribe to goals for them to be evaluated when they import scores. This is
|
||||
abstracted away from the user, as the operation to create a goal will automatically subscribe
|
||||
them to the same goal.
|
||||
|
||||
### Identification
|
||||
|
||||
Goals are identified by a checksum of their instructions. If two users make the goal to "AAA Freedom Dive", the goalID will be the same.
|
||||
|
||||
This allows us to do things like see what goals are popular, and reduce the general duplication on the database.
|
||||
|
||||
## User Subscriptions
|
||||
|
||||
Users may create and subscribe to goals. This is done with the [UGPT Create Goals Endpoint](TODO). If they create a goal that already exists, they are just subscribed to that goal.
|
||||
|
||||
If they create a goal that does not already exist, it is created, and they are subscribed.
|
||||
|
||||
Users may unsubscribe from goals that they no longer care about getting pinged for.
|
||||
|
||||
Subscriptions to goals are stored in a [GoalSubscriptionDocument](../../schemas/goal-sub.md).
|
||||
This document is uniquely identified by the joining of the `goalID` with the `userID`.
|
||||
|
||||
### Instant Direct Achievements
|
||||
|
||||
If a user creates/subscribes to a goal that they would instantly achieve, they are denied
|
||||
from adding goal directly.
|
||||
|
||||
### Evaluating A User's Progress
|
||||
|
||||
Goals have two main components in terms of evaluation -- `progress` and `outOf`. If the progress is greater than or equal to the `outOf`, the goal is marked as achieved.
|
||||
|
||||
Both of these values are numbers, and are not intended for human consumption. For example, the goal 'HARD CLEAR Freedom Dive' would be represented by an `outOf` of `6`, which is the internal lampIndex for a BMS HARD CLEAR.
|
||||
|
||||
For human representation, these values are formatted and stored in the Goal Subscription Document as `progressHuman` and `outOfHuman`, respectively. These values are prettified for human users, and leverage game-specific formatting in some cases.
|
||||
|
||||
!!! note
|
||||
Achieved goals are still calculated -- a user may actually find themselves un-achieving goals in some stranger cases.
|
||||
|
||||
If their goal is something like AAA 10% of a folder, and the folder gets larger, they
|
||||
may find themselves losing the achieved status on this goal.
|
||||
|
||||
!!! warning
|
||||
Goal Subscriptions are capped by the MAX_GOAL_SUBSCRIPTIONS [conf.json5](../setup/config.md) property, and defaults to 1000.
|
||||
|
||||
!!! danger
|
||||
Unsubscribing from goals directly is only possible if they do not have any parent quests.
|
||||
More on that below.
|
||||
|
||||
## What is a Quest?
|
||||
|
||||
Quests are groups of goals, defined in a structured form, with support for assigning
|
||||
notes next to goalIDs, and other generally useful stuff.
|
||||
|
||||
Their purpose for users is to give them a quick way to assign a bunch of goals they might care about.
|
||||
|
||||
For example, a dedicated user may make their own "Kaiden Checklist", and create a set of goals
|
||||
they think are useful for kaidens to aim for. Another user interested in this can just
|
||||
subscribe to that quest, and all the goals will be managed for them.
|
||||
|
||||
### Identification
|
||||
|
||||
Quests are identified by their `questID`, which is a completely arbitrary string.
|
||||
|
||||
At the moment, users cannot create quests directly, they are hard-defined by the [Database Seeds](../infrastructure/seeds.md).
|
||||
|
||||
### Evaluating a User's Progress
|
||||
|
||||
Similarly to goals, user's can subscribe to quests.
|
||||
|
||||
Quest subscriptions are interesting in that the `progress` factor is always an integer -- how many goals in the quest the user has achieved, and the `outOf` factor is always an integer -- how many goals need to be achieved in the quest for the quest to be marked as achieved.
|
||||
|
||||
Similarly, the `achieved` status on quests is identical to `progress` being greater than or equal to `outOf`.
|
||||
|
||||
### Parenting Goal Subscriptions
|
||||
|
||||
When a quest is subscribed to, all the goals in the quest are also subscribed to.
|
||||
|
||||
When a user is subscribed to a quest, they *must* also be subscribed to all the goals in
|
||||
that quest. If they aren't, they've desynced with the quest, and that's an awful
|
||||
user experience.
|
||||
|
||||
As such, Tachi keeps track of all the quests that care about this goal subscription, and users are *prevented from unsubscribing from this goal* while any of their quest subscriptions parent the goal.
|
||||
|
||||
### Instant Indirect Achievements
|
||||
|
||||
Subscribing to a quest *mandates* that all of the goals are assigned. It is not rare for this to mean that you subscribe to goals that are instantly achieved.
|
||||
|
||||
This is fine. If this does happen though, `wasInstantlyAchieved` is set on the goal, and no
|
||||
webhook event is emitted.
|
||||
|
||||
### Unsubscribing
|
||||
|
||||
For all `goalID`s in the quest, we check if this goal subscription has any other parent quests.
|
||||
|
||||
If this quest was the only reason the goal was assigned (i.e. it wasn't assigned directly, or isn't part of another quest), this quest will be unsubscribed from.
|
||||
|
||||
## Questlines
|
||||
|
||||
Questline are ordered lists of quests. Their purpose is to group quests together
|
||||
visually in an ordered manner.
|
||||
|
||||
Users *can not* "subscribe" to questlines, but they can use questlines as a utility for subscribing to all the related quests.
|
||||
@@ -1,42 +0,0 @@
|
||||
# Score ID implementation
|
||||
|
||||
Score IDs exist to dedupe scores when a user re-submits
|
||||
the same scores. This happens frequently with `file/`
|
||||
and `api/` [Import Types](../import/import-types.md),
|
||||
as they typically resubmit the same scores.
|
||||
|
||||
If a user only got a score once, we don't want to store
|
||||
it twice.
|
||||
|
||||
Sadly, we can't depend on things like timestamps to assert
|
||||
whether or whether not we've saw a score before. Many services
|
||||
alter/tamper their timestamps such that they're unreliable.
|
||||
|
||||
!!! example
|
||||
E-Amusement IIDX CSVs will change the timestamp of every single
|
||||
score when a new version comes out to the second the user created
|
||||
their new account.
|
||||
|
||||
*****
|
||||
|
||||
## Hashing
|
||||
|
||||
The score ID is created by joining the following properties:
|
||||
|
||||
- The `userID` that got this score
|
||||
- The `chartID` that this score was on
|
||||
- All [Provided Metrics](todo) for this GPT
|
||||
- Any [Optional Metrics](todo) that have been marked as `partOfScoreID`
|
||||
|
||||
and then hashing them with SHA256.
|
||||
|
||||
This is then prefixed with `T`, and returned.
|
||||
|
||||
## Clobbering
|
||||
|
||||
Since a scoreID isn't necessarily all of the possible statistics for a score, it's
|
||||
possible for users to "clobber" their scores, by importing a score without as many
|
||||
pieces of info (i.e. no judgements), then trying to import that same score with
|
||||
judgements later will not work, as the scoreID sees it as a duplicate.
|
||||
|
||||
See [Clobbering](todo).
|
||||
@@ -1,43 +0,0 @@
|
||||
# Search Implementation
|
||||
|
||||
Tachi's search implementation uses MongoDB's $text index. This breaks a query into words
|
||||
and compares each of them to the provided text fields.
|
||||
|
||||
*****
|
||||
|
||||
## __textScore
|
||||
|
||||
For our code, we mutate the documents we want to return with a special field: `__textScore`.
|
||||
|
||||
This field declares how 'close' the provided query was to the $text fields in this document.
|
||||
|
||||
This is sometimes exposed in the API for sorting reasons.
|
||||
|
||||
!!! bug
|
||||
MongoDB's $text matching algorithm isn't great for fuzzy matches - It doesn't
|
||||
like song titles like 'A', as it thinks 'a' is an article, and doesn't match it properly as
|
||||
a word.
|
||||
|
||||
!!! info
|
||||
Why not regex for fuzzy matches?
|
||||
|
||||
Regex has performance issues on larger datasets and we
|
||||
want to avoid it. Most regexes cannot use indexes, and therefore invoke a COLLSCAN, which
|
||||
we want to avoid.
|
||||
|
||||
## User Searching
|
||||
|
||||
Searching users, on the other hand, has to use regex-based
|
||||
searching.
|
||||
|
||||
The `$text` method attempts to break things up based on their
|
||||
words, but that doesn't help with usernames, as they are all
|
||||
too frequently `XxX_One_Long_Str1ng_xXx`.
|
||||
|
||||
Instead, we use a case insensitive regex - similar to SQL's
|
||||
`LIKE`.
|
||||
|
||||
This means we do not have a `__textScore` property for this
|
||||
search to sort on. Instead, we just constrict returns to
|
||||
around 15, and have the user whittle their search down
|
||||
better.
|
||||
@@ -1,121 +0,0 @@
|
||||
# Songs And Charts
|
||||
|
||||
Tachi structures its song and chart data in a specific
|
||||
manner in order to avoid copying properties all over
|
||||
the place.
|
||||
|
||||
*****
|
||||
|
||||
## Songs
|
||||
|
||||
Songs look like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "5.1.1.",
|
||||
"artist": "dj nagureo",
|
||||
"id": 1,
|
||||
"firstVersion": "0",
|
||||
"alt-titles": [],
|
||||
"search-titles": [],
|
||||
"data": {
|
||||
"genre": "PIANO AMBIENT"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
They're relatively small documents, and contain some metadata.
|
||||
|
||||
Depending on the game, the `data` prop will contain
|
||||
game-specific properties (i.e. not all games have
|
||||
song genres!)
|
||||
|
||||
Songs, however, don't contain any information about
|
||||
their *charts*. The charts are what people actually play.
|
||||
|
||||
In short, songs are *just* a collection of metadata that
|
||||
parents a chart document!
|
||||
|
||||
*****
|
||||
|
||||
## Chart Documents
|
||||
|
||||
Chart Documents **MUST** belong to a song. In the below
|
||||
chart, `songID` refers to the above song document:
|
||||
|
||||
```json
|
||||
{
|
||||
"chartID": "c2311194e3897ddb5745b1760d2c0141f933e683",
|
||||
"difficulty": "ANOTHER",
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"levelNum": 10,
|
||||
"level": "10",
|
||||
"data": {
|
||||
"inGameID": 1000,
|
||||
"notecount": 786,
|
||||
},
|
||||
"isPrimary": true,
|
||||
"versions": [
|
||||
"27-omni",
|
||||
"26-omni",
|
||||
"27",
|
||||
"26",
|
||||
"inf",
|
||||
"16-cs",
|
||||
"12-cs",
|
||||
"10-cs",
|
||||
"8-cs",
|
||||
"7-cs",
|
||||
"bmus"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Songs can have multiple charts, but charts can only have
|
||||
one song.
|
||||
|
||||
## Primary
|
||||
|
||||
Sometimes, games like to rechart things and release them
|
||||
under the exact same song. Sometimes, they even do this
|
||||
under the *exact* same internal songID!
|
||||
|
||||
A 'Primary' chart refers to a chart that is the *current*
|
||||
variant of that chart for this game. Non-Primary charts
|
||||
are not eligible for any rating calculations, nor do they
|
||||
contribute to profile rating.
|
||||
|
||||
To check if a chart is primary or not, the `isPrimary` property
|
||||
handles that.
|
||||
|
||||
For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"chartID": "103ff8bb004e1a8a005f808c025c3feb",
|
||||
"difficulty": "ANOTHER",
|
||||
"songID": 1,
|
||||
"playtype": "SP",
|
||||
"levelNum": 5,
|
||||
"level": "5",
|
||||
"data": {
|
||||
"inGameID": 1000,
|
||||
"notecount": 433
|
||||
},
|
||||
"isPrimary": false,
|
||||
"versions": [
|
||||
"1"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Notice that the chartID is different, and `isPrimary`
|
||||
is set to false.
|
||||
|
||||
Even though this chart is "5.1.1 (SP ANOTHER)", it isn't
|
||||
the primary chart for this songID + playtype + difficulty.
|
||||
|
||||
For the UI, Tachi will hide non-primary charts by default.
|
||||
Realistically, they only exist to support legacy scores
|
||||
without having to throw them away when a rechart occurs.
|
||||
@@ -1,60 +0,0 @@
|
||||
# Converter Failures
|
||||
|
||||
This page contains the list of possible converter fail
|
||||
states that can be thrown. All of them can be found in
|
||||
`src/lib/score-import/common/converter-failures.ts`.
|
||||
|
||||
*****
|
||||
|
||||
## SkipScoreFailure
|
||||
|
||||
This is a special case of a failure, in that it isn't
|
||||
logged or marked as an error in the import. This call
|
||||
just means we should skip the score.
|
||||
|
||||
An example case of this would be something like the
|
||||
eamusement IIDX CSV format, which can pass 'empty' scores
|
||||
for a chart. We would want to skip over those, and they
|
||||
aren't an error.
|
||||
|
||||
When this is thrown, the score is skipped, and nothing is
|
||||
imported.
|
||||
|
||||
## SongOrChartNotFoundFailure
|
||||
|
||||
!!! bug
|
||||
KTData means 'Kamaitachi Data', but this applies to
|
||||
Bokutachi just as much, it is just a legacy name
|
||||
holdover.
|
||||
|
||||
This failure means that the score could not match with
|
||||
anything in the database, such as giving a chartID that
|
||||
Tachi does not have in its database.
|
||||
|
||||
This failure also takes the current data, importType and
|
||||
parser context. This is so we can create [Orphan Scores](./orphans.md).
|
||||
|
||||
## Invalid Score Failure
|
||||
|
||||
This score provided invalid data that we cannot process.
|
||||
|
||||
An example of this would be an IIDX score with -1 EX Score.
|
||||
|
||||
## Internal Failure
|
||||
|
||||
This is an internal failure, and thrown when something bad
|
||||
has occurred on our end.
|
||||
|
||||
The most common case for throwing this is the "Song-Chart"
|
||||
desync. This occurs when a chart has no parent song (and
|
||||
a chart must have a parent song.)
|
||||
|
||||
!!! info
|
||||
This is generally used for states that should never happen,
|
||||
but we want to handle them nicely anyway.
|
||||
|
||||
## Any Exception
|
||||
|
||||
If any exception is thrown from a converter that is **NOT**
|
||||
an `instanceof` ConverterFailure, it is treated as an
|
||||
InternalFailure.
|
||||
@@ -1,38 +0,0 @@
|
||||
# Updating Goals
|
||||
|
||||
If a user has goals set, we need to update their
|
||||
progress on those goals as a result of this import.
|
||||
|
||||
*****
|
||||
|
||||
## Getting Relevant Goals
|
||||
|
||||
In V1 of Tachi, we fetched *all* goals for a user,
|
||||
and recalculated all of them.
|
||||
|
||||
This was fine for users with some goals, but for users with
|
||||
lots of goals, it quickly became a huge performance boon.
|
||||
|
||||
To avoid that, Tachi V2 fetches only the "relevant" goals
|
||||
as a result of the import. This is calculated as follows:
|
||||
|
||||
- Single Goals are matched if their chartID is inside the set of chartIDs affected.
|
||||
|
||||
- Multi Goals are matched if their data contains any chartIDs that were affected.
|
||||
|
||||
- Folder goals are matched if the folder contains any charts inside the set of chartIDs affected.
|
||||
|
||||
## Processing Goals
|
||||
|
||||
For every goal matched, we iterate over it and evaluate
|
||||
it using `EvaluateGoalForUser`.
|
||||
|
||||
We then need to convert the returns of that function into
|
||||
the expected goal format for ImportDocuments.
|
||||
|
||||
That means we have to compare the old UserGoal's progress
|
||||
with the newly returned one.
|
||||
|
||||
If nothing has changed, we return undefined. If something
|
||||
has changed, we return a `bwrite` key that contains a
|
||||
MongoDB bulk-write operation to update the UserGoal, and the expected import format of goalID, old, new.
|
||||
@@ -1,35 +0,0 @@
|
||||
# Import Documents and Import Timings
|
||||
|
||||
The final step of the process is to coalesce all the returns
|
||||
of the various steps above into one analysable document
|
||||
and return it.
|
||||
|
||||
*****
|
||||
|
||||
## Logging
|
||||
|
||||
If the import had over 500 scores, we log the import at Info level. This is because those are generally rare, and we
|
||||
want to know how our performance is doing at a glance.
|
||||
|
||||
If the import has over 1 score, it is logged as verbose.
|
||||
|
||||
Else, the import is logged as debug.
|
||||
|
||||
## Timings
|
||||
|
||||
An internal document - Import Timings - is created by
|
||||
storing the time each step of the import process took
|
||||
in milliseconds.
|
||||
|
||||
This is analysed regularly to check for performance
|
||||
degradation and look for potential optimisations.
|
||||
|
||||
This is also stored in the database.
|
||||
|
||||
## Returns
|
||||
|
||||
The Import Document **is** the return from ScoreImportMain.
|
||||
|
||||
This document contains all the relevant information
|
||||
about the import and what it has resulted in, and
|
||||
is also stored in the database under a random ID.
|
||||
@@ -1,64 +0,0 @@
|
||||
# Import Types
|
||||
|
||||
The score import code for Tachi uses 'Import Types' to
|
||||
determine what kind of code to run on a given import,
|
||||
and what converter to call (more on that later.)
|
||||
|
||||
*****
|
||||
|
||||
## Format
|
||||
|
||||
An import type is just a string. It is formatted as follows:
|
||||
|
||||
```
|
||||
type/name
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
There are three types.
|
||||
|
||||
| Type | Description |
|
||||
| :: | :: |
|
||||
| `file/` | The data is coming from a file, such as one from a multipart form submission. |
|
||||
| `ir/` | The data is coming from a HTTP Request sent to us. |
|
||||
| `api/` | The data is being fetched by us from another service through HTTP Requests. |
|
||||
|
||||
## User Intent
|
||||
|
||||
Some imports are performed with user intent, such as
|
||||
the user going to the Tachi website and uploading
|
||||
a file and clicking submit.
|
||||
|
||||
Some imports are performed without user intent, i.e.
|
||||
the user sets up a score hook to automatically import
|
||||
their scores whenever they get one. The reason for the
|
||||
distinction is for hiding "automatic" imports from the
|
||||
users imports page.
|
||||
|
||||
For certain endpoints, the special header `X-User-Intent`
|
||||
is set to indicate that this was done with User Intent.
|
||||
|
||||
!!! warning
|
||||
As this is just a convention, you can abuse it, but you
|
||||
shouldn't, because it's not nice to break things
|
||||
(and you may be banned).
|
||||
|
||||
## List of Import Types
|
||||
|
||||
| ImportType | Description | Availability | User Intent |
|
||||
| :: | :: | :: | :: |
|
||||
| `file/eamusement-iidx-csv` | The E-amusement CSV format for IIDX. This type accepts both Pre-HV and Post-HV formats. | Kamaitachi | Yes |
|
||||
| `file/batch-manual` | The Tachi BATCH-MANUAL format submitted through a multipart form. | Kamaitachi & Bokutachi | Yes |
|
||||
| `file/solid-state-squad` | The IIDX XML output by Solid State Squad. | Kamaitachi | Yes |
|
||||
| `file/pli-iidx-csv` | Same as eamusement's IIDX CSV, but output from PLI. | Kamaitachi | Yes |
|
||||
| `ir/direct-manual` | The Tachi BATCH-MANUAL format but submitted in a HTTP Request body as `application/json`. | Kamaitachi & Bokutachi | Depends on Header |
|
||||
| `ir/barbatos` | Barbatos's format submitted in a HTTP Request Body. | Kamaitachi | No |
|
||||
| `ir/fervidex` | Fervidex's score format submitted in a HTTP Request Body. | Kamaitachi | No |
|
||||
| `ir/fervidex-static` | Fervidex's profile sync format submitted in a HTTP Request Body. | Kamaitachi | No |
|
||||
| `ir/usc` | An implementation of the [USCIR](https://uscir.rtfd.io)'s POST /scores endpoint. | Bokutachi | No |
|
||||
| `ir/beatoraja` | A handler for BokutachiIR's score format. | Bokutachi | No |
|
||||
| `api/flo-iidx`, `api/eag-iidx` | Both the same IIDX format, but yielded from different APIs with different players. | Kamaitachi | Depends on Calling[^1] |
|
||||
| `api/flo-sdvx`, `api/eag-sdvx`, `api/min-sdvx`| See above, but for SDVX. | Kamaitachi | Depends on Calling[^1] |
|
||||
|
||||
[^1]: If this is synced manually by the user, I.e. they have called it to be synced, then it is with intent. If it was called for them through automatic synchronisation, then it was not done with user intent.
|
||||
@@ -1,108 +0,0 @@
|
||||
# Importing DryScores
|
||||
|
||||
This page goes over hydrating a DryScore into a fully
|
||||
fledged Tachi score, and then goes over how it is imported.
|
||||
|
||||
!!! warning
|
||||
Performance has been squeezed out of this process
|
||||
rather aggressively, as such, some of the more
|
||||
'obvious' ways to do things have been ignored
|
||||
for performance gains.
|
||||
|
||||
!!! info
|
||||
The code that handles this logic is found in
|
||||
`src/lib/score-import/framework/score-importing/score-importing.ts`.
|
||||
|
||||
*****
|
||||
|
||||
## Return Format
|
||||
|
||||
The `ImportIterableDatapoint` returns
|
||||
`ImportProcessingInfo` or `null`. The format for the former
|
||||
can be found in `tachi-common`, and **should be read** before
|
||||
following this document!
|
||||
|
||||
As a rough outline:
|
||||
```ts
|
||||
{
|
||||
// whether this import worked or not
|
||||
success: boolean;
|
||||
// ScoreImported implies success: true, and all others imply success: false.
|
||||
type: "ScoreImported" | "SongOrChartNotFound" // ... and more;
|
||||
// An error message.
|
||||
message: String | null;
|
||||
// Some errors return some data about the error, or things like scoreImported returns the score that was imported.
|
||||
content: see_implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Dealing with converter returns
|
||||
|
||||
As mentioned in [Parsers and Converters](./parse-conv.md),
|
||||
converters will return a DryScore, and its matching chart
|
||||
and song on success.
|
||||
|
||||
However, failures are also an expected throw from a
|
||||
converter function. We handle these throws by logging
|
||||
them dependent on their severity, and returning
|
||||
that this score has failed to be imported properly
|
||||
in the `ImportProcessingInfo` format.
|
||||
|
||||
If the converter was successful, we now have a DryScore, song and chart to work with.
|
||||
|
||||
## Hydration
|
||||
|
||||
Our first step is to turn that DryScore into a 'real score'.
|
||||
|
||||
!!! info
|
||||
The code for this is found in `HydrateScore`.
|
||||
|
||||
Before anything, we calculated the ScoreID for this score.
|
||||
This is used to dedupe scores, and is a checksum of
|
||||
the following properties.
|
||||
|
||||
- userID
|
||||
- chartID
|
||||
- lamp
|
||||
- grade
|
||||
- score
|
||||
- percent
|
||||
|
||||
We fill out all the properties that can be calculated
|
||||
about the score, such as the aptly named `calculatedData`,
|
||||
and things like `gradeIndex` and `lampIndex`, which are
|
||||
just the index of the grade/lamp string in the set of grade/lamps.
|
||||
|
||||
Other properties, such as `timeAdded` (the time this data was inserted into the database) can be trivially attached onto the score.
|
||||
|
||||
## Insertion
|
||||
|
||||
Now that we have a real Tachi score, we can insert it into
|
||||
the database.
|
||||
|
||||
The obvious solution here is something like:
|
||||
```ts
|
||||
await db.scores.insert(scoreDocument);
|
||||
```
|
||||
|
||||
But this has performance implications for large sets of
|
||||
scores.
|
||||
|
||||
To solve these performance issues, we use a queue to insert
|
||||
scores.
|
||||
|
||||
ScoreDocuments are appended to a queue of 500.
|
||||
If the queue hits 501 scores, the queue is bulk-written to
|
||||
the database. At the end of hydrating and queueing all scores, the queue is flushed one last time.
|
||||
|
||||
This simple optimisation provides large performance benefits.
|
||||
|
||||
## Final Returns
|
||||
|
||||
Importing each datapoint gives us an `ImportProcessingInfo`
|
||||
object OR `null`. We want to return only the former,
|
||||
as `null` is for skipped scores.
|
||||
|
||||
We filter out all the nulls and flush the queue before
|
||||
returning the array of `ImportProcessingInfo` objects
|
||||
that were returned from our import process.
|
||||
@@ -1,155 +0,0 @@
|
||||
# Score Import Main
|
||||
|
||||
This page refers to the entry point function for a score
|
||||
import.
|
||||
|
||||
This function can be found at `src/lib/score-import/framework/score-import-main.ts`.
|
||||
|
||||
## Signature
|
||||
|
||||
`ScoreImportMain` takes four arguments and an optional fifth.
|
||||
|
||||
| Argument | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `user` | [UserDocument](../../schemas/user.md) | The user that is making this import request. |
|
||||
| `userIntent` | boolean | Whether this import was performed with User Intent - See [Import Types](./import-types.md#user-intent) |
|
||||
| `importType` | ImportType | What kind of import "type" this is. For more on this, see [Import Types](./import-types.md)
|
||||
| `InputParser` | Function | The parser function to call. For more info, see [Parsing and Converting](./parse-conv.md)
|
||||
| `providedImportObjects` (Optional) | { logger, importID } | Optionally, a logger and existing importID can be passed here. This is used for scenarios where the logger and importID have already been created before importMain was called. |
|
||||
|
||||
## Import Logger
|
||||
|
||||
For ease of debugging, a logger is passed between most
|
||||
functions in the score importing process. This logger
|
||||
contains the context of the currently importing user
|
||||
and the ImportID.
|
||||
|
||||
## Steps
|
||||
|
||||
To make the process easier to parse, the importing process
|
||||
is split into multiple steps. These steps may split into
|
||||
further sub-steps, depending on how much occurs in said
|
||||
step.
|
||||
|
||||
!!! tip
|
||||
These steps are also commented inline in
|
||||
`score-import-main.ts`.
|
||||
|
||||
!!! warning
|
||||
These steps are only an outline of the process!
|
||||
|
||||
For more information on each specific step, links
|
||||
are given.
|
||||
|
||||
### Setup
|
||||
|
||||
If `providedImportObjects` is not given, then ScoreImportMain generates a new Logger and an ImportID.
|
||||
|
||||
### Parsing
|
||||
|
||||
This step calls the ParserFunction provided as an argument
|
||||
to this function. The returns from that are then used
|
||||
in the below steps.
|
||||
|
||||
- Full Page: [Parsing and Converting](./parse-conv.md)
|
||||
|
||||
### Importing
|
||||
|
||||
We iterate over the `iterable` returned from the Parser Function,
|
||||
calling the Converter Function for each element.
|
||||
|
||||
Once we have converted the element, we can fill it in
|
||||
and insert it into the database.
|
||||
|
||||
This process returns an array called the "Import Info".
|
||||
|
||||
This object holds information about the set of charts this
|
||||
import involved, and the set of scores that were imported.
|
||||
|
||||
- Full Page (Converting): [Parsing and Converting](./parse-conv.md)
|
||||
- Full Page (Importing): [Importing](./importing.md)
|
||||
|
||||
!!! info
|
||||
This step does most of the work, and is where a
|
||||
lot of our complexity lies.
|
||||
|
||||
### Parse Import Info
|
||||
|
||||
The next steps require certain information from our Import Info.
|
||||
|
||||
This step parses that data into some values we have use for, such as the set of chartIDs impacted
|
||||
by this import.
|
||||
|
||||
- Full Page: [Parsing ImportProcessingInfo](./parse-ipi.md)
|
||||
|
||||
### Sessions
|
||||
|
||||
Now that we have imported the scores and have got
|
||||
an array of scoreIDs newly created from Parse Import Info,
|
||||
we can move on to creating sessions from this import.
|
||||
|
||||
- Full Page: [Sessions](./sessions.md)
|
||||
|
||||
### Personal Bests
|
||||
|
||||
This step updates the 'personal bests' for the user. It
|
||||
iterates over every unique chartID modified in this import
|
||||
and conjoins the users best scores on the chart into one document.
|
||||
|
||||
This is to have a single document that has the users best
|
||||
lamp and score, which avoids having to join it together
|
||||
whenever we want to display a users best score and lamp
|
||||
at the same time.
|
||||
|
||||
- Full Page: [Personal Bests](./pbs.md)
|
||||
|
||||
### Game Stats
|
||||
|
||||
This step updates the user's Game Stats.
|
||||
|
||||
If this is the users first play on this game, then we create
|
||||
game stats for them.
|
||||
|
||||
The update is performed by recalculating their statistics
|
||||
with the new scores inserted.
|
||||
|
||||
!!! info
|
||||
This section is referred to as UGS sometimes internally.
|
||||
|
||||
This is short for User Game Stats.
|
||||
|
||||
- Full Page: [User Game Stats](./ugs.md)
|
||||
|
||||
### Goals
|
||||
|
||||
This step checks the charts modified in this import and
|
||||
retrieves all the relevant goals. It then checks
|
||||
all of these goals for whether their status has
|
||||
changed or not, and updates them accordingly.
|
||||
|
||||
- Full Page: [Goals](./goals.md)
|
||||
|
||||
### Quests
|
||||
|
||||
Checks the set of goals modified in the previous step,
|
||||
and gets the relevant quests. This step then re-evaluates all of those quests and
|
||||
updates accordingly.
|
||||
|
||||
- Full Page: [Quests](./quests.md)
|
||||
|
||||
### Import Document
|
||||
|
||||
Finally, we take all of the returns of the above steps
|
||||
and combine them into an import document.
|
||||
|
||||
This is inserted to the database, and is also the return
|
||||
value of this function.
|
||||
|
||||
### Import Timings
|
||||
|
||||
Every step in this process is timed in milliseconds.
|
||||
|
||||
The time each step took is saved alongside the ImportID.
|
||||
|
||||
This is used for internal debugging and degraded performance
|
||||
checks.
|
||||
@@ -1,33 +0,0 @@
|
||||
# Orphan Scores
|
||||
|
||||
SongOrChartNotFoundFailures state that we don't have the data
|
||||
to understand what chart/song this score is for.
|
||||
|
||||
However, that doesn't necessarily imply we won't have that
|
||||
data in the future. Orphaned scores are a way of storing
|
||||
score-like data without a song or chart as a parent.
|
||||
|
||||
Then, when the song and chart are made available, those
|
||||
scores can be imported as if nothing had changed!
|
||||
|
||||
*****
|
||||
|
||||
## How do they work?
|
||||
|
||||
Orphaned scores store the `data` and `context` from the parser.
|
||||
|
||||
When unorphaning is attempted, the Converter Function is
|
||||
called with that data and context.
|
||||
|
||||
If it results in a SongOrChartNotFoundFailure, we do nothing.
|
||||
|
||||
If it results in a success, we have a new DryScore we can import.
|
||||
|
||||
!!! info
|
||||
If another type of failure occurs - i.e. InvalidScoreFailure,
|
||||
then we remove the orphaned score from the DB.
|
||||
|
||||
!!! note
|
||||
This is why Converter Functions are completely
|
||||
static, so we can safely call the same Converter
|
||||
Function with this data to unorphan scores.
|
||||
@@ -1,32 +0,0 @@
|
||||
# Score Import Overview
|
||||
|
||||
Score importing in Tachi is *very* complex, and involves
|
||||
a lot of moving parts. For that reason, the entire process
|
||||
(and my rationale behind each step) is documented here.
|
||||
|
||||
The code for Score Importing can be found at
|
||||
`src/lib/score-import`.
|
||||
|
||||
The entry point function is `src/lib/score-import/framework/score-import-main.ts`, and everything else is located nearby.
|
||||
|
||||
*****
|
||||
|
||||
## Folders
|
||||
|
||||
Inside `src/lib/score-import` is two folders, `framework/`
|
||||
and `import-types`.
|
||||
|
||||
### `framework/`
|
||||
|
||||
The framework folder contains the 'moving parts' of the score importing,
|
||||
such as processing pbs and inserting scores.
|
||||
|
||||
This folder contains the "entry point" for the importing
|
||||
mechanism, which is discussed in [Score Import Main](./main.md).
|
||||
|
||||
### `import-types/`
|
||||
|
||||
The Import-Types folder contains the specific parsers
|
||||
and converter functions for a given ImportType.
|
||||
|
||||
You can read more about import types [here](./import-types.md).
|
||||
@@ -1,324 +0,0 @@
|
||||
# Parsing and Converting
|
||||
|
||||
Tachi faces an interesting problem with its score importing
|
||||
code. We could rewrite our entire score importing process
|
||||
for every possible import method, or we could try to
|
||||
factor out the common parts of this process.
|
||||
|
||||
The latter is what we've gone for, but it isn't without
|
||||
its own traps.
|
||||
|
||||
We face the first obvious problem when it comes to actually
|
||||
"getting" the scores from a source and turning them into
|
||||
our format.
|
||||
|
||||
Our source needs to be dynamically set, sometimes we might
|
||||
be drawing scores from a file, sometimes from a HTTP
|
||||
request body, sometimes from another API!
|
||||
|
||||
Then, we need to convert the data that document gave us
|
||||
into a format the rest of our code to understand.
|
||||
|
||||
To solve this problem, Tachi uses two processes,
|
||||
Parsing and Converting.
|
||||
|
||||
## Outline
|
||||
|
||||
As a rough outline, the parser converts data from
|
||||
any source into an iterable.
|
||||
|
||||
The iterable is then traversed, and the Converter
|
||||
is called for every element of the iterable.
|
||||
|
||||
The converter function turns the element into something
|
||||
the rest of the Tachi import code can work with.
|
||||
|
||||
## Parsing
|
||||
|
||||
Parsing is the job of drawing the data from the source into
|
||||
an **iterable** (The usage of iterable and **NOT** array will be explained later).
|
||||
|
||||
A parser function is intended to be created *outside* of
|
||||
score-import-main, which means it can be really whatever
|
||||
it wants to be, and draw data from whatever source it needs
|
||||
to.
|
||||
|
||||
!!! warning
|
||||
Parser functions return more than just an iterable,
|
||||
but in the interest of introducing complexity slowly
|
||||
we're going to pretend that they only return an
|
||||
iterable for now.
|
||||
|
||||
### Implementation
|
||||
|
||||
As an example, let's say we had a CSV being sent to us as a file.
|
||||
|
||||
We could write a parser for that as follows:
|
||||
|
||||
```js
|
||||
// note: awful example code, this is NOT how to parse a CSV!
|
||||
function ParserFunction(request) {
|
||||
let csv = request.file;
|
||||
|
||||
return csv.toString().split("\n");
|
||||
}
|
||||
```
|
||||
|
||||
This takes the provided file, splits it on newlines, and returns
|
||||
the array. This meets all our criteria, as it returns
|
||||
an iterable, but we still have problems.
|
||||
|
||||
Firstly, the calling signature for this involves a `request`. Score Import Main doesn't know of any HTTP requests, as it might not even be triggered by one.
|
||||
|
||||
We could solve this by passing a request object, and
|
||||
also any other context a parser could possibly need,
|
||||
but that quickly breaks down when we start needing to
|
||||
fetch from APIs.
|
||||
|
||||
Instead, we define a ParserFunction is defined to be a function that
|
||||
takes *one* argument, the Import Logger.
|
||||
|
||||
This means that we actually have to use some
|
||||
closures in order to call a parser.
|
||||
|
||||
```js
|
||||
function CreateParser(request) {
|
||||
return (logger) => {
|
||||
return request.file.toString().split("\n");
|
||||
}
|
||||
}
|
||||
|
||||
const ParserFunction = CreateParser(request);
|
||||
```
|
||||
|
||||
Using closures, we can dynamically create the parser function
|
||||
and enclose any context it needs to use inside of it.
|
||||
|
||||
This approach allows ScoreImportMain to continue knowing
|
||||
nothing about where the data is coming from, and
|
||||
avoids passing lots of initial context, bloating up the
|
||||
score import function.
|
||||
|
||||
The iterable returned by the parser should be in the
|
||||
most immediately sensible format.
|
||||
|
||||
!!! info
|
||||
Parser Functions may be asynchronous.
|
||||
|
||||
### Iterable Vs. Array
|
||||
|
||||
Above, we specifically mentioned that the Parser function
|
||||
returns an *iterable*. There's a subtle difference here
|
||||
that allows for significant performance gains when getting
|
||||
data from a paginated API.
|
||||
|
||||
Let's write an example where we retrieve data from an API.
|
||||
|
||||
```js
|
||||
function CreateParser() {
|
||||
return async (logger) => {
|
||||
let page = 1;
|
||||
|
||||
let array = [];
|
||||
while (true) {
|
||||
// pretend this returns some scores
|
||||
|
||||
let res = await fetch("example.com?page=" + page).then(r => r.json());
|
||||
|
||||
array.push(...res.scores);
|
||||
|
||||
if (!res.moreData) {
|
||||
break;
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It's not bad, but it has some performance issues if the
|
||||
user has a lot of scores, by creating a massive array
|
||||
in memory and *then* passing it.
|
||||
|
||||
We can improve this code significantly using generators.
|
||||
|
||||
```js
|
||||
function CreateParser() {
|
||||
return async function* (logger) {
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
let res = await fetch("example.com?page=" + page).then(r => r.json());
|
||||
|
||||
for (const score of res.scores) {
|
||||
yield score;
|
||||
}
|
||||
|
||||
if (!res.moreData) {
|
||||
break;
|
||||
}
|
||||
|
||||
page++;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With generators, this code can now yield each score from
|
||||
the data returned, instead of having to bubble it up in
|
||||
one large array.
|
||||
|
||||
### Other Returns
|
||||
|
||||
We mentioned above that parsers return more than
|
||||
just an iterable.
|
||||
|
||||
In practice, a Parser returns four things in an object. Context, the Game this import is for, the Iterable discussed above and a Class Handler.
|
||||
|
||||
#### `context`
|
||||
|
||||
Some import formats require context to be parsed properly.
|
||||
|
||||
The most obvious example is the IIDX E-Amusement CSV format, which does not declare whether the scores are from
|
||||
single or double play inside the file.
|
||||
|
||||
To properly interpret a score of, say, 1000 on 5.1.1. ANOTHER, we need to have some context about the score.
|
||||
That is, we need data that isn't part of the 'score'.
|
||||
|
||||
To solve this, parsers return a `context` object which
|
||||
contains context for the converter function.
|
||||
|
||||
For IIDX E-Amusement, the parser function could interpret
|
||||
the playtype from user input, and then return it as context.
|
||||
|
||||
#### `game`
|
||||
|
||||
Sometimes, the game an import is for is not statically
|
||||
known from the Import Type. This means that the parser
|
||||
will have to return the `game` this import is for rather
|
||||
than inferring it.
|
||||
|
||||
An example of a format doing this would be the [BATCH MANUAL](foo) format,
|
||||
which declares what game the import is for in a header.
|
||||
|
||||
#### `classHandler`
|
||||
|
||||
Sometimes, an import may result in the changing of a users'
|
||||
classes. This may also sometimes be dependent on data that
|
||||
is part of the import.
|
||||
|
||||
An example of this would be the Fervidex Static import type,
|
||||
which passes a set of the users scores, and also passes
|
||||
their Dans.
|
||||
|
||||
Since dans are part of the user's IIDX classes, we need to
|
||||
take the information from that request and update classes
|
||||
accordingly.
|
||||
|
||||
The classHandler returned from the parser should be a callable function
|
||||
that will update the user's classes dependent on this data.
|
||||
|
||||
!!! info
|
||||
If no class information is dependent on the content
|
||||
known by the parser function, then `classHandler`
|
||||
can be set to null, and it will not be called.
|
||||
|
||||
### Final Example
|
||||
|
||||
With all these other things to return, we can
|
||||
write a simple example of a parser as follows:
|
||||
|
||||
```js
|
||||
function CreateIIDXFileParser(request) {
|
||||
return (logger) => {
|
||||
let csv = request.file;
|
||||
|
||||
let playtype = request.body.playtype;
|
||||
|
||||
if (playtype !== "SP" && playtype !== "DP") {
|
||||
// todo: throw error
|
||||
}
|
||||
|
||||
return {
|
||||
iterable: csv.toString().split("\n"),
|
||||
game: "iidx",
|
||||
context: {
|
||||
playtype,
|
||||
},
|
||||
classHandler: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Converters
|
||||
|
||||
let's say we have a parser for SDVX that returns:
|
||||
```js
|
||||
[{
|
||||
score: 9000000,
|
||||
lamp: "HARD CLEAR",
|
||||
timestamp: 1623760037
|
||||
},
|
||||
// <more similar elements here...>
|
||||
]
|
||||
```
|
||||
|
||||
Our converter has to take every element here and
|
||||
*convert* it into the form the rest of Tachi can
|
||||
work with.
|
||||
|
||||
Unlike Parser Functions, Converter Functions are static,
|
||||
that means that they cannot be dynamically constructed
|
||||
with closures or similar.
|
||||
|
||||
### Arguments
|
||||
|
||||
Converters are called with these four arguments:
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `data` | ParserElement | The given element in the parser's iterable. |
|
||||
| `context` | ParserContext | The context the parser returned. |
|
||||
| `importType` | [ImportType](./import-types.md) | The import type this import is for. |
|
||||
| `logger` | Logger | The import logger. |
|
||||
|
||||
### Returns
|
||||
|
||||
Converter functions are expected to return three values
|
||||
on success.
|
||||
|
||||
- `dryScore`
|
||||
|
||||
A dry score is a 'partial' real Tachi Score format
|
||||
with certain properties left blank (because they can be
|
||||
filled elsewhere).
|
||||
|
||||
In short, the Dry Score is all the properties that can
|
||||
only be derived from the parser's passed data.
|
||||
|
||||
Other properties, like calculated data are filled out
|
||||
in a score "hydration" process (hence the name Dry Score).
|
||||
|
||||
- `chart`
|
||||
|
||||
The chart this score is for.
|
||||
|
||||
- `song`
|
||||
|
||||
The song this score is for.
|
||||
|
||||
### Throws
|
||||
|
||||
Converters are also expected to fail.
|
||||
For this, we have a very specific throw format
|
||||
that should be thrown whenever a specific error has occurred.
|
||||
|
||||
These are called Failures, and a full list of them can be
|
||||
found [here](./conv-failures.md).
|
||||
|
||||
If a non-failure is thrown, it is logged as an error and the score is ignored (because crashing the entire
|
||||
import from an unexpected error is a bad idea.)
|
||||
@@ -1,32 +0,0 @@
|
||||
# Parsing Import Processing Info
|
||||
|
||||
The ImportProcessingInfo array returned from our
|
||||
importing process is unwieldy to work with. We are interested
|
||||
in what it has to say, though.
|
||||
|
||||
This step converts that array into data we use
|
||||
in successive score import steps.
|
||||
|
||||
!!! info
|
||||
Because the logic for this is essentially just one
|
||||
loop, it does not get its own file in `score-importing/`.
|
||||
|
||||
Instead, it is defined inside `score-import-main.ts`.
|
||||
|
||||
*****
|
||||
|
||||
## Output
|
||||
|
||||
| Property | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `scoreIDs` | Array<string> | The array of scoreIDs imported as a result of this import. |
|
||||
| `errors` | Array<{ type, message }> | An array of the failed ImportProcessingInfo's Types and Error Messages.
|
||||
| `chartIDs` | Set<string> | A set of the chartIDs modified by this import. This is a set to ensure that every chartID here is unique. |
|
||||
| `scorePlaytypeMap` | Record<Playtype, Array<Score>> | All of the score documents returned filtered into Playtype buckets.
|
||||
|
||||
### What's with ScorePlaytypeMap?
|
||||
|
||||
Some imports may import scores from multiple Playtypes. We
|
||||
frequently want to only refer to the set of scores modified
|
||||
under a given playtype (For importing a users game:playtype stats, as an example)
|
||||
so this is a very useful data structure to have.
|
||||
@@ -1,59 +0,0 @@
|
||||
# Personal Bests
|
||||
|
||||
Tachi has a lot of scenarios where it needs to reference
|
||||
a users best score alongside their best lamp.
|
||||
|
||||
This is a standard for most arcade games to conjoin the
|
||||
best aspects of all your scores, so Tachi needs to
|
||||
replicate that behaviour.
|
||||
|
||||
However, not all games agree on what aspects to join about
|
||||
scores. Although in most scenarios we will be joining
|
||||
a user's best lamp with their best score, there are some
|
||||
games (like IIDX) which have other statistics that need
|
||||
to be conjoined.
|
||||
|
||||
*****
|
||||
|
||||
## Calculating Personal Bests
|
||||
|
||||
A user is only allowed one personal best per chart, this
|
||||
allows us to know what personal bests need to be modified
|
||||
by looking at the set of chartIDs modified from this import.
|
||||
|
||||
We can iterate over that set and recalculate the users
|
||||
personal best for each chart.
|
||||
|
||||
Firstly, we select the users score on the chart with the
|
||||
highest percent, then we select the users score on the
|
||||
chart with the highest lampIndex.
|
||||
|
||||
We can then join the relevant properties of this
|
||||
into a PBScore, where the best parts of the lampPB
|
||||
are unioned with the best part of the scorePB.
|
||||
|
||||
!!! info
|
||||
A ScorePB refers to a user's best Percent/Score on a chart.
|
||||
|
||||
A PBScore refers to the aforementioned conjoined score
|
||||
document.
|
||||
|
||||
## Game Specific Things
|
||||
|
||||
Some games require specific conjoining code. The below games
|
||||
have said exceptional behaviour.
|
||||
|
||||
### SDVX, USC
|
||||
|
||||
VF5 and VF6 depends on the users best lamp and their best score.
|
||||
|
||||
That is, you cannot just select the larger volforce from
|
||||
the two scores, as HC 9m -> NC 9.1M should have the
|
||||
volforce of HC 9.1M.
|
||||
|
||||
### IIDX, BMS
|
||||
|
||||
IIDX and BMS have BP. This number is the amount of bads
|
||||
plus the amount of poors a player made. The lowest non-null
|
||||
value for this should be selected as that property for the
|
||||
PBScore.
|
||||
@@ -1,20 +0,0 @@
|
||||
# Updating Quests
|
||||
|
||||
We update quests by looking at the set of modified
|
||||
goalIDs, and check the user's assigned quests to
|
||||
get those that are affected.
|
||||
|
||||
*****
|
||||
|
||||
## Returns
|
||||
|
||||
We evaluate every quest that contain a goal that has just had it's status change.
|
||||
For each one we create a bulkwrite operation to update the user's quest
|
||||
progress.
|
||||
|
||||
If the user has newly achieved a quest, a Webhook Event
|
||||
is emitted, which could be hooked into by our Discord Bot.
|
||||
|
||||
If the progress has changed at all, the data is
|
||||
pushed to an array which is then returned. This is
|
||||
attached onto the Import Document.
|
||||
@@ -1,55 +0,0 @@
|
||||
# Sessions
|
||||
|
||||
This page documents how Sessions are constructed.
|
||||
For a humanised explaination of sessions, see
|
||||
[this page](../../wiki/features.md#sessions).
|
||||
|
||||
*****
|
||||
|
||||
## Creating New Sessions
|
||||
|
||||
Scores are sorted on the time they were achieved. Scores
|
||||
without timestamps are discarded from any session processing.
|
||||
|
||||
Scores are then grouped into buckets of two hours, in pseudocode:
|
||||
|
||||
```ts
|
||||
let lastTimestamp = 0;
|
||||
|
||||
for (score of scores) {
|
||||
if (score.timestamp > lastTimestamp + TWO_HOURS) {
|
||||
CloseBucket();
|
||||
CreateNewBucketWithThisScore();
|
||||
}
|
||||
else {
|
||||
AppendToBucket();
|
||||
}
|
||||
|
||||
lastTimestamp = score.timestamp;
|
||||
}
|
||||
```
|
||||
|
||||
This means that if there was more than two hours between
|
||||
any two successive scores, the session is marked as terminated
|
||||
and a new bucket of scores is created.
|
||||
|
||||
Once we have gotten all the scores we
|
||||
can create a session from them. We need to generate a random
|
||||
name (we have some stuff in `src/datasets` for this).
|
||||
|
||||
We also need to calculate statistics for this session,
|
||||
this is dependent on the game + playtype being used.
|
||||
|
||||
## Appending To Sessions
|
||||
|
||||
If a score within 2 hours of an existing session,
|
||||
then that score is appended to the session.
|
||||
|
||||
!!! bug
|
||||
Sessions do not currently conjoin if a user was to
|
||||
get a score between two sessions such that
|
||||
there was now less than two hours between the first
|
||||
sessions last score and the second sessions first score.
|
||||
|
||||
This is so difficult to properly resolve that Tachi simply
|
||||
makes no attempt to stop it.
|
||||
@@ -1,116 +0,0 @@
|
||||
# User Game Stats
|
||||
|
||||
A user has overarching statistics for a game.
|
||||
|
||||
This part of the score importing process updates those
|
||||
overarching statistics, such as their profile rating
|
||||
and their classes.
|
||||
|
||||
!!! note
|
||||
If a user has never played this game + playtype
|
||||
combination before, a new user game stats
|
||||
object is saved to the database as a result
|
||||
of this update.
|
||||
|
||||
*****
|
||||
|
||||
## Format
|
||||
|
||||
A User Game Stats object looks like this:
|
||||
```ts
|
||||
{
|
||||
userID: string;
|
||||
game: "iidx" | "bms" // ... so on
|
||||
playtype: "SP" | "DP" // ... the set of playtypes this game supports.
|
||||
// A set of continuous statistics about a user.
|
||||
ratings: {
|
||||
// A record of numbers with keys determined
|
||||
// by the game + playtype combination.
|
||||
// such as:
|
||||
VF6: number;
|
||||
// for SDVX and USC.
|
||||
},
|
||||
// A set of discrete statistics about a user.
|
||||
classes: {
|
||||
// This typically contains things like dan ranks
|
||||
// or skill divisions, such as:
|
||||
dan: 14,
|
||||
// for IIDX, or
|
||||
skillColour: 19
|
||||
// for gitadora.
|
||||
|
||||
// Note that instead of remembering each number
|
||||
// for each class, they are defined as constants
|
||||
// in common/src/constants/game.ts
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ratings
|
||||
|
||||
Ratings are calculated for the given Game + Playtype combination.
|
||||
|
||||
Different combinations will have different statistics,
|
||||
Gitadora will have a profile skill level, whereas SDVX
|
||||
would use Profile Volforce.
|
||||
|
||||
All the rating functions for a game are declared
|
||||
in `src/lib/score-import/framework/user-game-stats/rating.ts`.
|
||||
|
||||
## Classes
|
||||
|
||||
There are two types of classes in Tachi. Static classes
|
||||
are classes that can always be derived from the data around
|
||||
them. Things like this are typically discrete buckets
|
||||
for continuous data, such as Skill Colours in GITADORA.
|
||||
|
||||
The other type of classes are external classes. These
|
||||
are things like Dans, where the information cannot be
|
||||
derived from any other data around it, and instead must
|
||||
be explicitly told to change.
|
||||
|
||||
### Class Handlers
|
||||
|
||||
You may recall that in [Parsers and Converters](./parse-conv.md) it was documented that parsers can return a
|
||||
ClassHandler. The ClassHandler is intended to handle those
|
||||
explicit changes.
|
||||
|
||||
For example, let's say we have an import type that tells us:
|
||||
```js
|
||||
{
|
||||
scores: [{score: 1000, songID: 1, diff: "spa"}],
|
||||
sp_class: "kaiden"
|
||||
}
|
||||
```
|
||||
|
||||
We could return a classHandler from our parser function
|
||||
that encloses this updated information, such as:
|
||||
```ts
|
||||
function CreateClassHandler(spClass: number): ClassHandler {
|
||||
return (../../* classHandlers have some args but we dont need them for this example */) => {
|
||||
return {
|
||||
dan: spClass
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This returned classHandler is called with 5 arguments
|
||||
and is expected to return a partial record of the users
|
||||
classes. This is then merged with the static classes
|
||||
and returned as the users classes.
|
||||
|
||||
### Static Class Handlers
|
||||
|
||||
Static class handlers are built in, and always called
|
||||
when a user's game stats is updated.
|
||||
|
||||
## Deltas
|
||||
|
||||
Once a user's classes have been calculated, they are diffed
|
||||
against the current user's stats. If they have been
|
||||
increased, then a redis message is sent out, and things
|
||||
like the Tachi discord bot may choose to echo this
|
||||
in the discord.
|
||||
|
||||
The deltas are returned to be part of the ImportDocument.
|
||||
@@ -1,43 +0,0 @@
|
||||
# Codebase Overview
|
||||
|
||||
This part of the documentation is for the [Tachi-Server](https://github.com/zkldi/Tachi/tree/main/server) codebase.
|
||||
|
||||
## Codebase Documentation vs. Code Documentation
|
||||
|
||||
This is documentation for the **Codebase**. **NOT** documentation for the code.
|
||||
|
||||
The distinction is because we aren't writing a library here - there's no need to document function
|
||||
signatures or what function calls are meant to do. That can all be done inline because no other
|
||||
projects depend on our function calls!
|
||||
|
||||
This documentation is more meta-level. Why things are in certain folders, what certain enums
|
||||
correspond to, how `thing` works, etc.
|
||||
|
||||
## Repos and Licenses
|
||||
|
||||
Tachi is a monorepo, and is made up of many projects. These are:
|
||||
|
||||
- `client/`, Which is a React frontend for Tachi.
|
||||
|
||||
The client and the server are fairly decoupled. Someone could trivially create their own frontend client for Tachi.
|
||||
|
||||
- `server/`, Which is an Express-Typescript backend for Tachi.
|
||||
|
||||
This contains all of our API calls, and interfaces with our database, and powers the actual score import engine.
|
||||
|
||||
- `seeds/`, Which is a git-tracked set of data to be synced with Tachi.
|
||||
|
||||
**This is the source of truth for the songs, charts, and more on the site!**
|
||||
By submitting PRs to this, you can fix bugs on the website, add new charts, and more.
|
||||
|
||||
- `bot/`, Which is a discord bot frontend for Tachi.
|
||||
|
||||
- `common/`, Which contains common types, utils and functions shared between all other packages.
|
||||
|
||||
This is also published to NPM when it hits production.
|
||||
|
||||
- `docs/`, Which contains Tachi documentation.
|
||||
|
||||
- `sieglinde/`, Which contains our BMS/PMS analysis functions.
|
||||
|
||||
Of these, `server/` and `client/` are licensed under the AGPL3. The `seeds/` are licensed under the unlicense, and everything else is MIT.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Tachi API Clients
|
||||
|
||||
Tachi lets users create API Keys, such that programs can interact with their account on their behalf. These API Keys have permissions and other things. You can read about that [here](../../api/auth.md).
|
||||
|
||||
However, it's common for another programmer to want certain permissions for their client. For example, if I was making a score import hook for Tachi, I would need the `score_submit` permission.
|
||||
|
||||
We could ask users to manually create an API Key, copy it into a config file, and hope that they get the permissions right, or we could set up a nice flow.
|
||||
|
||||
## OAuth2 Flow
|
||||
|
||||
Tachi fully supports OAuth2. When you create a Tachi API Client, you can set a `redirectUri`, which is where your OAuth2 flow will go. You can read about it [here](./oauth2.md).
|
||||
|
||||
## Client File Flow
|
||||
|
||||
Alternatively, if you're just looking for a nice UI for users to create an API Key with the right permissions, check out the [Client File Flow](./file-flow.md).
|
||||
@@ -1,7 +0,0 @@
|
||||
# Branching Model
|
||||
|
||||
`main` is the current release version of `Tachi`, and is automatically deployed into production.
|
||||
|
||||
## How should I PR?
|
||||
|
||||
You should submit your PRs for `main`.
|
||||
@@ -1,24 +0,0 @@
|
||||
# Database Seeds
|
||||
|
||||
Tachi tracks the contents of its songs and charts in something called the [Database Seeds](https://github.com/zkldi/Tachi/tree/main/seeds).
|
||||
|
||||
The databases in question aren't (normally) altered by the server code. We essentially overload git and its CI tools to version control parts of our database.
|
||||
|
||||
## What's in the seeds?
|
||||
|
||||
The seeds contain all the [SongDocument](../../schemas/song.md)s and [ChartDocument](../../schemas/chart.md)s for all of the games supported by Tachi.
|
||||
|
||||
They also include all Folder Documents, Table Documents and BMS Course Documents.
|
||||
|
||||
## Synchronisation
|
||||
|
||||
When pushes are made to `main`, our running production servers will automatically update to that new bit of data.
|
||||
|
||||
## Why bother?
|
||||
|
||||
Making all of this data public and easily accessible is one of the best ways to help out other people making rhythm game tools.
|
||||
|
||||
The database seeds are an invaluable resource for other programmers who don't want to scrape data themselves.
|
||||
|
||||
It's also very useful for Tachi. Having charts on a public git repo allows anyone to trivially PR things they know to be wrong.
|
||||
This level of openness to contribution is great, and has resulted in a lot of good work being done by the community.
|
||||
@@ -1,44 +0,0 @@
|
||||
# Client File Flow
|
||||
|
||||
While we have an [OAuth2 Flow](./oauth2.md), that requires another webserver.
|
||||
What if you just want an API key to throw inside a config file? This is a
|
||||
common use case.
|
||||
|
||||
For this, we have the Client File Flow. This flow is entirely done on our
|
||||
site, and results in the user downloading a file, or copying a string.
|
||||
|
||||
## Outline
|
||||
|
||||
!!! note
|
||||
This documentation uses `boku.tachi.ac` as the example site. You should
|
||||
replace this with the instance of Tachi you're pointing against, if it
|
||||
is different.
|
||||
|
||||
- You navigate the user to `https://boku.tachi.ac/client-file-flow/YOUR_CLIENT_ID`.
|
||||
- They are asked if they want to create an API Key for your client.
|
||||
- If they select yes, an API Key is created for your client, and depending on your client parameters, they get it.
|
||||
|
||||
|
||||
## Download Format
|
||||
|
||||
When you create a Tachi API Client, you can select the `File Template` parameter. This will change the format of the key given to the user.
|
||||
|
||||
For example, Let's say you wanted the user to download a `.json` file with
|
||||
your token.
|
||||
|
||||
You could set a template of something like:
|
||||
|
||||
```json
|
||||
{
|
||||
"tachi-api-token": "%%TACHI_KEY%%",
|
||||
"someOtherField": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
If the `File Template` is not set, it is just output normally, without any templating.
|
||||
|
||||
The first instance of `%%TACHI_KEY%%` will be replaced with the generated API key.
|
||||
|
||||
The other file parameter you control is the `File Name`. If this is set, the user will be presented with a button that will download the above content.
|
||||
|
||||
If it is not set, the contents of the template are shown in browser, and the user will have to copy-paste the API Key.
|
||||
@@ -1,185 +0,0 @@
|
||||
# Logging
|
||||
|
||||
As mentioned in the [Toolchain](./toolchain), we use
|
||||
WinstonJS for logging. We use a slightly modified setup
|
||||
of winston, but the same basic logging principles apply.
|
||||
|
||||
!!! note
|
||||
If you like my defaults for logging, they can be quickly
|
||||
invoked with the [Mei](https://github.com/zkldi/mei) wrapper.
|
||||
|
||||
---
|
||||
|
||||
## Log Levels
|
||||
|
||||
Tachi uses the following log levels, listed in order of
|
||||
severity.
|
||||
|
||||
### Crit
|
||||
|
||||
```ts
|
||||
logger.crit("foo");
|
||||
```
|
||||
|
||||
`crit` or Critical is the most severe log level in tachi.
|
||||
|
||||
Calling this means **the process must now completely exit**.
|
||||
|
||||
This fail state is triggered in things like not being able
|
||||
to connect to a Mongo or Redis instance, where the application
|
||||
absolutely cannot function anymore and should quit immediately.
|
||||
|
||||
### Severe
|
||||
|
||||
```ts
|
||||
logger.severe("foo");
|
||||
```
|
||||
|
||||
`severe` is the second most severe log level in tachi.
|
||||
|
||||
Calling this means an error has occurred, and that error implies
|
||||
it will apply to multiple parts of the codebase.
|
||||
|
||||
This log level is used with things like a Song-Chart
|
||||
desync -- A chart must have a song as a parent, but if it
|
||||
doesn't, that's a severe-level error.
|
||||
|
||||
Think of this like an error with wider-reaching implications.
|
||||
|
||||
### Error
|
||||
|
||||
```ts
|
||||
logger.error("foo");
|
||||
```
|
||||
|
||||
`error` is the third most severe log level in tachi.
|
||||
|
||||
Calling this means an error has occurred, but the impact of
|
||||
it is limited to the function or general area it was called in.
|
||||
|
||||
Errors may be recovered from or ignored by the code, but generally that should be reserved for `warn`.
|
||||
|
||||
### Warn
|
||||
|
||||
```ts
|
||||
logger.warn("foo");
|
||||
```
|
||||
|
||||
`warn` indicates that something has gone wrong, but is safely
|
||||
recoverable from, such as a mathematical function being given NaN unexpectedly, and just returning 0.
|
||||
|
||||
`warn` calls **MUST** be recoverable from, and must not
|
||||
imply severe damage to the global state of the application.
|
||||
|
||||
### Info
|
||||
|
||||
```ts
|
||||
logger.info("foo");
|
||||
```
|
||||
|
||||
!!! info
|
||||
This is the default [LOG_LEVEL](./config) for tachi.
|
||||
|
||||
This means that all the levels below this are
|
||||
not displayed to the console or stored.
|
||||
|
||||
`info` indicates that something notable has happened. This
|
||||
should not be used for any errors - instead, it should be
|
||||
used for notable events, such as a new user signing up.
|
||||
|
||||
### Verbose
|
||||
|
||||
```ts
|
||||
logger.verbose("foo");
|
||||
```
|
||||
|
||||
`verbose` indicates that something has happened. This is
|
||||
used for debugging, and is typically only enabled in dev
|
||||
to find out what has gone wrong.
|
||||
|
||||
### Debug
|
||||
|
||||
```ts
|
||||
logger.debug("foo");
|
||||
```
|
||||
|
||||
`debug` is the least notable logging level. This is used
|
||||
exclusively for debugging, and logs typically uninteresting
|
||||
things like Captcha Requests being sent, or a single score was imported.
|
||||
|
||||
## Logger Usage
|
||||
|
||||
In `src/lib/logger/logger.ts` we define a wrapper around winston
|
||||
for our logging needs. We use two functions for this.
|
||||
|
||||
The first function is `CreateLogCtx`. This spawns an
|
||||
instance of the logger with "context".
|
||||
|
||||
This context allows us to keep track of common information
|
||||
for the logger.
|
||||
|
||||
For file-level logging, you should do:
|
||||
|
||||
```ts
|
||||
import CreateLogCtx from "~src/lib/logger/logger";
|
||||
const logger = CreateLogCtx(__filename);
|
||||
|
||||
logger.info("foo");
|
||||
// [src/file.ts] INFO: foo
|
||||
```
|
||||
|
||||
!!! info
|
||||
CreateLogCtx is short for Create Log Context.
|
||||
|
||||
This will spawn an instance of the logger with the context
|
||||
of the current filename.
|
||||
|
||||
For more specific logging, you should create a logger
|
||||
and pass it around as an argument to a function.
|
||||
|
||||
This is used in `src/lib/score-import`, as we create a
|
||||
logger with the user's name and import type as "context".
|
||||
|
||||
```ts
|
||||
const logger = CreateLogCtx(`${username} ${userID}`);
|
||||
|
||||
logger.info("foo");
|
||||
// [zkldi INFO: foo
|
||||
|
||||
SomeOtherFunction(argument1, argument2, logger);
|
||||
```
|
||||
|
||||
!!! info
|
||||
Logger should be the last argument for a function that
|
||||
takes a logger.
|
||||
|
||||
The only exception to this is if it uses our `fetch` API,
|
||||
which **MUST** always be the last call.
|
||||
|
||||
If we have an existing logger and want to append context,
|
||||
we can do that with the `AppendLogCtx` function.
|
||||
|
||||
```ts
|
||||
const logger = CreateLogCtx("foo");
|
||||
|
||||
logger.info("hello!");
|
||||
// [foo] INFO: hello!
|
||||
|
||||
const logger2 = AppendLogCtx("bar", logger);
|
||||
|
||||
logger2.info("hello!");
|
||||
// [foo | bar] INFO: hello!
|
||||
```
|
||||
|
||||
## Log Files
|
||||
|
||||
Logs are saved to `${pwd}/logs` in two files. The first
|
||||
file logs everything above `LOG_LEVEL`. The second file
|
||||
logs `error` and above calls.
|
||||
|
||||
!!! bug
|
||||
Unintentional behaviour occurs here if LOG_LEVEL is
|
||||
above `error` - the main file will not log errors,
|
||||
but the error file will log errors regardless.
|
||||
|
||||
This may be fixed at some point, but is fairly low priority.
|
||||
@@ -1,59 +0,0 @@
|
||||
# OAuth2 Flow
|
||||
|
||||
`tachi-server` has a functional implementation of OAuth2, which lets people create clients to request APIKeys from users.
|
||||
|
||||
This is the preferred way of handling authorisation between web applications, as it can be done without the user ever really having to deal with their API keys!
|
||||
|
||||
!!! note
|
||||
The below steps assume some familiarity with OAuth2. If you are not familiar, I find [this](https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2) to be the best explaination.
|
||||
|
||||
We use an *almost* standard OAuth2 flow, but with the added react-app caveat of POSTing for an intermediate token. If that makes sense to you, you don't need to read this page!
|
||||
|
||||
## Process
|
||||
|
||||
In this scenario, we have two users, user A, who is making a service that integrates with Tachi, and user B, who wants to link integrate their service with their tachi profile.
|
||||
|
||||
!!! info
|
||||
In this example we will use `boku.tachi.ac` as the Tachi site name.
|
||||
|
||||
- An OAuth2 client is created by user A.
|
||||
|
||||
This client will have the following properties.
|
||||
|
||||
```json
|
||||
{
|
||||
"clientID": "ABCDEF", // this is a random string in practice.
|
||||
"clientSecret": "GHIJKL", // this is another random string.
|
||||
"name": "Epic Games",
|
||||
"author": 1,
|
||||
"redirectUri": "https://epicgames.example.com/tachi-auth-callback",
|
||||
"requestedPermissions": ["customise_score"],
|
||||
"apiKeyFormat": null, // These are for the Client File Flow.
|
||||
"apiKeyFilename": null, // More on that later.
|
||||
}
|
||||
```
|
||||
|
||||
- User B wants to link their account to this service, and must click on an auth link on Tachi.
|
||||
|
||||
In the `tachi-client`, this link is `https://boku.tachi.ac/oauth/request-auth?clientID={clientID}`
|
||||
|
||||
EpicGames would show this link to the user, and they would click it.
|
||||
|
||||
While on Tachi, they are presented with the option to accept linking with `clientID`, or decline it.
|
||||
|
||||
- If they accept, `tachi-client` will make a POST request to `https://boku.tachi.ac/api/v1/oauth/create-code`, which will create an intermediate authorisation code.
|
||||
|
||||
The user and this authorisation code are then taken to the `redirectUri` defined in the client. In our case, this means they are taken to
|
||||
`https://epicgames.example.com/tachi-auth-callback?code=SOME_INTERMEDIATE_TOKEN`
|
||||
|
||||
This token **IS NOT** an API Key, but rather an intermediate value that needs to then be converted up.
|
||||
|
||||
EpicGames would now have to take this token and make a POST request to `https://boku.tachi.ac/api/v1/oauth/token`, with their client secret and the intermediate token.
|
||||
|
||||
This POST request will then return the API Key EpicGames wants! The user can then be redirected by EpicGames to wherever they want.
|
||||
|
||||
## The application I want to integrate isn't a web app!
|
||||
|
||||
That's fine. Infact, it's very common for us to integrate with applications
|
||||
that just want an API token inside a JSON file. For that, we have the
|
||||
[Client File Flow](./file-flow.md)
|
||||
@@ -1,405 +0,0 @@
|
||||
# Configuration Info
|
||||
|
||||
The codebase uses a file called `conf.json5` to handle
|
||||
various configurable options. It also reads some things from the process environment.
|
||||
|
||||
## What is JSON5?
|
||||
|
||||
JSON5 is an extension of JSON which is better suited
|
||||
for configuration files.
|
||||
|
||||
You can read more about it [here](https://json5.org/), but
|
||||
the main benefits for us are as follows:
|
||||
|
||||
- Comments
|
||||
- No Quoting properties
|
||||
- Trailing Commas
|
||||
|
||||
## Example Config File
|
||||
|
||||
```js
|
||||
{
|
||||
MONGO_DATABASE_NAME: "testingdb",
|
||||
CAPTCHA_SECRET_KEY: "something_secret",
|
||||
SESSION_SECRET: "something_secret",
|
||||
FLO_API_URL: "https://flo.example.com",
|
||||
EAG_API_URL: "https://eag.example.com",
|
||||
MIN_API_URL: "https://min.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",
|
||||
OUR_URL: "https://example.com",
|
||||
INVITE_CODE_CONFIG: {
|
||||
BATCH_SIZE: 2,
|
||||
INVITE_CAP: 100,
|
||||
BETA_USER_BONUS: 5,
|
||||
},
|
||||
CDN_CONFIG: {
|
||||
WEB_LOCATION: "http://localhost:9000/tachi-public",
|
||||
SAVE_LOCATION: {
|
||||
TYPE: "S3_BUCKET",
|
||||
ENDPOINT: "http://tachi-s3:9000",
|
||||
ACCESS_KEY_ID: "minio",
|
||||
SECRET_ACCESS_KEY: "password",
|
||||
BUCKET: "tachi-public",
|
||||
REGION: "us-east-1",
|
||||
},
|
||||
},
|
||||
TACHI_CONFIG: {
|
||||
TYPE: "omni",
|
||||
NAME: "Tachi Example Config",
|
||||
GAMES: [
|
||||
"iidx",
|
||||
"museca",
|
||||
"maimai",
|
||||
"sdvx",
|
||||
"ddr",
|
||||
"bms",
|
||||
"chunithm",
|
||||
"usc",
|
||||
],
|
||||
IMPORT_TYPES: [
|
||||
"file/eamusement-iidx-csv",
|
||||
"file/batch-manual",
|
||||
"file/solid-state-squad",
|
||||
"file/pli-iidx-csv",
|
||||
"ir/direct-manual",
|
||||
"ir/barbatos",
|
||||
"ir/fervidex",
|
||||
"ir/fervidex-static",
|
||||
"ir/beatoraja",
|
||||
"ir/usc",
|
||||
"ir/kshook-sv3c",
|
||||
"api/eag-iidx",
|
||||
"api/eag-sdvx",
|
||||
"api/flo-iidx",
|
||||
"api/flo-sdvx",
|
||||
"api/min-sdvx",
|
||||
],
|
||||
},
|
||||
LOGGER_CONFIG: {
|
||||
FILE: false,
|
||||
CONSOLE: true,
|
||||
LOG_LEVEL: "info",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
**DO NOT BLINDLY COPY THIS CONFIGURATION FILE!**
|
||||
|
||||
Seriously, The `SESSION_SECRET` token MUST not be
|
||||
public.
|
||||
|
||||
## JSON5 Properties
|
||||
|
||||
All properties are required unless called optional or they have a default.
|
||||
|
||||
### MONGO_DATABASE_NAME
|
||||
|
||||
- Type: String
|
||||
|
||||
What collection to use for your database.
|
||||
|
||||
### CAPTCHA_SECRET_KEY
|
||||
|
||||
- Type: String
|
||||
|
||||
Google gives us a Captcha Secret Key in order for Captcha
|
||||
to work on our site.
|
||||
|
||||
### SESSION_SECRET
|
||||
|
||||
- Type: String
|
||||
|
||||
This key is used to encrypt Session Cookies.
|
||||
|
||||
!!! warning
|
||||
If this key is figured out, anyone can log in as
|
||||
anyone.
|
||||
|
||||
Make sure this is an appropriately long string
|
||||
generated from a *cryptographically secure* source.
|
||||
That is, do not just mash your keyboard.
|
||||
|
||||
You can generate secure random strings with something
|
||||
like [KeePass](https://keepass.info/).
|
||||
|
||||
### FLO_API_URL, EAG_API_URL, MIN_API_URL
|
||||
|
||||
- Type: String
|
||||
|
||||
The URL for the `FLO, EAG or MIN` services. This is used for integration
|
||||
with the Kamaitachi version of Tachi.
|
||||
|
||||
### FLO/MIN/EAG_OAUTH2_INFO
|
||||
|
||||
- Type: OAuth2Info (Optional)
|
||||
|
||||
If present, these define our OAuth2 Client data for interacting with these services. These are like this like this:
|
||||
```js
|
||||
{
|
||||
FLO_OAUTH2_INFO: {
|
||||
CLIENT_ID: "OUR_CLIENT_ID",
|
||||
CLIENT_SECRET: "OUR_CLIENT_SECRET",
|
||||
REDIRECT_URI: "https://tachi.example.com"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning
|
||||
The server will throw a fatal error if you have OAUTH2_INFO set for one service, but not an API_URL.
|
||||
|
||||
Maybe a better solution would be to have the API_URL inside the OAUTH2_INFO. Ah well.
|
||||
|
||||
### CLIENT_DEV_SERVER
|
||||
|
||||
- Type: String
|
||||
- Default: Null
|
||||
|
||||
If present, and a string, this points to the local dev server for a react app. Having this
|
||||
option set results in CORS being enabled for *that* specific URL. This is useful for local
|
||||
development, but should not be used in production.
|
||||
|
||||
### RATE_LIMIT
|
||||
|
||||
- Type: Positive Integer
|
||||
- Default: 500
|
||||
|
||||
Determines how many requests an APIKey OR IP can make every minute.
|
||||
|
||||
The default is set to the very generous 500, as it's possible for users to accidentally hit 100 requests/min by refreshing very fast.
|
||||
|
||||
### OAUTH_CLIENT_CAP
|
||||
|
||||
- Type: Positive Integer
|
||||
- Default: 15
|
||||
|
||||
The amount of OAuth2Clients one user can create at any one time. Defaults to 15.
|
||||
|
||||
### OPTIONS_ALWAYS_SUCCEEDS
|
||||
|
||||
- Type: Boolean
|
||||
- Default: false
|
||||
|
||||
If true, all `OPTIONS` requests to the server will return `200`, no matter what. This is a hack used for development CORS.
|
||||
|
||||
### BEATORAJA_QUEUE_SIZE
|
||||
|
||||
- Type: Integer
|
||||
- Default: 3
|
||||
|
||||
How many unique players have to have played a chart on the beatoraja IR for it to be de-orphaned.
|
||||
|
||||
!!! note
|
||||
Note that LR2 scores or database imports do not count towards this total.
|
||||
|
||||
!!! warning
|
||||
The lowest legal value for this field is 2.
|
||||
|
||||
### OUR_URL
|
||||
|
||||
- Type: String
|
||||
|
||||
Where *this* server is hosted. This is used to
|
||||
provide callback URLs inside emails.
|
||||
|
||||
### EMAIL_CONFIG
|
||||
|
||||
- Type: EMAIL_CONFIG (required)
|
||||
|
||||
SMTP is always configured. Set:
|
||||
|
||||
- `TACHI_EMAIL_FROM` - `From` header (must match a verified sender when using Postmark).
|
||||
- `TACHI_EMAIL_HOST`, `TACHI_EMAIL_PORT`, `TACHI_EMAIL_SECURE` (`true` / `false`).
|
||||
- Optionally `TACHI_EMAIL_AUTH_USER` / `TACHI_EMAIL_AUTH_PASS` for SMTP auth (local Mailpit
|
||||
typically needs none).
|
||||
- For Postmark, use host `smtp.postmarkapp.com` (usually port `587` with `TACHI_EMAIL_SECURE=false`)
|
||||
and set either auth field to your server API token (both username and password are the token for
|
||||
Postmark SMTP).
|
||||
|
||||
`TRANSPORT_OPS` is derived from these variables and passed to Nodemailer.
|
||||
|
||||
```ts
|
||||
interface EMAIL_CONFIG {
|
||||
FROM: string;
|
||||
TRANSPORT_OPS: any;
|
||||
}
|
||||
```
|
||||
|
||||
### INVITE_CODE_CONFIG
|
||||
|
||||
- Type: INVITE_CODE_CONFIG (Optional)
|
||||
|
||||
Configures how invites are created by Tachi.
|
||||
If not present, the site will not require invite codes at all.
|
||||
|
||||
`BATCH_SIZE` determines how many invites to create every month,
|
||||
`INVITE_CAP` determines how many invites a user can have -- ever.
|
||||
`BETA_USER_BONUS` determines how many additional invites users of Kamaitachi 1 have out of the box.
|
||||
|
||||
```ts
|
||||
interface INVITE_CODE_CONFIG: {
|
||||
BATCH_SIZE: integer;
|
||||
INVITE_CAP: integer;
|
||||
BETA_USER_BONUS: integer;
|
||||
};
|
||||
```
|
||||
|
||||
### TACHI_INVITE_ADMIN_INITIAL_INVITE_CODE
|
||||
|
||||
- Type: String (Optional, environment variable only)
|
||||
|
||||
A one-time bootstrap invite code for first-time instance setup. When `INVITE_CODE_CONFIG` is
|
||||
configured, new instances have a chicken-and-egg problem: registration requires an invite code,
|
||||
but invite codes can only be created by an existing user.
|
||||
|
||||
Set this environment variable to a long, random secret. The first person to register with this
|
||||
code - while the `account` table is still empty - becomes the site admin. Once the first admin
|
||||
exists, the code is no longer accepted.
|
||||
|
||||
### TACHI_CONFIG
|
||||
|
||||
- Type: TACHI_CONFIG
|
||||
|
||||
Configures what the Tachi Server instance supports, and what it's generally doing.
|
||||
|
||||
```ts
|
||||
interface TACHI_CONFIG: {
|
||||
NAME: string;
|
||||
TYPE: "kamai" | "boku" | "omni";
|
||||
GAMES: Game[];
|
||||
IMPORT_TYPES: ImportTypes[];
|
||||
}
|
||||
```
|
||||
|
||||
#### NAME
|
||||
|
||||
The name of the server. This is reported at `/api/v1/status`.
|
||||
|
||||
#### TYPE
|
||||
|
||||
What type of tachi-server this is. `kamai` will enable Kamaitachi Only routes, `boku` will enable
|
||||
Bokutachi only routes, and `omni` will enable both.
|
||||
|
||||
#### GAMES
|
||||
|
||||
What games are supported by this server. For more information, see [Games](../../wiki/games.md).
|
||||
|
||||
#### IMPORT_TYPES
|
||||
|
||||
What importTypes are legal for this server. For more information, see [Import Types](../import/import-types.md).
|
||||
|
||||
### LOGGER_CONFIG
|
||||
|
||||
- Type: LOGGER_CONFIG (Optional)
|
||||
|
||||
Configures how logs are sent around in Tachi.
|
||||
|
||||
```ts
|
||||
interface LOGGER_CONFIG: {
|
||||
LOG_LEVEL: "debug" | "verbose" | "info" | "warn" | "error" | "severe" | "crit";
|
||||
CONSOLE: boolean;
|
||||
FILE: boolean;
|
||||
SEQ_API_KEY: string | undefined;
|
||||
DISCORD?: {
|
||||
WEBHOOK_URL: string;
|
||||
WHO_TO_TAG: string[];
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### LOG_LEVEL
|
||||
|
||||
What log level to use out of the box. If no LOGGER_CONFIG is provided, defaults to "info".
|
||||
|
||||
#### CONSOLE
|
||||
|
||||
Whether to log to the console or not. If no LOGGER_CONFIG is provided, defaults to true.
|
||||
|
||||
#### FILE
|
||||
|
||||
Whether to log to a log file or not. If no LOGGER_CONFIG is provided, defaults to false.
|
||||
|
||||
#### SEQ_API_KEY
|
||||
|
||||
(Optional)
|
||||
|
||||
If present, this is an API Key for logging to a Seq server, which is set in the process environment.
|
||||
|
||||
If no LOGGER_CONFIG is provided, this is not set.
|
||||
|
||||
#### DISCORD
|
||||
|
||||
(Optional)
|
||||
|
||||
If present, this configures a discord `WEBHOOK_URL` to log info or higher messages to.
|
||||
`WHO_TO_TAG` is an array of userIDs to tag in the case of a `severe` or `fatal` error.
|
||||
|
||||
If no LOGGER_CONFIG is provided, this is not set.
|
||||
|
||||
### CDN_CONFIG
|
||||
|
||||
- Type: CDN_CONFIG
|
||||
|
||||
Configures the CDN for the Tachi Server. Files are always stored in an S3-compatible bucket (AWS S3, Backblaze, MinIO, etc.).
|
||||
|
||||
For local development with `docker-compose-dev.yml`, run the `tachi-s3` MinIO service and point `SAVE_LOCATION.ENDPOINT` at it (from the `tachi-dev` container, `http://tachi-s3:9000`). Use a `WEB_LOCATION` URL that browsers can load (for example `http://localhost:9000/<bucket>` when MinIO’s API port is published to the host).
|
||||
|
||||
```ts
|
||||
interface CDN_CONFIG: {
|
||||
WEB_LOCATION: string;
|
||||
SAVE_LOCATION: {
|
||||
TYPE: "S3_BUCKET";
|
||||
ENDPOINT: string;
|
||||
ACCESS_KEY_ID: string;
|
||||
SECRET_ACCESS_KEY: string;
|
||||
BUCKET: string;
|
||||
KEY_PREFIX?: string;
|
||||
REGION?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### WEB_LOCATION
|
||||
|
||||
Configures a URL to redirect users to when returning CDN contents. This could be something like `https://cdn.boku.tachi.ac` or a path-style URL to your bucket on MinIO.
|
||||
|
||||
#### SAVE_LOCATION
|
||||
|
||||
Configures the S3-compatible API endpoint and bucket used for uploads (profile pictures, score import payloads, etc.).
|
||||
|
||||
## Process Environment
|
||||
|
||||
The process environment also contains necessary things for functional Tachi Server running.
|
||||
|
||||
!!! info
|
||||
These variables are put into the process environment instead of the conf.json5 file because
|
||||
they're easier to change between docker instances. This Helps us scale and deploy.
|
||||
|
||||
### PORT
|
||||
|
||||
The PORT environment variable specifies what port our express server should listen to.
|
||||
If not set, this will log a warning and default to 8080.
|
||||
|
||||
### MONGO_URL
|
||||
|
||||
Where our mongoDB instance is. This would be `localhost:27017` if hosting on the same box.
|
||||
If not set, this will terminate the process with a critical error.
|
||||
|
||||
### SEQ_URL
|
||||
|
||||
Where a Seq instance is. If no `LOGGER_CONFIG.SEQ_API_KEY` was defined, this is fine. If it was,
|
||||
this will log a warning, and nothing will be sent to Seq.
|
||||
|
||||
### NODE_ENV
|
||||
|
||||
Expected to be either "dev", "production", "staging" or "test". If not set, this will terminate the process.
|
||||
@@ -1,130 +0,0 @@
|
||||
# File/Folder Organisation
|
||||
|
||||
`tachi-server` has a specific setup of files and folders
|
||||
to ensure that code is at where it's most sensible.
|
||||
|
||||
!!! note
|
||||
This documentation is a rough guide for where
|
||||
to place files if you are writing a new file,
|
||||
or where to look for certain functionality.
|
||||
|
||||
It is not a comprehensive tutorial for every file
|
||||
in the repo, as that would be a pain to keep updated.
|
||||
|
||||
*****
|
||||
|
||||
## Top Level
|
||||
|
||||
All of these are at the root level of the project.
|
||||
|
||||
### `/src`
|
||||
|
||||
All of the server TypeScript code goes here.
|
||||
|
||||
### `/js`
|
||||
|
||||
!!! info
|
||||
This folder is gitignored.
|
||||
|
||||
When compiled, `tsc` will output the JS code here.
|
||||
|
||||
### `/scripts`
|
||||
|
||||
Various scripts for interacting with `tachi-server`, such
|
||||
as single-use scripts for importing some data, or
|
||||
frequently used scripts such as updating BMS tables.
|
||||
|
||||
## TypeScript Source Code
|
||||
|
||||
All of these are inside `/src`.
|
||||
|
||||
### `/datasets`
|
||||
|
||||
Some of `tachi-server`'s code interacts with datasets that
|
||||
aren't worth putting into MongoDB, such as splash text.
|
||||
|
||||
This is mainly for things where we want to randomly select
|
||||
from the list, and not perform any serious lookups - which
|
||||
is why it's a good fit for splash text/automatic session names.
|
||||
|
||||
!!! info
|
||||
Selecting a random element from an entire collection
|
||||
in MongoDB is relatively expensive, and would quadruple
|
||||
the time an import takes.
|
||||
|
||||
!!! warning
|
||||
TypeScript does not support copying over non-code files.
|
||||
|
||||
You can use `cp` in post to move files around, or
|
||||
place the data in memory, either is fine.
|
||||
|
||||
### `/external`
|
||||
|
||||
Code relating to the "external" applications for `tachi-server`,
|
||||
such as MongoDB and Redis.
|
||||
|
||||
### `/lib`
|
||||
|
||||
Sets of code for `tachi-server` functionality. This is the
|
||||
main important part of the codebase for handling things
|
||||
like score imports, logging, and more.
|
||||
|
||||
### `/server`
|
||||
|
||||
This contains the express application that `tachi-server`
|
||||
uses in order to be a server.
|
||||
|
||||
This contains our API, IR implementations and a way of
|
||||
serving our PWA.
|
||||
|
||||
### `/test-utils`
|
||||
|
||||
Tachi's tests need mocks and some specialised code in order
|
||||
to work well. This folder contains all of those things.
|
||||
|
||||
!!! warning
|
||||
**NOTHING** from this folder should be ran in production.
|
||||
|
||||
### `/utils`
|
||||
|
||||
Small utilities for interacting with Tachi, such as
|
||||
functions that retrieve a user given certain params.
|
||||
|
||||
This also contains utilities for handling song/chart
|
||||
database lookups - such as looking up on BMS hash.
|
||||
|
||||
## Express Server
|
||||
|
||||
All of the below folders are under `/src/server`.
|
||||
|
||||
As mentioned above, Tachi stores the routing for our
|
||||
APIs and IR implementations here.
|
||||
|
||||
### `/middleware`
|
||||
|
||||
This contains the middleware we use for the server,
|
||||
such as authentication middleware and such.
|
||||
|
||||
### `/router`
|
||||
|
||||
This contains the actual 'routes' for our server.
|
||||
|
||||
The folders here **MUST** be 1:1 with the endpoints
|
||||
on the server. For example, the implementation of
|
||||
|
||||
```
|
||||
https://boku.tachi.ac/api/v1/foo/bar
|
||||
```
|
||||
|
||||
**MUST** be found at `src/server/router/api/v1/foo/bar/router.ts`
|
||||
|
||||
### Router Files
|
||||
|
||||
The only files allowed to declare endpoints are `router.ts` files.
|
||||
|
||||
This allows us to separate functionality from API structure
|
||||
in cases where an API call needs to do a lot of things.
|
||||
|
||||
## Test Files
|
||||
|
||||
This documentation has been moved to [its own page](./testing.md)!
|
||||
@@ -1,10 +0,0 @@
|
||||
# Style
|
||||
|
||||
For style, we use a custom tool called [Cadence](https://github.com/CadenceJS/Cadence).
|
||||
|
||||
This is effectively a huge ESLint config with everything I care about enabled.
|
||||
|
||||
It's worth noting that this is an *extremely strict* linter. You are **expected** to have "Format on Save" turned on, in order to use this linter properly.
|
||||
|
||||
!!! info
|
||||
You can run ESLint in the repo any time with `pnpm lint`.
|
||||
@@ -1,58 +0,0 @@
|
||||
# Tests
|
||||
|
||||
Tachi makes extensive use of tests to ensure code
|
||||
is of high quality when its released.
|
||||
|
||||
*****
|
||||
|
||||
## Running Tests
|
||||
|
||||
Tests are ran with [node-tap](https://node-tap.org).
|
||||
|
||||
You can run the server test suite with the following script:
|
||||
|
||||
```
|
||||
pnpm test
|
||||
```
|
||||
|
||||
This will execute every test, and also perform coverage
|
||||
analysis.
|
||||
|
||||
!!! bug
|
||||
Use the workspace Vitest entrypoints (`bun test`, `just test-typescript`, …) rather than running `node`
|
||||
directly on `.ts` files. Bun invokes Vitest without a separate transpiler for day-to-day runs.
|
||||
|
||||
## Writing Tests
|
||||
|
||||
Tests should be located in the same folder as the file they're testing, and tests should only ever test
|
||||
exports from one file at a time.
|
||||
|
||||
Tests should call things like `ResetDBState` and `CloseAllConnections` as lifecycle hooks to ensure
|
||||
the file exits.
|
||||
|
||||
Tests should use the extension `.test.ts`, and keep
|
||||
the same filename as the file they are testing.
|
||||
|
||||
## Coverage
|
||||
|
||||
Coverage should be kept above 80%. New code should be tested.
|
||||
|
||||
Pull Requests will be rejected if they contain significant
|
||||
changes that are untested!
|
||||
|
||||
## Single Process TAP
|
||||
|
||||
Since we don't mock our MongoDB install out, we run a real instance of Mongo for our tests, and run against it.
|
||||
|
||||
TAP runs every test file in isolation. This is a good idea normally, as it means state is never shared across files, and nothing bad is generally going to happen.
|
||||
|
||||
*However*, it takes around 2 seconds to connect to the MongoDB server when a new test file is started. These 2 seconds add up over the course of a ~1500 test file suite, and result in tests taking over 15-20 minutes.
|
||||
|
||||
To get around this, we use a Single Process TAP hack, which rolls all of our test files up into one test file.
|
||||
|
||||
The hack part here is **not** an understatement, and it is genuinely rather dirty. **BUT**, it runs in ~40-50 seconds, works the same for coverage, and can just generally be ignored as an abstraction layer.
|
||||
|
||||
As a downside, this makes error messages *way* more cryptic, but for a 20x performance gain, it can be lived with.
|
||||
|
||||
!!! warning
|
||||
If you are getting failures like "Child test left in suite", one of your test files is *crashing* the entire process, leaving all other tests just dangling.
|
||||
@@ -1,85 +0,0 @@
|
||||
# Contribution Guides
|
||||
|
||||
The Tachi repository that you just set up is made up of multiple "components".
|
||||
|
||||
All of them have their own things going on, so this page has all the guides for each specific component.
|
||||
|
||||
## What can I contribute to?
|
||||
|
||||
In order of difficulty, here are the components of Tachi you can contribute to!
|
||||
|
||||
All of these sections summarise a part of Tachi, and end with a link to a guide you can use to
|
||||
contribute to it.
|
||||
|
||||
### Issue Reports
|
||||
|
||||
You can report issues on the [GitHub](https://github.com/zkldi/Tachi) repository. This requires
|
||||
_absolutely no programming knowledge_ on your part. All you have to do is write up a nice summary
|
||||
of the bug.
|
||||
|
||||
!!! note
|
||||
Although they're called GitHub _issues_, they're actually used for tracking anything. If you've
|
||||
came up with a cool feature idea, send it over as an issue! `zk` will read and Triage them.
|
||||
|
||||
For more information, read our [Issue Reporting Guide](./components/issues.md).
|
||||
|
||||
### Documentation
|
||||
|
||||
We store our documentation as a series of markdown files in the [Main Repository](https://github.com/zkldi/Tachi). You can find it under the `docs/` folder.
|
||||
|
||||
Writing, maintaining and proofreading the documentation is something that is **severely** neglected
|
||||
at the moment. Simple things like typo fixes, all the way up to writing new explanations about major features
|
||||
are **thoroughly** appreciated, as `zk` prioritises maintaining the core of working code.
|
||||
|
||||
If you're interested in this, check out the [Documentation Contribution Guide](./components/documentation.md).
|
||||
|
||||
### Database Seeds
|
||||
|
||||
We use an interesting system for parts of our database. We actually store a game's songs and charts _in_
|
||||
our GitHub repository! That means you can:
|
||||
|
||||
- Open the `songs` file for a game.
|
||||
- Edit a `title` of a song that has a typo.
|
||||
- Add a couple new songs that were added in the latest update
|
||||
- Submit your changes back and if they're accepted...
|
||||
- They automatically synchronise with the site!
|
||||
|
||||
!!! important
|
||||
This part of Tachi is the most important part for external contributors.
|
||||
You guys know these games better than `zk` does, and you guys keep an eye on all the updates for your games!
|
||||
|
||||
If people don't add songs/charts to this database, `zk` will **not** keep an eye on the game for you! Someone *has* to pick up the reigns for each game!
|
||||
|
||||
If you want to add/fix songs, charts, folders or tables for your favourite game - **START HERE!**
|
||||
|
||||
Or in general, if you just want to contribute and don't know what to -- **this is the MOST in need of help. Always.**
|
||||
|
||||
Want to get started on contributing to the Database? Check out our [Database Contribution Guide](./components/seeds.md).
|
||||
|
||||
### Server, Client
|
||||
|
||||
The server and client form the powerful _core_ of Tachi.
|
||||
|
||||
The server handles all of our logic -- How do we get scores, where should scores come from, how do we calculate all these stats and way more.
|
||||
|
||||
The client tries to then place a slick UI over that logic and its exposed API.
|
||||
|
||||
!!! warning
|
||||
Tachi's core is not an amazingly complex beast, but it is _not_ going to be reasonably followable
|
||||
with not a lot of programming experience. You'll need some background in programming to be able to do almost anything in this area.
|
||||
|
||||
That said, we still have a thorough guide -- It's not *from 0*, but it is *from some programming knowledge*.
|
||||
|
||||
Want to get started on contributing to the Core? Check out our [Core Contribution Guide](./components/core.md).
|
||||
|
||||
We'll cover...
|
||||
|
||||
- Running a local development instance of Tachi.
|
||||
- How to configure the server with `conf.json5`.
|
||||
- How our codebase is laid out.
|
||||
- How to run tests and more!
|
||||
|
||||
## That's it for now!
|
||||
|
||||
Everything else in Tachi isn't seeking external contribution at the moment. So, feel free to check
|
||||
out one of the above linked guides!
|
||||
@@ -1,43 +0,0 @@
|
||||
# Client + Server Contribution Guide
|
||||
|
||||
The client and server are the meat and potatoes of Tachi. They handle all of our requests and display our fancy UI.
|
||||
|
||||
Contributing here is a bit more difficult than contributing to the seeds, but it's certainly not impossible!
|
||||
|
||||
Plus, it's good fun to be able to mess around with websites. If you've got something you want to mess around with, you might find it surprisingly easy to do!
|
||||
|
||||
## Pre-Setup
|
||||
|
||||
You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
|
||||
|
||||
## Component Overview
|
||||
|
||||
The content for the client is inside `client/` and the content for the server is inside `server/`.
|
||||
|
||||
The client and server share quite a bit of code. This is inside `common/`.
|
||||
|
||||
To run the client and server, use `just start`. You can hit `Ctrl+C` to stop the server.
|
||||
|
||||
## Editing the Client
|
||||
|
||||
With `just start` running, the client will listen for changes you make, and reload accordingly. You will see your changes reflected on http://localhost:3000.
|
||||
|
||||
## Editing the Server
|
||||
|
||||
Likewise, with `just start` running, the server will listen for changes you make, and reload accordingly.
|
||||
|
||||
!!! warning
|
||||
Be careful with triggering a server reload. If you do it mid-import you can cause some serious state issues.
|
||||
|
||||
If you suspect that your local state is screwed up, run `just wipe-local-db` to reset
|
||||
the database.
|
||||
|
||||
## Getting real data
|
||||
|
||||
The client, out of the box, is sort of hard to test because you'll have no scores to display.
|
||||
|
||||
Use `just load-kamai-dataset` or `just load-boku-dataset` to load a *real* dataset from either of the Tachis.
|
||||
|
||||
You'll then need to edit `server/conf.json5` and change `MONGO_DATABASE_NAME` to `"anon-kamai"` or `"anon-boku"`.
|
||||
|
||||
Everyone's passwords are set to `password`, so feel free to log in as anyone, and see real data!
|
||||
@@ -1,45 +0,0 @@
|
||||
# Documentation Guide
|
||||
|
||||
The documentation component of Tachi powers the website you're currently viewing. Hi!
|
||||
|
||||
## Pre-Setup
|
||||
|
||||
You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
|
||||
|
||||
## Component Overview
|
||||
|
||||
All of the content for this component is inside the `docs/` folder.
|
||||
|
||||
It contains another folder, inconveniently called `docs/`, which contains all of the markdown files that are our documentation.
|
||||
|
||||
There's another folder called `includes/`, which contains some things that are constantly
|
||||
referenced throughout the documentation.
|
||||
|
||||
At the top level, there's `mkdocs.yml` which configures how our documentation works later.
|
||||
|
||||
## Software Overview
|
||||
|
||||
We use [MKDocs Material](https://squidfunk.github.io/mkdocs-material/) for our documentation.
|
||||
It extends markdown a bit to let us add things like admonitions and references.
|
||||
|
||||
Their documentation is **incredibly** good, so check their stuff out there if you want to see what features are available.
|
||||
|
||||
Other than that, our documentation is [markdown](https://www.markdownguide.org/basic-syntax/). If you know how to format a discord message, you know how to write documentation!
|
||||
|
||||
## Running the Documentation
|
||||
|
||||
Use `just docs start` inside Tachi to start up a local documentation viewer on http://localhost:8001.
|
||||
|
||||
This will automatically refresh when you edit anything related to the documentation, so you can quickly see how your stuff goes.
|
||||
|
||||
## A bit about mkdocs.yml
|
||||
|
||||
MKDocs has only one configuration file -- `mkdocs.yml`. This is a [YAML](https://en.wikipedia.org/wiki/YAML) file that configures the documentation we output.
|
||||
|
||||
It also manages the order of pages on the site. **You need to edit this if you're adding new pages! They aren't automatically added!**
|
||||
|
||||
## Contributing Back
|
||||
|
||||
It's just documentation. Make the changes and commit them up, ideally with `docs:` as the commit prefix.
|
||||
|
||||
That is to say: your commit messages should look like `docs: fixed typo in API route`.
|
||||
@@ -1,30 +0,0 @@
|
||||
# GitHub Issues Guide
|
||||
|
||||
This isn't really a Tachi component, but it's important enough to have a guide.
|
||||
|
||||
We use GitHub issues for all of our issue tracking and feature planning, if you're looking to request a feature or report a bug, good etiquette is defined here.
|
||||
|
||||
## Etiquette
|
||||
|
||||
Be nice.
|
||||
|
||||
If you're reporting a bug, **be as explicit as possible**. If something crashed, what were you doing at the time of the crash? Remember that a human has to read your post and attempt to diagnose the issue.
|
||||
|
||||
For the same reasons that you wouldn't go to a doctor and just say "It hurts", you can't just report a software issue as "It's broken". Low effort and ambiguous bug reports will be closed.
|
||||
|
||||
## Large Feature Requests
|
||||
|
||||
If you've came up with an idea for a new large feature, that's awesome! Before you post it though, think it through for a while.
|
||||
I personally find it very helpful to think on ideas in the shower, but basically anything where you can think about an idea undistracted is useful (like a walk or something).
|
||||
|
||||
This will help you shape up the feature, and makes it easier for maintainers to implement said feature (Especially if it's major).
|
||||
|
||||
Regardless, once you've posted a large feature request, expect there to be discussion in the thread. You should check your emails!
|
||||
|
||||
## Existing Issues
|
||||
|
||||
Search for existing issues before opening a new one. There might already be one open with a similar thing.
|
||||
|
||||
## That's it!
|
||||
|
||||
Don't fret too much about writing perfect issues, but put some effort into them. The development team look at everything that comes through, and will tidy up bits of your issue for you if you make a mistake.
|
||||
@@ -1,138 +0,0 @@
|
||||
# Database Seeds Guide
|
||||
|
||||
The database seeds component of Tachi controls the actual data inside the app.
|
||||
|
||||
Changes made in this component are automatically synchronised with the live Tachi database,
|
||||
it's kind of a cool system, and allows anyone to contribute patches to songs, charts, folders, and more!
|
||||
|
||||
## Pre-Setup
|
||||
|
||||
You must have [Setup a local dev environment](../setup.md) in order to work nicely with the docs!
|
||||
|
||||
## Component Overview
|
||||
|
||||
All of the content for this component is inside the `seeds/` folder.
|
||||
|
||||
It contains the `collections/` folder, which contains JSON files for their respective databases.
|
||||
|
||||
!!! example
|
||||
`collections/songs-iidx.json` will be synced with the `songs-iidx` collection in the database.
|
||||
|
||||
The other available folder is `scripts/`.
|
||||
This contains a Typescript package for scripting changes to the database seeds, and also contains a script that tests whether the data in the seeds is valid.
|
||||
We'll go over this more later.
|
||||
|
||||
## Software Overview
|
||||
|
||||
The collections are all just plain JSON files.
|
||||
|
||||
The scripts are written in either TypeScript or JavaScript.
|
||||
|
||||
## How do I know what a song is meant to look like?
|
||||
|
||||
These are called schemas (or interfaces) and you can find them in two places:
|
||||
|
||||
- The source code.
|
||||
|
||||
You can find the actual interfaces we use inside the common module, it's at `common/src/types.ts`. This contains interface definitions like `SongDocument` which,
|
||||
expectedly, define what a song is meant to look like.
|
||||
|
||||
- The documentation.
|
||||
|
||||
We also maintain a bit more human readable form for schemas in this documentation, under the [schemas section](../../schemas).
|
||||
|
||||
!!! warning
|
||||
This may be slightly out of sync with the current content. Please report it if it is!
|
||||
|
||||
## Important Scripts
|
||||
|
||||
After doing **anything** to the collections, such as adding a new item to the database, you **MUST** run `just seeds sort` inside the `scripts/` folder.
|
||||
|
||||
This will deterministically sort the data you put into the collections, making sure that `git` history stays sane (i.e. it only says things that actually changed, changed).
|
||||
|
||||
Before sending any changes, run `just seeds test` to check all your data. If anything you've sent is invalid, it'll be logged in `failed-tests.log`, and the command won't pass.
|
||||
|
||||
This is automatically ran when changes are sent to me, but you should run it yourself before committing anyway!
|
||||
|
||||
!!! info
|
||||
Your changes won't be synced with the database if they fail the tests.
|
||||
|
||||
If you're confused about why tests are failing, ask in the discord.
|
||||
|
||||
## Rerunners
|
||||
|
||||
Inside the `scripts/` folder is a folder called `rerunners/`.
|
||||
These scripts are intended to be kept around and re-ran (hence the name).
|
||||
|
||||
You'll find a bunch of useful little utilities here, and there's sadly too many to document.
|
||||
|
||||
It should be obvious what each one does, though.
|
||||
|
||||
If you're looking to add a BMS table to Tachi for an example, you'd look into the rerunner scripts.
|
||||
|
||||
## Single-Use Scripts
|
||||
|
||||
Some scripts we only want to run once, and we don't really need to keep them around. These are saved into the `personal` folder, and will never be tracked by `git`.
|
||||
|
||||
You can make quick hacky scripts here.
|
||||
|
||||
!!! warning
|
||||
Anything in this folder will *NOT* be shared. Please be generous with what you share,
|
||||
far too many people have assumed nobody is interested in their scripts, and then lost
|
||||
the scripts when moving machines!
|
||||
|
||||
## Scripting Changes
|
||||
|
||||
We provide utilities for mutating data inside the collections comfortably inside `scripts/util.js`. The most important one is `MutateCollection`, which, as expected, mutates a collection.
|
||||
|
||||
It takes a collection name (like `songs-iidx.json`) and a callback function that receives the data inside that collection.
|
||||
|
||||
Whatever that callback returns replaces the data in the collection.
|
||||
|
||||
Below are some examples of what you can do with this API.
|
||||
|
||||
=== "Batch Renaming an artist"
|
||||
```js
|
||||
const { MutateCollection } = require("../util");
|
||||
|
||||
// Let's say all songs made by the artist "Scrimbly" were renamed to
|
||||
// BEMANI SOUND TEAM "Scrimbly".
|
||||
MutateCollection("songs-iidx.json", (songs) => {
|
||||
// for all the songs in songs-iidx.json
|
||||
for (const song of songs) {
|
||||
// if the song artist is "Scrimbly"
|
||||
if (song.artist === "Scrimbly") {
|
||||
// replace it
|
||||
song.artist = 'BEMANI SOUND TEAM "Scrimbly"'
|
||||
}
|
||||
}
|
||||
|
||||
// return the same piece of data we got, with our mutations applied.
|
||||
return songs;
|
||||
});
|
||||
```
|
||||
|
||||
=== "Adding data"
|
||||
```js
|
||||
const { MutateCollection } = require("../util");
|
||||
|
||||
MutateCollection("songs-iidx.json", (songs) => {
|
||||
songs.push({
|
||||
// some new song object
|
||||
})
|
||||
|
||||
songs.push({
|
||||
// another new song object
|
||||
})
|
||||
|
||||
// return the same piece of data we got, with our mutations applied.
|
||||
return songs;
|
||||
});
|
||||
```
|
||||
|
||||
For even more examples, see the `rerunners/` folder, as it makes liberal use of this API.
|
||||
|
||||
## Running scripts
|
||||
|
||||
To run a seeds script, type `seeds` and pick your script. This will type out the
|
||||
command for you.
|
||||
@@ -1,9 +0,0 @@
|
||||
# Adding New BMS Tables
|
||||
|
||||
Adding new BMS tables to Tachi is remarkably simple.
|
||||
|
||||
Firstly, you need to edit `common/src/constants/bms-tables.ts`. Place your new table in the array.
|
||||
|
||||
Now, run the `rerunners/bms-pms/sync-bms-tablefolders.ts` script inside the database seeds.
|
||||
|
||||
Congratulations! You've added support for a BMS table.
|
||||
@@ -1,77 +0,0 @@
|
||||
# Merging DDR data into seeds
|
||||
|
||||
!!! important
|
||||
You need to have a working setup of Tachi to use this script.
|
||||
|
||||
For instructions on how to do that, see [Tachi Setup](../setup.md).
|
||||
|
||||
## Using `musicdb.xml`
|
||||
|
||||
We have a script that already exists for parsing a `musicdb.xml` file and merging it with the database seeds.
|
||||
This is convenient for when you want to provide an update for DDR based off data you have.
|
||||
|
||||
### Importing DDR gamedata
|
||||
|
||||
Navigate to `seeds/scripts/rerunners/ddr`.
|
||||
The file you're looking to run is `parse-gameData-xml.ts`, but it needs two arguments:
|
||||
|
||||
- `--input`, which is the location of the `musicdb.xml` file you're looking to parse.
|
||||
- `--version`, what version of the game this music db is from. For a list of versions, see the [DDR Config](../../game-support/games/ddr-SP.md#versions).
|
||||
|
||||
You can run this script by typing `bun parse-gameData-xml.ts --input YOUR_INPUT_HERE --version YOUR_VERSION_HERE` in the terminal.
|
||||
|
||||
For example, if your file is in the same directory as the script, and you're importing from DDR World:
|
||||
`bun parse-gameData-xml.ts --input ./musicdb.xml --version world`
|
||||
|
||||
!!! warning "Warning"
|
||||
|
||||
If all the difficulties in your XML file are set to `255`, you will need to enter them manually before importing.
|
||||
Otherwise, the song won't be imported properly, and the difficulties won't show up.
|
||||
|
||||
If the song is also in DDR Konaste, and you have the relevant json file, you can copy the values over.
|
||||
Alternatively, you can use [RemyWiki](https://remywiki.com/). Look up the song's name, then copy the difficulties manually from the table.
|
||||
|
||||
- Search for your song on RemyWiki.
|
||||
- Scroll down to "Difficulty & Notecounts", then look for the table containing your game version.
|
||||
- Manually copy the difficulties from it.
|
||||
|
||||
For example, with [Cheerleader](https://remywiki.com/Cheerleader):
|
||||
The difficulty in `musicdb.xml` is set to `<diffLv __type="u8" __count="10">255 255 255 255 255 0 255 255 255 255</diffLv>`, which is incorrect.
|
||||
After following these steps, you should end up with `<diffLv __type="u8" __count="10">2 6 9 13 15 0 6 9 13 15</diffLv>`.
|
||||
|
||||
Notes: There are 10 numbers, each one corresponding to a level of difficulty.
|
||||
The first 5 are for SP, and the remaining are for DP.
|
||||
The difficulty levels range from *BEGINNER*, *BASIC*, *DIFFICULT*, *EXPERT*, to *CHALLENGE*.
|
||||
Any difficulty above 19 will be ignored, and a difficulty of 0 means that the song does not have it.
|
||||
The *BEGINNER* field for DP is always set to 0 because it doesn't exist.
|
||||
|
||||
### Updating stepCounts
|
||||
|
||||
Step counts are generated from SSQ files. You're on your own to find them.
|
||||
|
||||
Place all of your SSQ files in the `ssq` folder. The name of each file should correspond to the `basename` of each song, which is a 4~5 characters identifier.
|
||||
Then, run `bun parse-charts.ts`
|
||||
|
||||
|
||||
## Using a JSON file
|
||||
|
||||
We have a script that already exists for parsing a JSON file and merging it with the database seeds.
|
||||
This is convenient for when you want to provide an update for DDR based off data you have.
|
||||
|
||||
!!! danger "Important"
|
||||
|
||||
JSON files are usually used for Konaste game data, and therefore won't have all arcade songs.
|
||||
Due to encoding differences, songs may contain Unicode characters in them instead of their actual names. You will have to manually fix this.
|
||||
Additionally, the basename of existing songs may be edited incorrectly.
|
||||
Therefore, this option is **not** recommended.
|
||||
|
||||
Navigate to `seeds/scripts/rerunners/ddr`.
|
||||
The file you're looking to run is `parse-gameData-json.ts`, but it needs two arguments:
|
||||
|
||||
- `--input`, which is the location of the JSON file you're looking to parse.
|
||||
- `--version`, what version of the game this music db is from. For a list of versions, see the [DDR Config](../../game-support/games/ddr-SP.md#versions).
|
||||
|
||||
You can run this script by typing `bun parse-gameData-json.ts --input YOUR_INPUT_HERE --version YOUR_VERSION_HERE` in the terminal.
|
||||
|
||||
For example, if your file is in the same directory as the script, and you're importing from DDR Konaste:
|
||||
`bun parse-gameData-json.ts --input ./music.json --version konaste`
|
||||
@@ -1,24 +0,0 @@
|
||||
# Merging IIDX Data into seeds
|
||||
|
||||
Updating seeds via IIDX data is a complicated endeavour. Luckily, we've automated it with scripts.
|
||||
|
||||
!!! important
|
||||
You need to have a working setup of Tachi to use this script.
|
||||
|
||||
For instructions on how to do that, see [Tachi Setup](../setup.md).
|
||||
|
||||
## How to use
|
||||
|
||||
You will need `ifstools` in your `$PATH`. You can do this by installing `python3` and then running `pip install ifstools`.
|
||||
|
||||
Navigate to `seeds/scripts/rerunners/iidx/iidx-mdb-parse`.
|
||||
|
||||
Run `bun merge-mdb.ts --help` for information on what arguments are needed.
|
||||
|
||||
Fill out those arguments, and run the script in the terminal. If everything has gone correctly, `charts-iidx.json` and `songs-iidx.json` will be updated accordingly.
|
||||
|
||||
## Blacklisting
|
||||
|
||||
Sometimes, you might want to exclude certain charts from the parser.
|
||||
Edit `blacklist.txt` accordingly.
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# Merging a SDVX music_db.xml into seeds
|
||||
|
||||
We have a script that already exists for parsing a `music_db.xml` file and merging it with the database seeds.
|
||||
This is convenient for when you want to provide an update for SDVX based off data you have.
|
||||
|
||||
!!! important
|
||||
You need to have a working setup of Tachi to use this script.
|
||||
|
||||
For instructions on how to do that, see [Tachi Setup](../setup.md).
|
||||
|
||||
## How to use
|
||||
|
||||
Navigate to `seeds/scripts/rerunners/sdvx`.
|
||||
|
||||
The file you're looking to run is `merge-mdb.ts`, but it needs two arguments:
|
||||
|
||||
- `--input`, which is the location of the `music_db.xml` file you're looking to parse.
|
||||
- `--version`, what version of the game this music db is from. For a list of versions, see the [SDVX Config](../../game-support/games/sdvx-Single.md#versions).
|
||||
|
||||
You can run this script by typing `bun merge-mdb.ts --input YOUR_INPUT_HERE --version YOUR_VERSION_HERE` in the terminal.
|
||||
@@ -1,27 +0,0 @@
|
||||
# Contributing Overview
|
||||
|
||||
So, you're interested in contributing to Tachi? Awesome! All help is appreciated.
|
||||
But, what *can* you contribute, and where can you help out?
|
||||
|
||||
## What does Tachi need?
|
||||
|
||||
Contrary to what you might already think, contributing to Tachi actually has remarkably little to
|
||||
do with code.
|
||||
|
||||
The primary Tachi maintainer (henceforth referred to as `zk`, because that's their name)
|
||||
deals with the core of the codebase; things like importing scores, adding new features and most of the UI work
|
||||
is handled by them.
|
||||
|
||||
Tachi actually doesn't really have a shortage of people who can write good code, and the core of the codebase
|
||||
feature-wise is maintainable entirely by `zk` comfortably.
|
||||
|
||||
What **isn't** possible to maintain, is the sheer amount of things going on in every game! `zk`
|
||||
cannot reasonably keep track of all the games we support and everything that's going on in them.
|
||||
|
||||
That's where you step in - people who actually actively *play* games on Tachi! By letting `zk`
|
||||
know about things going on in your game - and even contributing things yourself - you help make their life easier
|
||||
and Tachi's support for your favourite game even better!
|
||||
|
||||
## Sounds good.
|
||||
|
||||
Awesome! Start with the [Setup](./setup.md) guide. This will get Tachi running on your local PC, so you can test all your changes.
|
||||
@@ -1,173 +0,0 @@
|
||||
# Setting up a Local Development Environment
|
||||
|
||||
Before you can contribute to Tachi, it'll help to have a functional setup on your machine.
|
||||
|
||||
You don't _necessarily_ need to have a working install to contribute - you could easily
|
||||
make documentation contributions without having anything running on your machine - but
|
||||
it's extremely helpful to be able to run Tachi's things while working on them.
|
||||
|
||||
## 0. Install the basics
|
||||
|
||||
### VSCode
|
||||
|
||||
We'll need a code editor so we can actually edit Tachi's code.
|
||||
|
||||
Please install [VSCode](https://code.visualstudio.com).
|
||||
We'll use this as our editor because of it's excellent support for dev containers.
|
||||
|
||||
### Terminal
|
||||
|
||||
You'll also need a terminal to run commands in. For Linux and Mac users, you can just open
|
||||
a Terminal app.
|
||||
|
||||
However, for Windows users we recommend installing the [Windows Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=en-gb&gl=GB).
|
||||
|
||||
With a terminal open you can proceed to the next steps!
|
||||
|
||||
### Git
|
||||
|
||||
You'll need `git` to clone Tachi to your machine.
|
||||
|
||||
=== "Windows"
|
||||
Install git [from the official website](https://git-scm.com/downloads).
|
||||
|
||||
=== "Ubuntu, Debian"
|
||||
Open a terminal and type this:
|
||||
`sh
|
||||
sudo apt install git
|
||||
`
|
||||
|
||||
=== "Arch, Manjaro"
|
||||
Open a terminal and type this:
|
||||
|
||||
```sh
|
||||
sudo pacman -S git
|
||||
```
|
||||
|
||||
=== "MacOS"
|
||||
Open a terminal and type this:
|
||||
|
||||
```sh
|
||||
brew install git
|
||||
```
|
||||
|
||||
## 1. Getting Docker.
|
||||
|
||||
To set everything else up for local development, we'll use [Docker](https://docker.com).
|
||||
|
||||
=== "Windows, WSL Ubuntu"
|
||||
You should install [Docker Desktop](https://docs.docker.com/desktop/) instead.
|
||||
Docker doesn't work well inside WSL.
|
||||
|
||||
=== "Debian"
|
||||
[Please use the official Docker install guide.](https://docs.docker.com/engine/install/debian/)
|
||||
|
||||
=== "Ubuntu"
|
||||
[Please use the official Docker install guide.](https://docs.docker.com/engine/install/ubuntu/)
|
||||
|
||||
=== "Arch, Manjaro"
|
||||
Open a terminal and type this:
|
||||
|
||||
```sh
|
||||
sudo pacman -S docker docker-compose
|
||||
```
|
||||
|
||||
=== "MacOS"
|
||||
Open a terminal and type this:
|
||||
|
||||
```sh
|
||||
brew install docker docker-compose
|
||||
```
|
||||
|
||||
!!! info
|
||||
Docker is like a VM[^1]. It runs an entire Linux box to contain your software in, and generally sidesteps the whole "works on some machines" problem.
|
||||
|
||||
## 2. Fork and pull the repo.
|
||||
|
||||
Since you can't just commit straight to someone else's codebase (that would be a massive security issue), you need to make a fork of Tachi - One owned by you!
|
||||
|
||||
Go to [the Tachi repository](https://github.com/zkldi/Tachi) and click the Fork button in the top right (Make sure you're signed in).
|
||||
|
||||
Now, back to the terminal:
|
||||
|
||||
!!! tip
|
||||
It's good organisation to make a folder on your PC for codestuffs.
|
||||
If you do that, make sure you open the terminal in that folder,
|
||||
so your Tachi repo will save there!
|
||||
|
||||
Open a terminal and type the following commands:
|
||||
|
||||
```sh
|
||||
# This will create a folder called Tachi on your PC.
|
||||
# It'll create it wherever your terminal is currently open in.
|
||||
git clone https://github.com/YOUR_GITHUB_USERNAME/Tachi
|
||||
|
||||
# Open this repository in VSCode!
|
||||
code Tachi
|
||||
```
|
||||
|
||||
## 3. Get into the container.
|
||||
|
||||
### What is a container?
|
||||
|
||||
Your personal machine could be running anything. Windows, Mac, Linux, whatever!
|
||||
Tachi expects to be running on Linux and with specific versions of certain software running.
|
||||
It's a huge pain to ask _you_ to install that software and manage it yourself.
|
||||
Plus, subtle differences between Windows and Linux cause problems _all_ the time.
|
||||
|
||||
As such, we work _inside_ a docker container. This is sort of like having a Linux VM with everything set up perfectly for you.
|
||||
I've spent quite a bit of time making this container user friendly, and it has so many nice things pre-installed for you.
|
||||
|
||||
Perhaps more importantly, the container has everything needed to run Tachi perfectly. Neat!
|
||||
|
||||
### Getting into it
|
||||
|
||||
With `VSCode` open to Tachi, install the [Dev Container](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension.
|
||||
|
||||
Then, hit `Ctrl+Shift+P` to view all commands, and run `Dev Containers: Rebuild and Reopen in Container`.
|
||||
|
||||
!!! warning
|
||||
First time setup can take a very long time.
|
||||
This depends on the performance of your machine, and whether you're using Windows or not.
|
||||
|
||||
You can click `view log` in the bottom right to see the progress of making the container.
|
||||
|
||||
### Working in the container
|
||||
|
||||
**You want to do ALL your work inside the container.**
|
||||
Doing thing outside of the container will cause issues or crashes.
|
||||
|
||||
There is a subtle confusing trick here. We now want to use a terminal _inside_ our container.
|
||||
**Do not use a terminal outside of VSCode now.**
|
||||
|
||||
To open a terminal inside `VSCode`, use `Ctrl+J` to open the bottom panel.
|
||||
Inside there there should be a `TERMINAL` tab, click that.
|
||||
There should be a `+` at the top right of the panel. Click that to open a new terminal!
|
||||
|
||||
You should see a message starting with `Welcome to Tachi!`. Inside this shell, you have full access to Tachi and all of its utilities.
|
||||
|
||||
## 4. Authenticate with Github.
|
||||
|
||||
You'll need to authenticate with GitHub before you can actually push changes back
|
||||
to your repository.
|
||||
|
||||
Type `gh auth login` and follow the instructions.
|
||||
You should now be properly authenticated!
|
||||
|
||||
## 5. Start Tachi!
|
||||
|
||||
With a terminal open inside the `Tachi` container you just cloned, run `just start`.
|
||||
|
||||
The frontend will be running on `http://localhost:3000`.
|
||||
The backend listens on `http://localhost:8080`, and the Vite dev server **proxies** `/api` and `/ir` there so the browser talks to the app on port 3000 only (same-origin HTTP, no self-signed certificates).
|
||||
|
||||
!!! tip
|
||||
Type `just` in the terminal to see other available commands.
|
||||
|
||||
Navigate to http://localhost:3000 and check your Tachi instance!
|
||||
|
||||
## 6. OK, Now what.
|
||||
|
||||
Now that you've got a working version of Tachi running on your local PC, you should go check out the [component-specific contribution guides](./components.md)!
|
||||
|
||||
[^1]: Docker is not actually a VM, it's significantly smarter and does some Linux jail cgroup nonsense. All you need to care about is that we're using it to spawn Linux VMs on your host system.
|
||||
@@ -1,165 +0,0 @@
|
||||
# Client Implementation
|
||||
|
||||
As you probably can expect, the client implementation is entirely frontend-related stuff.
|
||||
|
||||
This includes things like how to render score tables for this GPT, and how to colour
|
||||
in enums.
|
||||
|
||||
## Where do Client Implementations go?
|
||||
|
||||
Implementations should be written in `client/src/lib/game-implementations.tsx`.
|
||||
|
||||
It's fine to inline implementations here, but feel free to break out into a separate file (See what IIDX does for a reference) if you need to.
|
||||
|
||||
## `enumColours`
|
||||
|
||||
For all the ENUM metrics in this game, give all of their values colours.
|
||||
|
||||
!!! tip
|
||||
The `COLOUR_SET` global is useful for this, as it provides consistent identity
|
||||
throughout tachi.
|
||||
|
||||
## `enumIcons`
|
||||
|
||||
What [Font Awesome v5](https://fontawesome.com/v5/search) icon should we use to
|
||||
represent each enum?
|
||||
|
||||
## `difficultyColours`
|
||||
|
||||
If this game uses fixed difficulties, give each difficulty name a colour here.
|
||||
|
||||
## `classColours`
|
||||
|
||||
Give each class value a colour. These are used to render the class badges.
|
||||
|
||||
## `ratingSystems`
|
||||
|
||||
Although all games have `level` and `levelNum`, some optional properties may be useful here.
|
||||
|
||||
This is an array of functions that take a chart and return information about it.
|
||||
|
||||
These are used to sort charts in tables when the `difficulty` header is used to sort.
|
||||
|
||||
!!! example
|
||||
The `CreateRatingSys` util is used for this:
|
||||
|
||||
```ts
|
||||
[
|
||||
CreateRatingSys(
|
||||
"NC Tier",
|
||||
"Tierlist Ratings for Normal Clears.",
|
||||
(c) => c.data.ncTier?.value,
|
||||
(c) => c.data.ncTier?.text,
|
||||
(c) => c.data.ncTier?.individualDifference
|
||||
),
|
||||
CreateRatingSys(
|
||||
"HC Tier",
|
||||
"Tierlist Ratings for Hard Clears.",
|
||||
(c) => c.data.hcTier?.value,
|
||||
(c) => c.data.hcTier?.text,
|
||||
(c) => c.data.hcTier?.individualDifference
|
||||
),
|
||||
CreateRatingSys(
|
||||
"EXHC Tier",
|
||||
"Tierlist Ratings for EX-HARD Clears.",
|
||||
(c) => c.data.exhcTier?.value,
|
||||
(c) => c.data.exhcTier?.text,
|
||||
(c) => c.data.exhcTier?.individualDifference
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
!!! example
|
||||
ITG also uses this to leverage the sorting abilities of this: we want to sort
|
||||
things on level, but if the level is the same, we want to break ties on BPM.
|
||||
|
||||
```ts
|
||||
[
|
||||
CreateRatingSys(
|
||||
"BPM",
|
||||
"How fast are the streams in this chart?",
|
||||
(c) => c.data.streamBPM,
|
||||
(c) => c.data.streamBPM?.toString()
|
||||
),
|
||||
]
|
||||
```
|
||||
|
||||
## `scoreHeaders`
|
||||
|
||||
What should the headers be for the score cells when rendering scores for this GPT?
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
[
|
||||
["Score", "Score", NumericSOV((x) => x.scoreData.percent)],
|
||||
["Deltas", "Deltas", NumericSOV((x) => x.scoreData.percent)],
|
||||
["Lamp", "Lamp", NumericSOV((x) => x.scoreData.enumIndexes.lamp)],
|
||||
]
|
||||
```
|
||||
|
||||
will correspond to the headers in the red box
|
||||
|
||||

|
||||
|
||||
## `scoreCoreCells`
|
||||
|
||||
When rendering a score row, how should we render the actual score information cells?
|
||||
|
||||
This function gets `sc`, which is either a score or a PB for this GPT, and `chart`; the chart this score was on.
|
||||
|
||||
!!! important
|
||||
The amount of cells returned should be **EXACTLY** the same length as the headers.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
({ sc }) => (
|
||||
<>
|
||||
<MillionsScoreCell
|
||||
score={sc.scoreData.score}
|
||||
grade={sc.scoreData.grade}
|
||||
colour={GetEnumColour(sc, "grade")}
|
||||
/>
|
||||
<PopnJudgementCell score={sc} />
|
||||
<PopnLampCell score={sc} />
|
||||
</>
|
||||
),
|
||||
```
|
||||
|
||||
will correspond to the cells in these columns.
|
||||
|
||||

|
||||
|
||||
## `ratingCell`
|
||||
|
||||
How should we render the rating cell for this GPT?
|
||||
|
||||
This is a function that takes in the aforementioned `sc` and `chart`, alongside `rating`, which is the currently selected score rating algorithm.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
({ sc, chart, rating }) => (
|
||||
<>
|
||||
{rating === "blockRating" ? (
|
||||
<td>
|
||||
<strong>
|
||||
{chart.data.rankedLevel === null
|
||||
? "Unranked Chart."
|
||||
: sc.calculatedData.blockRating === null
|
||||
? "Failed"
|
||||
: sc.calculatedData.blockRating}
|
||||
</strong>
|
||||
</td>
|
||||
) : (
|
||||
<RatingCell score={sc} rating={rating} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
```
|
||||
|
||||
will correspond to the cells in this column.
|
||||
|
||||

|
||||
|
||||
## That's it!
|
||||
|
||||
Congrats! If you've done this, the [Server Implementation](./server-impl.md) and the [Common Configuration](./common-config/index.md), you've just added full support for a game to Tachi! Nice job!
|
||||
@@ -1,426 +0,0 @@
|
||||
# Common Configuration
|
||||
|
||||
This page documents how support for a game is written in `common/`.
|
||||
|
||||
There are two kinds of configs here.
|
||||
|
||||
## Where do configurations go?
|
||||
|
||||
Configurations should be written in `common/src/config/game-support/GAMENAME.ts`.
|
||||
|
||||
Once you have written a config, go to `common/src/config/config.ts` and import it. Mount the game configuration on `GAME_CONFIGS`, and the GPT configuration on `GAME_PT_CONFIGS`.
|
||||
|
||||
## A quick note on Games vs GPTs.
|
||||
|
||||
A game config is configuration for a game. Games in Tachi aren't actually the meaty part of support, instead, games form a kind of "group" for their playtypes.
|
||||
|
||||
For example, in IIDX there are two kinds of playtypes - Single Play and Double Play. Although these are completely separate kinds of games (it wouldn't make sense to share scores between them), they do share songs.
|
||||
|
||||
The game part - in this case `iidx` - defines things that are shared between all of its playtypes - things like what to call the game and what the song documents look like.
|
||||
|
||||
Whereas the game + playtype (typically shortened to GPT) is the part that contains almost all of the actual configuration. This distinction will become more obvious when you see the difference in size between the two configs.
|
||||
|
||||
!!! note
|
||||
I picked the shorthand `GPT` before ChatGPT et. al. blew up. Ah well.
|
||||
|
||||
## Game Configurations
|
||||
|
||||
Here is an example configuration - taken from our actual implementation of IIDX.
|
||||
|
||||
```ts
|
||||
export const IIDX_CONF = {
|
||||
name: "beatmania IIDX",
|
||||
playtypes: ["SP", "DP"],
|
||||
songData: z.strictObject({
|
||||
genre: z.string(),
|
||||
displayVersion: z.nullable(z.string()),
|
||||
}),
|
||||
} as const satisfies INTERNAL_GAME_CONFIG;
|
||||
```
|
||||
|
||||
!!! note
|
||||
`game` in Tachi is a bit of an unfortunate name. While - intuitively - most people
|
||||
will think of the game as the important part, `game` in Tachi functions more like
|
||||
a "grouping" of playtypes.
|
||||
|
||||
Our IIDX game contains two playtypes - SP and DP, these are implemented almost
|
||||
entirely separately -- SP and DP could have entirely different calculations if they
|
||||
wanted!
|
||||
|
||||
Things should be grouped together if the songs involved in the game can have
|
||||
multiple charts across-playtypes. Since songs in IIDX can have SP and DP difficulties,
|
||||
they share a `game`!
|
||||
|
||||
### `name`
|
||||
|
||||
This is the name for the game that will be displayed to end users. This should be formatted generally how the game formats things.
|
||||
|
||||
### `playtypes`
|
||||
|
||||
The list of playtypes this game supports. For games that only have one playtype (and likely will never have another), "Single" is typically used.
|
||||
|
||||
!!! example
|
||||
For SDVX, we use `playtypes: ["Single"]`. Since SDVX doesn't normally have any sort of separate playtypes.
|
||||
|
||||
### `songData`
|
||||
|
||||
What game-specific properties should exist on a song?
|
||||
|
||||
This is written as a [Zod](https://github.com/colinhacks/zod) schema, which allows us to simultaneously declare the type of additional data, and how to validate it.
|
||||
|
||||
This game-specific information is stored on the `data` field of a song.
|
||||
|
||||
!!! example
|
||||
Here's an example IIDX song:
|
||||
|
||||
```json
|
||||
{
|
||||
"altTitles": [],
|
||||
"artist": "dj nagureo",
|
||||
"data": {
|
||||
"displayVersion": "1",
|
||||
"genre": "PIANO AMBIENT"
|
||||
},
|
||||
"id": 1,
|
||||
"searchTerms": [],
|
||||
"title": "5.1.1."
|
||||
},
|
||||
```
|
||||
|
||||
Note the `data` field, which allows for game-specific metadata.
|
||||
|
||||
## GPT Configurations
|
||||
|
||||
This is the *real* meat-and-potatoes for implementing something for Tachi.
|
||||
|
||||
Here is our actual implementation for WACCA's Single playtype.
|
||||
|
||||
!!! note
|
||||
WACCA only has one playtype - "Single".
|
||||
|
||||
```ts
|
||||
export const WACCA_SINGLE_CONF = {
|
||||
providedMetrics: {
|
||||
score: {
|
||||
type: "INTEGER",
|
||||
validate: p.isBetween(0, 1_000_000),
|
||||
formatter: FmtNum,
|
||||
description: "The score value. This is between 0 and 1 million.",
|
||||
},
|
||||
lamp: {
|
||||
type: "ENUM",
|
||||
values: ["FAILED", "CLEAR", "MISSLESS", "FULL COMBO", "ALL MARVELOUS"],
|
||||
minimumRelevantValue: "CLEAR",
|
||||
description: "The type of clear this score was.",
|
||||
},
|
||||
},
|
||||
|
||||
derivedMetrics: {
|
||||
grade: {
|
||||
type: "ENUM",
|
||||
values: [
|
||||
"D",
|
||||
"C",
|
||||
"B",
|
||||
"A",
|
||||
"AA",
|
||||
"AAA",
|
||||
"S",
|
||||
"S+",
|
||||
"SS",
|
||||
"SS+",
|
||||
"SSS",
|
||||
"SSS+",
|
||||
"MASTER",
|
||||
],
|
||||
minimumRelevantValue: "S",
|
||||
description: "The grade this score was.",
|
||||
},
|
||||
},
|
||||
|
||||
optionalMetrics: {
|
||||
...FAST_SLOW_MAXCOMBO,
|
||||
},
|
||||
|
||||
defaultMetric: "score",
|
||||
preferredDefaultEnum: "grade",
|
||||
|
||||
scoreRatingAlgs: {
|
||||
rate: {
|
||||
description: "Rating as it's implemented in game.",
|
||||
},
|
||||
},
|
||||
profileRatingAlgs: {
|
||||
naiveRate: {
|
||||
description: "A naive rating algorithm that just sums your 50 best scores.",
|
||||
},
|
||||
rate: {
|
||||
description:
|
||||
"Rating as it's implemented in game, taking 15 scores from the latest version and 35 from all old versions.",
|
||||
},
|
||||
},
|
||||
sessionRatingAlgs: {
|
||||
rate: { description: "The average of your best 10 ratings this session." },
|
||||
},
|
||||
|
||||
defaultScoreRatingAlg: "rate",
|
||||
defaultProfileRatingAlg: "naiveRate",
|
||||
defaultSessionRatingAlg: "rate",
|
||||
|
||||
difficulties: {
|
||||
type: "FIXED",
|
||||
order: ["NORMAL", "HARD", "EXPERT", "INFERNO"],
|
||||
shorthand: {
|
||||
NORMAL: "NRM",
|
||||
HARD: "HRD",
|
||||
EXPERT: "EXP",
|
||||
INFERNO: "INF",
|
||||
},
|
||||
default: "EXPERT",
|
||||
},
|
||||
|
||||
classes: {
|
||||
stageUp: {
|
||||
type: "PROVIDED",
|
||||
values: WaccaStageUps,
|
||||
},
|
||||
colour: {
|
||||
type: "DERIVED",
|
||||
values: WaccaColours,
|
||||
},
|
||||
},
|
||||
|
||||
orderedJudgements: ["marvelous", "great", "good", "miss"],
|
||||
|
||||
versions: {
|
||||
reverse: "REVERSE",
|
||||
},
|
||||
|
||||
chartData: z.strictObject({}),
|
||||
|
||||
preferences: z.strictObject({}),
|
||||
scoreMeta: z.strictObject({ mirror: z.boolean().optional() }),
|
||||
|
||||
supportedMatchTypes: ["songTitle", "tachiSongID"],
|
||||
} as const satisfies INTERNAL_GAME_PT_CONFIG;
|
||||
```
|
||||
|
||||
We'll go over each bit of this.
|
||||
|
||||
## Metrics
|
||||
|
||||
See [Metrics](./metrics.md) and [Metric Groups](./metric-groups.md).
|
||||
|
||||
## `defaultMetric`
|
||||
|
||||
What should the default metric for this GPT be? This is used for chart leaderboards,
|
||||
and should *ideally* be an `INTEGER` or `DECIMAL` metric. It is not legal for this
|
||||
to be a `GRAPH` or `NULLABLE_GRAPH` metric, as those are not sanely comparable.
|
||||
|
||||
## `preferredDefaultEnum`
|
||||
|
||||
If the user has no preferences overriding this, what should this GPT default to showing
|
||||
when showing enum graphs?
|
||||
|
||||
!!! example
|
||||

|
||||
|
||||
Here, we see that the site has picked lamps by default to show. This is because
|
||||
the default enum for IIDX is `lamp`.
|
||||
|
||||
!!! note
|
||||
Users may override this preference in their settings for this GPT.
|
||||
|
||||
## Score, Session, Profile Rating Algorithms
|
||||
|
||||
These are all the exact same idea, but appear in different parts of Tachi.
|
||||
|
||||
All of these define the names of rating algorithms and a description.
|
||||
|
||||
Rating algorithms are functions that return a number or null. This can be used
|
||||
to implement things like VOLFORCE (SDVX, USC) or tierlist ratings.
|
||||
|
||||
Optionally, a rating algorithm's config may specify a `formatter:` field, which is
|
||||
a function that transforms the number into a string somehow. If this is not specified,
|
||||
this will default to a function that rounds the number to two decimal places.
|
||||
|
||||
## Difficulties
|
||||
|
||||
There are two kinds of difficulties available in Tachi. `"FIXED"`, which is a more traditional approach defining a fixed set of possible difficulties for your song (NORMAL, HYPER, ANOTHER, etc.), and `"DYNAMIC"`, which allows any arbitrary string as a difficulty name.
|
||||
|
||||
### Dynamic Difficulties
|
||||
|
||||
Dynamic difficulties are intended for use in games where a song may have any number of possible charts.
|
||||
|
||||
An example of this would be something like `osu!`, where a song may have as many difficulties as it wants, with *any* string as their name.
|
||||
|
||||
### Fixed Difficulties
|
||||
|
||||
For games where songs can only have a certain amount of difficulties (most arcade games)
|
||||
a fixed config likely makes more sense.
|
||||
|
||||
With a fixed config, you define the `order:` in which these difficulties appear in,a default difficulty to redirect to if the song is selected in the search bar and
|
||||
optionally, some shorthand (generally for mobile view).
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
difficulties: {
|
||||
type: "FIXED",
|
||||
order: ["NORMAL", "HARD", "EXPERT", "INFERNO"],
|
||||
shorthand: {
|
||||
NORMAL: "NRM",
|
||||
HARD: "HRD",
|
||||
EXPERT: "EXP",
|
||||
INFERNO: "INF",
|
||||
},
|
||||
default: "EXPERT",
|
||||
}
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
Classes are enums for profiles. These allow you to store discrete values on a user's
|
||||
profile. What values are supported are controlled by the `values:` field.
|
||||
You can use the `ClassValue` helper function to help define values.
|
||||
|
||||
There are two types of classes:
|
||||
|
||||
### "DERIVED"
|
||||
|
||||
In which the value of the class is derived from the user's profile metrics.
|
||||
|
||||
!!! example
|
||||
A common example of this would be things like `colour` rankings in konami arcade
|
||||
games. These are cutoffs like `>1500 jubility = ORANGE, >2000 jubility = GREEN`.
|
||||
|
||||
### "PROVIDED"
|
||||
|
||||
In which the value of the class is provided by a score import somehow.
|
||||
|
||||
!!! example
|
||||
A common example of this would be things like dans. These are not functions
|
||||
of existing state, and must be stated in an import method.
|
||||
|
||||
!!! note
|
||||
"DERIVED" classes are allowed to go back down - if the deriving implementation
|
||||
says the class is now worse, the class the user has will decrease.
|
||||
|
||||
However, "PROVIDED" classes cannot be superceded by worse ones - if the user was
|
||||
10th dan and then cleared 3rd dan, their dan will *not* be overwritten. The only
|
||||
way for a "PROVIDED" class to go down is to contact an admin at the moment.
|
||||
|
||||
Maybe the in the future, users could have the ability to manually wipe their own
|
||||
classes, in-case they corrupt their profile.
|
||||
|
||||
## `orderedJudgements`
|
||||
|
||||
An ordered list (best to worst) of strings, representing the name of judgements (hit windows) for this GPT.
|
||||
|
||||
## Versions
|
||||
|
||||
See [Versions](./versions.md), as whether your GPT needs this or not requires some assessment.
|
||||
|
||||
## Chart Data
|
||||
|
||||
The `chartData` field allows you to define GPT-specific fields on chart documents.
|
||||
|
||||
For example, in IIDX we *need* to store the `notecount` of a chart somewhere (it's used to calculate `percent` and `grade`).
|
||||
|
||||
The `chartData` field is a [Zod](https://github.com/colinhacks/zod) schema indicating the structure of this additional information.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
chartData: z.strictObject({
|
||||
inGameID: z.number().int().nonnegative(),
|
||||
clearTier: z.strictObject({
|
||||
value: z.number(),
|
||||
text: z.string(),
|
||||
individualDifference: z.boolean(),
|
||||
}).nullable(),
|
||||
}),
|
||||
```
|
||||
|
||||
declares that a chart for this GPT should look like:
|
||||
|
||||
```ts
|
||||
{
|
||||
"chartID": "5088a4d0e1ee9d0cc2f625934306e45b1a60699b",
|
||||
"data": {
|
||||
"inGameID": 1,
|
||||
"clearTier": { value: 12.5, text: "12C", individualDifference: false }
|
||||
},
|
||||
"difficulty": "ADV",
|
||||
"isPrimary": true,
|
||||
"level": "10",
|
||||
"levelNum": 10,
|
||||
"playtype": "Single",
|
||||
"songID": 1,
|
||||
"versions": ["exceed", "konaste"]
|
||||
},
|
||||
```
|
||||
|
||||
Note the GPT-specific properties in the `data` field now.
|
||||
|
||||
## Preferences
|
||||
|
||||
If this GPT should have specific preferences (for BMS, you can set a list of tables you don't want to see, for example), list them here as a Zod schema.
|
||||
|
||||
!!! example
|
||||
A `preferences` of
|
||||
|
||||
```ts
|
||||
preferences: z.strictObject({ showCustomCharts: z.boolean() })
|
||||
```
|
||||
|
||||
corresponds to a settings document of:
|
||||
```ts
|
||||
[
|
||||
{
|
||||
"userID": 1,
|
||||
"game": "iidx",
|
||||
"playtype": "SP",
|
||||
"preferences": {
|
||||
"preferredScoreAlg": null,
|
||||
"preferredSessionAlg": null,
|
||||
"preferredProfileAlg": null,
|
||||
"preferredDefaultEnum": null,
|
||||
"defaultTable": null,
|
||||
"preferredRanking": null,
|
||||
"stats": [],
|
||||
"gameSpecific": {
|
||||
"showCustomCharts": false
|
||||
}
|
||||
},
|
||||
"rivals": []
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Note the `preferences.gameSpecific` part, which holds these game-specific
|
||||
preferences.
|
||||
|
||||
## Score Metadata
|
||||
|
||||
Sometimes, we want to store stuff about a score that isn't really related to a metric.
|
||||
|
||||
A common example of this would be something like the mods the player was using - were
|
||||
they using RANDOM, MIRROR, etc?
|
||||
|
||||
!!! note
|
||||
Score Metadata **DOES NOT** appear on PBs. This metadata is *specific* to the score
|
||||
itself, and would never make sense to merge.
|
||||
|
||||
If a PB is comprised of a best score using RANDOM, and a best lamp using MIRROR,
|
||||
what should the PB merge to? It doesn't make any sense as an operation.
|
||||
|
||||
This, similarly, is a Zod schema, and appears on a score documents `scoreMeta` field.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
This is for [BATCH MANUAL](todo) usage. BATCH MANUAL has to specify a `matchType` - how should it resolve a given identifier?
|
||||
|
||||
Some import methods might want to use in game IDs, hashes of a certain kind, etc.
|
||||
|
||||
For a list of all possible match types, see [Match Types](./match-types.md).
|
||||
|
||||
You should list the available `matchTypes` in this field of the config.
|
||||
@@ -1,61 +0,0 @@
|
||||
# Match Types
|
||||
|
||||
For [BATCH MANUAL](./todo) imports, we need a way of resolving given identifiers
|
||||
into charts on Tachi. These may be through a variety of methods, such as chart hashes,
|
||||
in game IDs, etc.
|
||||
|
||||
This is a list of **all** available match types. However, what match types are available
|
||||
depend on what game you're importing scores for.
|
||||
|
||||
## Identifier Only
|
||||
|
||||
The following match types only need an `identifier` defined.
|
||||
|
||||
### `bmsChartHash`
|
||||
|
||||
Uses `identifier` as an MD5 or SHA25 hash.
|
||||
|
||||
### `itgChartHash`
|
||||
|
||||
Uses `identifier` as a GroovestatsV3 hash.
|
||||
|
||||
### `popnChartHash`
|
||||
|
||||
Uses `identifier` as a SHA256 hash, for pop'n file contents.
|
||||
|
||||
### `uscChartHash`
|
||||
|
||||
Uses `identifier` as a SHA1 hash, since that's what USC uses.
|
||||
|
||||
## Identifier + Difficulty
|
||||
|
||||
These match types need both `identifier` and a `difficulty` defined.
|
||||
|
||||
### `ddrSongHash`
|
||||
|
||||
Looks up on the 32-character song hash for this chart, where the hash is from the official DDR e-amusement website.
|
||||
|
||||
### `inGameID`
|
||||
|
||||
Looks up on the in game ID for this chart.
|
||||
|
||||
### `sdvxInGameID`
|
||||
|
||||
Looks up on the in game ID for this chart, but allows a special difficulty string - `"ANY_INF"` to be passed.
|
||||
|
||||
If `"ANY_INF"` is the difficulty, then that difficulty will try to find a chart with this in game ID where the difficulty is any of INF, GRV, HVN, VVD or XCD.
|
||||
|
||||
### `songTitle`
|
||||
|
||||
Looks up the song on its title or any of its defined `altTitles`. Can also match the artist field of the song if an `artist` property is defined.
|
||||
|
||||
!!! warning
|
||||
It is generally not recommended to enable this method, as it's particularly error-prone. Scores that can't be unequivocally identified will get rejected.
|
||||
|
||||
### `tachiSongID`
|
||||
|
||||
Looks up the song on Tachi's defined `songID`.
|
||||
|
||||
## Adding a new Match Type
|
||||
|
||||
Does your GPT implementation need its own match type? See [Adding a new match type](./COOKBOOK TODO).
|
||||
@@ -1,102 +0,0 @@
|
||||
|
||||
# Metric Groups
|
||||
|
||||
Metrics for a GPT are grouped into three categories:
|
||||
|
||||
## Provided Metrics
|
||||
|
||||
These metrics **must** be provided in a score import. These are, in effect, **MANDATORY** metrics.
|
||||
|
||||
These metrics go into the scoreID.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
providedMetrics: {
|
||||
score: {
|
||||
type: "INTEGER",
|
||||
validate: p.isBetween(0, 1_000_000),
|
||||
formatter: FmtNum,
|
||||
description: "The score value. This is between 0 and 1 million.",
|
||||
},
|
||||
lamp: {
|
||||
type: "ENUM",
|
||||
values: ["FAILED", "CLEAR", "MISSLESS", "FULL COMBO", "ALL MARVELOUS"],
|
||||
minimumRelevantValue: "CLEAR",
|
||||
description: "The type of clear this score was.",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
Any score import for a GPT like this *must* provide a compliant `score:` and `lamp:` value.
|
||||
|
||||
## Derived Metrics
|
||||
|
||||
These metrics are derived from the provided metrics (and the chart the score was on), but stored on scores for conveniences sake. These will **ALWAYS** be present on a score.
|
||||
|
||||
It may also be the case that this derived form of a metric is the default metric for a game, in which case, we'd definitely want to store it for leaderboards!
|
||||
|
||||
These metrics **DO NOT** go into the scoreID, as they are merely functions of other captured state.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
derivedMetrics: {
|
||||
percent: {
|
||||
type: "DECIMAL",
|
||||
validate: p.isBetween(0, 100),
|
||||
formatter: FmtPercent,
|
||||
description: "EX Score divided by the maximum possible EX Score on this chart.",
|
||||
},
|
||||
grade: {
|
||||
type: "ENUM",
|
||||
values: ["F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX"],
|
||||
minimumRelevantValue: "A",
|
||||
description:
|
||||
"Grades as they are in IIDX. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience.",
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
In IIDX, the derived metrics are `percent` (as it's exScore / maximumEXOnThisChart)
|
||||
and `grade`, which is also derivable.
|
||||
|
||||
We want to store these values for convenience and displaying on the UI, so we will.
|
||||
|
||||
## Optional Metrics
|
||||
|
||||
Sometimes, we want to store some metrics that not all score imports could provide. For that, we have optional metrics.
|
||||
|
||||
These metrics are defined like any other, but are all allowed to be not-provided by an importer.
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
optionalMetrics: {
|
||||
bp: {
|
||||
type: "INTEGER",
|
||||
validate: p.isPositive,
|
||||
formatter: FmtScoreNoCommas,
|
||||
description: "The total bads + poors in this score.",
|
||||
},
|
||||
gauge: {
|
||||
type: "DECIMAL",
|
||||
validate: p.isBetween(0, 100),
|
||||
formatter: FmtPercent,
|
||||
description:
|
||||
"The life in percent (between 0 and 100) that was on the gauge at the end of the chart.",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
For IIDX, not all importing methods might know these values, and we don't want to exclude users of those services from being able to import their scores.
|
||||
|
||||
As such, we mark these as optional.
|
||||
|
||||
!!! warning
|
||||
It's possible, but rare, that an optional metric should be part of the scoreID.
|
||||
|
||||
For these cases, you can mark an optional metric with `partOfScoreID: true`.
|
||||
This metric will now - as you'd expect - be part of the scoreID.
|
||||
|
||||
We actually use this in SDVX for `exScore`. EX Score might not be available, as it
|
||||
doesn't necessarily have to be enabled by the user - but if it does exist, we want
|
||||
to track it properly. That is to say, if the user raises their `exScore` but nothing
|
||||
else, we should still count that as a new score - despite `exScore` being optional!
|
||||
@@ -1,203 +0,0 @@
|
||||
# Metrics
|
||||
|
||||
Metrics are what kind of values we want to track on this GPT's scores. Common things for this are values like `score`, `lamp`, maybe even `grade`.
|
||||
|
||||
A metric definition looks like this:
|
||||
|
||||
```ts
|
||||
{
|
||||
type: "INTEGER",
|
||||
validate: p.isBetween(0, 100_000),
|
||||
formatter: FmtNum,
|
||||
description: "The score value.",
|
||||
}
|
||||
```
|
||||
|
||||
## Metric Types
|
||||
|
||||
We're allowed 5 types of metrics:
|
||||
|
||||
- `DECIMAL`
|
||||
|
||||
This metric is expected to be a decimal.
|
||||
|
||||
!!! example
|
||||
`ts
|
||||
percent: {
|
||||
type: "DECIMAL",
|
||||
validate: p.isBetween(0, 100),
|
||||
formatter: FmtPercent,
|
||||
description: "EX Score divided by the maximum possible EX Score on this chart.",
|
||||
},
|
||||
`
|
||||
|
||||
- `INTEGER`
|
||||
|
||||
This metric is expected to be a whole number.
|
||||
|
||||
!!! example
|
||||
`ts
|
||||
{
|
||||
type: "INTEGER",
|
||||
validate: p.isBetween(0, 100_000),
|
||||
formatter: FmtNum,
|
||||
description: "The score value.",
|
||||
}
|
||||
`
|
||||
|
||||
- `ENUM`
|
||||
|
||||
This metric is expected to be a string in a provided ordered list of strings. This is used to implement things like `"FAILED", "CLEAR", "FULL COMBO", "PERFECT"` lamps.
|
||||
|
||||
!!! example
|
||||
`ts
|
||||
{
|
||||
type: "ENUM",
|
||||
values: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
minimumRelevantValue: "EASY CLEAR",
|
||||
description: "The type of clear this was.",
|
||||
},
|
||||
`
|
||||
|
||||
- `GRAPH`
|
||||
|
||||
This metric is an array of numbers.
|
||||
|
||||
- `NULLABLE_GRAPH`
|
||||
|
||||
This metric is an array of numbers or null.
|
||||
|
||||
!!! example
|
||||
`ts
|
||||
gaugeHistory: {
|
||||
type: "NULLABLE_GRAPH",
|
||||
validate: p.isBetween(0, 100),
|
||||
description:
|
||||
"A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart.",
|
||||
},
|
||||
`
|
||||
|
||||
Importantly: something like `[99, 74, 12, null, null, null]` would be legal as a nullable graph.
|
||||
|
||||
This does not mean the field itself is nullable, only the values inside the array!
|
||||
|
||||
## Enum Metrics
|
||||
|
||||
Metrics of type `ENUM` are special in many ways. Most importantly, they don't require
|
||||
any sort of validation - the input is either a member of `values` or it isn't.
|
||||
|
||||
An ENUM metric looks like this:
|
||||
|
||||
```ts
|
||||
{
|
||||
type: "ENUM",
|
||||
values: [
|
||||
"NO PLAY",
|
||||
"FAILED",
|
||||
"ASSIST CLEAR",
|
||||
"EASY CLEAR",
|
||||
"CLEAR",
|
||||
"HARD CLEAR",
|
||||
"EX HARD CLEAR",
|
||||
"FULL COMBO",
|
||||
],
|
||||
minimumRelevantValue: "EASY CLEAR",
|
||||
description: "The type of clear this was.",
|
||||
},
|
||||
```
|
||||
|
||||
The order of `values` is extremely important: the first value should be the worst possible value in the metric, and the last value should be the best possible value.
|
||||
|
||||
`minimumRelevantValue` indicates the smallest value in this enum that a user will
|
||||
ever realistically care about getting. This is used in the UI and other places to
|
||||
hide useless ENUM values from the user.
|
||||
|
||||
!!! example
|
||||

|
||||
|
||||
Despite the fact this user failed a bunch of stuff this session, the UI won't
|
||||
show me my new `FAILED` or `ASSIST CLEAR` scores!
|
||||
|
||||
## Integer and Decimal Metrics
|
||||
|
||||
These kinds of metrics, given their more open-ended nature, require some validation.
|
||||
|
||||
### Validation
|
||||
|
||||
There are two kinds of metric validations. If a metric can be validated, it should have
|
||||
a property called `validate` which returns true on success, and a string representing
|
||||
an error message on failure:
|
||||
|
||||
!!! example
|
||||
```ts
|
||||
{
|
||||
type: "INTEGER",
|
||||
validate: (value) => {
|
||||
if (value > 100_000) { return "Score cannot be greater than 100k." }
|
||||
|
||||
return true;
|
||||
},
|
||||
formatter: FmtNum,
|
||||
description: "The score value.",
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
The type of input is already checked for you. You don't need to check whether your
|
||||
input is an integer or not if you declare `type: "INTEGER"`. The same goes for
|
||||
`type: "GRAPH"`, the validator instead runs on each element in the array.
|
||||
|
||||
!!! tip
|
||||
The reason for this rather strange type signature (`string | true`) is because we
|
||||
can use our validation library - [Prudence](https://github.com/zkldi/Prudence) - to create validators for us. Instead
|
||||
of writing out that validation code, we can use `p.isBetween(0, 100_000)`!
|
||||
|
||||
However, it might not be possible to validate a metric without knowing what chart
|
||||
the score is on. For example, in IIDX the maximum amount of score you can get is
|
||||
the chart's notecount \* 2.
|
||||
|
||||
Since we don't know what this GPT chart's look like at this time (we haven't defined them yet!)
|
||||
we instead don't declare a validator and put down `chartDependentMax: true` instead.
|
||||
|
||||
We will have to write the validator later in the server implementation.
|
||||
|
||||
### Formatting
|
||||
|
||||
We also want to format these metrics into a string. For example, a `percent` metric may want to stringify into `92.45%` instead of `92.49938222293842`.
|
||||
More subtle formatting rules can be defined here too, such as how in Jubeat, scores are
|
||||
easier to read written like `903,283` instead of `903283`.
|
||||
|
||||
## Graph Metrics
|
||||
|
||||
Graph metrics (`GRAPH` and `NULLABLE_GRAPH`) also have to define a validator, but the validator is called on each element of their respective arrays.
|
||||
|
||||
To validate that all of the values in a graph metric are between 0 and 10, you could
|
||||
write a validator like:
|
||||
|
||||
```ts
|
||||
function validate(value: number) {
|
||||
if (value < 0) return "too small";
|
||||
if (value > 10) return "too big";
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
This is remarkably similar to how validators work for `INTEGER` and `DECIMAL` metrics.
|
||||
|
||||
Furthermore, graph metrics can specify a `size` function, which validates the size of
|
||||
the array too. You can use this to enforce a certain array size (such as exactly 100 entries, or similar).
|
||||
|
||||
## Now what?
|
||||
|
||||
This is how metrics work, but there's more detail in how they're defined in each game's config.
|
||||
|
||||
For example, some metrics (like `grade` for a lot of games) are merely functions of other metrics. Making every import method handle this themselves seems silly. How do we tackle this? This, and more, is answered in [Metric Groups](./metric-groups.md)!
|
||||
@@ -1,32 +0,0 @@
|
||||
# Versions
|
||||
|
||||
Versions are a method for us marking charts as being available in a certain "version" of the game.
|
||||
|
||||
The predominant use for this is for arcade games, where new versions of the game generally come out and add/remove charts.
|
||||
|
||||
It's useful for us to know what charts are available in what version for two purposes.
|
||||
|
||||
The first allows us to create folders - users playing on a certain version of a game don't want to see charts they can't play!
|
||||
|
||||
The second is more complex.
|
||||
|
||||
## Disambiguation and `isPrimary`
|
||||
|
||||
Sadly, the real world isn't easy to deal with. It's possible for a chart to exist in
|
||||
version A of a game, get removed in version B, and then come back in version C with
|
||||
new, completely different charts.
|
||||
|
||||
**Worse still**, it's possible for that song to use the *exact same* in game ID it used
|
||||
before. If we use `inGameID` to identify things, now we've got a critical ambiguity!
|
||||
|
||||
Does `inGameID: 1, difficulty: ANOTHER` refer to the version A chart, or the version C one? For games where metrics are chart-dependent, this could be *lethal* to the leaderboards.
|
||||
|
||||
It's much easier to get a good score in IIDX if the chart thinks it has less notes than it actually has!
|
||||
|
||||
To fix this, we also use versions as an identifer for disambiguation. If a score comes
|
||||
in, it **MAY** pass what version the score was attained on.
|
||||
|
||||
This allows us to immediately disambiguate the previous case.
|
||||
|
||||
If a version is **not** passed in, we assume that the score is for the chart marked as
|
||||
`isPrimary: true`. Furthermore, seeds tests **enforce** that duplicates on certain IDs expected-to-be-unique cannot be true unless all-but-one-of-them is marked as `isPrimary: false`.
|
||||
@@ -1,87 +0,0 @@
|
||||
# Arcaea Support
|
||||
|
||||
This game has the internal GPTString of `arcaea:Touch`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 10 million, plus bonus points dependent on how many shiny PUREs you get. |
|
||||
| `lamp` | "LOST", "EASY CLEAR", "CLEAR", "HARD CLEAR", "FULL RECALL", "PURE MEMORY" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "D", "C", "B", "A", "AA", "EX", "EX+" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pure`
|
||||
- `far`
|
||||
- `lost`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `potential` | Potential as it is implemented in Arcaea. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naivePotential` | The average of your best 10 potentials this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naivePotential` | The average of your best 30 potential values. This is different to in-game, as it does not take into account your recent scores in any way. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `Past`
|
||||
- `Present`
|
||||
- `Future`
|
||||
- `Eternal`
|
||||
- `Beyond`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `badge` | DERIVED | BLUE, GREEN, ASH_PURPLE, PURPLE, RED, ONE_STAR, TWO_STARS, THREE_STARS
|
||||
| `courseBanner` | PROVIDED | PHASE_1, PHASE_2, PHASE_3, PHASE_4, PHASE_5, PHASE_6, PHASE_7, PHASE_8, PHASE_9, PHASE_10, PHASE_11, PHASE_12
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `mobile` | Mobile |
|
||||
| `switch` | Nintendo Switch |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameStrID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
@@ -1,98 +0,0 @@
|
||||
# BMS (14K) Support
|
||||
|
||||
This game has the internal GPTString of `bms:14K`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in BMS. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `gaugeHistory` | Array<Decimal> | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `gaugeHistoryEasy` | Array<Decimal> | The easy gauge history. |
|
||||
| `gaugeHistoryGroove` | Array<Decimal> | The groove gauge history. |
|
||||
| `gaugeHistoryHard` | Array<Decimal> | The hard gauge history. |
|
||||
| `epg` | Integer | The amount of early PGreats in this score. |
|
||||
| `egr` | Integer | The amount of early greats in this score. |
|
||||
| `egd` | Integer | The amount of early goods in this score. |
|
||||
| `ebd` | Integer | The amount of early bads in this score. |
|
||||
| `epr` | Integer | The amount of early poors in this score. |
|
||||
| `lpg` | Integer | The amount of late PGreats in this score. |
|
||||
| `lgr` | Integer | The amount of late greats in this score. |
|
||||
| `lgd` | Integer | The amount of late goods in this score. |
|
||||
| `lbd` | Integer | The amount of late bads in this score. |
|
||||
| `lpr` | Integer | The amount of late poors in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pgreat`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 10 sieglinde ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 20 sieglinde ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `CHART`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `genocideDan` | PROVIDED | NORMAL_1, NORMAL_2, NORMAL_3, NORMAL_4, NORMAL_5, NORMAL_6, NORMAL_7, NORMAL_8, NORMAL_9, NORMAL_10, INSANE_1, INSANE_2, INSANE_3, INSANE_4, INSANE_5, INSANE_6, INSANE_7, INSANE_8, INSANE_9, INSANE_10, INSANE_KAIDEN, OVERJOY
|
||||
| `stslDan` | PROVIDED | SL0, SL1, SL2, SL3, SL4, SL5, SL6, SL7, SL8, SL9, SL10, SL11, SL12
|
||||
|
||||
## Versions
|
||||
|
||||
This game has no versions, and presumably doesn't need to disambiguate its IDs.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `bmsChartHash`
|
||||
- `tachiSongID`
|
||||
@@ -1,101 +0,0 @@
|
||||
# BMS (7K) Support
|
||||
|
||||
This game has the internal GPTString of `bms:7K`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in BMS. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `gaugeHistory` | Array<Decimal> | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `gaugeHistoryEasy` | Array<Decimal> | The easy gauge history. |
|
||||
| `gaugeHistoryGroove` | Array<Decimal> | The groove gauge history. |
|
||||
| `gaugeHistoryHard` | Array<Decimal> | The hard gauge history. |
|
||||
| `epg` | Integer | The amount of early PGreats in this score. |
|
||||
| `egr` | Integer | The amount of early greats in this score. |
|
||||
| `egd` | Integer | The amount of early goods in this score. |
|
||||
| `ebd` | Integer | The amount of early bads in this score. |
|
||||
| `epr` | Integer | The amount of early poors in this score. |
|
||||
| `lpg` | Integer | The amount of late PGreats in this score. |
|
||||
| `lgr` | Integer | The amount of late greats in this score. |
|
||||
| `lgd` | Integer | The amount of late goods in this score. |
|
||||
| `lbd` | Integer | The amount of late bads in this score. |
|
||||
| `lpr` | Integer | The amount of late poors in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pgreat`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 10 sieglinde ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 20 sieglinde ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `CHART`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `genocideDan` | PROVIDED | NORMAL_1, NORMAL_2, NORMAL_3, NORMAL_4, NORMAL_5, NORMAL_6, NORMAL_7, NORMAL_8, NORMAL_9, NORMAL_10, INSANE_1, INSANE_2, INSANE_3, INSANE_4, INSANE_5, INSANE_6, INSANE_7, INSANE_8, INSANE_9, INSANE_10, INSANE_KAIDEN, OVERJOY
|
||||
| `stslDan` | PROVIDED | SL0, SL1, SL2, SL3, SL4, SL5, SL6, SL7, SL8, SL9, SL10, SL11, SL12, ST0, ST1, ST2, ST3, ST4, ST5, ST6, ST7, ST8, ST9, ST10, ST11, ST12
|
||||
| `newGenerationDan` | PROVIDED | NORMAL_1, NORMAL_2, NORMAL_3, NORMAL_4, NORMAL_5, NORMAL_6, NORMAL_7, NORMAL_8, NORMAL_9, NORMAL_10, INSANE_0, INSANE_1, INSANE_2, INSANE_3, INSANE_4, INSANE_5, INSANE_6, INSANE_7, INSANE_8, INSANE_9, INSANE_10, INSANE_KAIDEN
|
||||
| `lnDan` | PROVIDED | DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, KAIDEN, OVERJOY, UDON
|
||||
| `scratchDan` | PROVIDED | KYU_7, KYU_6, KYU_5, KYU_4, KYU_3, KYU_2, KYU_1, DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, KAIDEN
|
||||
|
||||
## Versions
|
||||
|
||||
This game has no versions, and presumably doesn't need to disambiguate its IDs.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `bmsChartHash`
|
||||
- `tachiSongID`
|
||||
@@ -1,114 +0,0 @@
|
||||
# CHUNITHM Support
|
||||
|
||||
This game has the internal GPTString of `chunithm:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 1.01 million. |
|
||||
| `noteLamp` | "NONE", "FULL COMBO", "ALL JUSTICE", "ALL JUSTICE CRITICAL" | The type of combo this was. |
|
||||
| `clearLamp` | "FAILED", "CLEAR", "HARD", "BRAVE", "ABSOLUTE", "CATASTROPHY" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `scoreGraph` | Array<Decimal> | The history of the projected score, queried in one-second intervals. |
|
||||
| `lifeGraph` | Array<Decimal> | Challenge gauge history, queried in one-second intervals. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `jcrit`
|
||||
- `justice`
|
||||
- `attack`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rating` | The rating value of this score. This is identical to the system used in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 10 ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 50 ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BASIC`
|
||||
- `ADVANCED`
|
||||
- `EXPERT`
|
||||
- `MASTER`
|
||||
- `ULTIMA`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | BLUE, GREEN, ORANGE, RED, PURPLE, COPPER, SILVER, GOLD, PLATINUM, RAINBOW, RAINBOW_II, RAINBOW_III, RAINBOW_IV, RAINBOW_EX_I, RAINBOW_EX_II, RAINBOW_EX_III
|
||||
| `dan` | PROVIDED | DAN_I, DAN_II, DAN_III, DAN_IV, DAN_V, DAN_INFINITE
|
||||
| `emblem` | PROVIDED | DAN_I, DAN_II, DAN_III, DAN_IV, DAN_V, DAN_INFINITE
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `paradiselost` | PARADISE LOST |
|
||||
| `new` | NEW |
|
||||
| `newplus` | NEW PLUS |
|
||||
| `sun` | SUN |
|
||||
| `sun-intl` | SUN International |
|
||||
| `sun-omni` | SUN Omnimix |
|
||||
| `sunplus` | SUN PLUS |
|
||||
| `sunplus-intl` | SUN PLUS International |
|
||||
| `sunplus-omni` | SUN PLUS Omnimix |
|
||||
| `luminous` | LUMINOUS |
|
||||
| `luminous-intl` | LUMINOUS International |
|
||||
| `luminous-omni` | LUMINOUS Omnimix |
|
||||
| `luminousplus` | LUMINOUS PLUS |
|
||||
| `luminousplus-intl` | LUMINOUS PLUS International |
|
||||
| `luminousplus-omni` | LUMINOUS PLUS Omnimix |
|
||||
| `verse` | VERSE |
|
||||
| `verse-intl` | VERSE International |
|
||||
| `verse-omni` | VERSE Omnimix |
|
||||
| `xverse` | X-VERSE |
|
||||
| `xverse-intl` | X-VERSE International |
|
||||
| `xverse-omni` | X-VERSE Omnimix |
|
||||
| `xversex` | X-VERSE-X |
|
||||
| `xversex-intl` | X-VERSE-X International |
|
||||
| `xversex-omni` | X-VERSE-X Omnimix |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
@@ -1,96 +0,0 @@
|
||||
# DDR (DP) Support
|
||||
|
||||
This game has the internal GPTString of `ddr:DP`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 1 million. |
|
||||
| `lamp` | "FAILED", "ASSIST", "CLEAR", "FULL COMBO", "GREAT FULL COMBO", "PERFECT FULL COMBO", "MARVELOUS FULL COMBO", "LIFE4" | The type of clear this user got. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "E", "D", "D+", "C-", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "AA-", "AA", "AA+", "AAA" | The grade this score was. Note that grades are capped at F if this was a fail. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `flare` | "0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "EX" | The Flare rank. If no Flare is provided, Flare 0 is chosen by default. |
|
||||
| `exScore` | Integer | The EXScore value. Marvelous and O.K. judgements are worth 3 points, Perfect judgements are worth 2 points, Great judgements are worth 1 point, and Good and lower judgements are not worth any points. |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `MARVELOUS`
|
||||
- `PERFECT`
|
||||
- `GREAT`
|
||||
- `GOOD`
|
||||
- `MISS`
|
||||
- `OK`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Flare Skill as it's implemented in DDR World. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Average of your 10 best Flare Points this session |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Flare Skill as it's implemented in DDR World, taking 30 best flare points from 3 different categories: CLASSIC (DDR 1st~X3 vs 2ndMIX), WHITE (DDR(2013)~DDR A), GOLD (DDR A20~WORLD). |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BEGINNER`
|
||||
- `BASIC`
|
||||
- `DIFFICULT`
|
||||
- `EXPERT`
|
||||
- `CHALLENGE`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `flare` | DERIVED | NONE, NONE+, NONE++, NONE+++, MERCURY, MERCURY+, MERCURY++, MERCURY+++, VENUS, VENUS+, VENUS++, VENUS+++, EARTH, EARTH+, EARTH++, EARTH+++, MARS, MARS+, MARS++, MARS+++, JUPITER, JUPITER+, JUPITER++, JUPITER+++, SATURN, SATURN+, SATURN++, SATURN+++, URANUS, URANUS+, URANUS++, URANUS+++, NEPTUNE, NEPTUNE+, NEPTUNE++, NEPTUNE+++, SUN, SUN+, SUN++, SUN+++, WORLD
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `a` | A |
|
||||
| `a20` | A20 |
|
||||
| `a20plus` | A20+ |
|
||||
| `a3` | A3 |
|
||||
| `konaste` | Konaste |
|
||||
| `world` | World |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
- `ddrSongHash`
|
||||
@@ -1,96 +0,0 @@
|
||||
# DDR (SP) Support
|
||||
|
||||
This game has the internal GPTString of `ddr:SP`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 1 million. |
|
||||
| `lamp` | "FAILED", "ASSIST", "CLEAR", "FULL COMBO", "GREAT FULL COMBO", "PERFECT FULL COMBO", "MARVELOUS FULL COMBO", "LIFE4" | The type of clear this user got. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "E", "D", "D+", "C-", "C", "C+", "B-", "B", "B+", "A-", "A", "A+", "AA-", "AA", "AA+", "AAA" | The grade this score was. Note that grades are capped at F if this was a fail. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `flare` | "0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "EX" | The Flare rank. If no Flare is provided, Flare 0 is chosen by default. |
|
||||
| `exScore` | Integer | The EXScore value. Marvelous and O.K. judgements are worth 3 points, Perfect judgements are worth 2 points, Great judgements are worth 1 point, and Good and lower judgements are not worth any points. |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `MARVELOUS`
|
||||
- `PERFECT`
|
||||
- `GREAT`
|
||||
- `GOOD`
|
||||
- `MISS`
|
||||
- `OK`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Flare Skill as it's implemented in DDR World. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Average of your 10 best Flare Points this session |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `flareSkill` | Flare Skill as it's implemented in DDR World, taking 30 best flare points from 3 different categories: CLASSIC (DDR 1st~X3 vs 2ndMIX), WHITE (DDR(2013)~DDR A), GOLD (DDR A20~WORLD). |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BEGINNER`
|
||||
- `BASIC`
|
||||
- `DIFFICULT`
|
||||
- `EXPERT`
|
||||
- `CHALLENGE`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `flare` | DERIVED | NONE, NONE+, NONE++, NONE+++, MERCURY, MERCURY+, MERCURY++, MERCURY+++, VENUS, VENUS+, VENUS++, VENUS+++, EARTH, EARTH+, EARTH++, EARTH+++, MARS, MARS+, MARS++, MARS+++, JUPITER, JUPITER+, JUPITER++, JUPITER+++, SATURN, SATURN+, SATURN++, SATURN+++, URANUS, URANUS+, URANUS++, URANUS+++, NEPTUNE, NEPTUNE+, NEPTUNE++, NEPTUNE+++, SUN, SUN+, SUN++, SUN+++, WORLD
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `a` | A |
|
||||
| `a20` | A20 |
|
||||
| `a20plus` | A20+ |
|
||||
| `a3` | A3 |
|
||||
| `konaste` | Konaste |
|
||||
| `world` | World |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
- `ddrSongHash`
|
||||
@@ -1,91 +0,0 @@
|
||||
# GITADORA (Dora) Support
|
||||
|
||||
This game has the internal GPTString of `gitadora:Dora`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | The percent this score was worth. Sometimes referred to as 'Achievement Rate' in game. This is a value between 0 and 100. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "EXCELLENT" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "C", "B", "A", "S", "SS", "MAX" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `perfect`
|
||||
- `great`
|
||||
- `good`
|
||||
- `ok`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `skill` | Skill Rating as it's implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `skill` | The average of your best 10 skill ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveSkill` | Your best 50 skill levels added together, regardless of whether the chart is HOT or not. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BASIC`
|
||||
- `ADVANCED`
|
||||
- `EXTREME`
|
||||
- `MASTER`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | WHITE, ORANGE, ORANGE_GRD, YELLOW, YELLOW_GRD, GREEN, GREEN_GRD, BLUE, BLUE_GRD, PURPLE, PURPLE_GRD, RED, RED_GRD, BRONZE, SILVER, GOLD, RAINBOW
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `konaste` | Konaste |
|
||||
| `fuzzUp` | FUZZ-UP |
|
||||
| `highVoltage` | HIGH-VOLTAGE |
|
||||
| `nextage` | NEX+AGE |
|
||||
| `exchain` | EXCHAIN |
|
||||
| `matixx` | Matixx |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
@@ -1,95 +0,0 @@
|
||||
# GITADORA (Gita) Support
|
||||
|
||||
This game has the internal GPTString of `gitadora:Gita`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | The percent this score was worth. Sometimes referred to as 'Achievement Rate' in game. This is a value between 0 and 100. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "EXCELLENT" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "C", "B", "A", "S", "SS", "MAX" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `perfect`
|
||||
- `great`
|
||||
- `good`
|
||||
- `ok`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `skill` | Skill Rating as it's implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `skill` | The average of your best 10 skill ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveSkill` | Your best 50 skill levels added together, regardless of whether the chart is HOT or not. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BASIC`
|
||||
- `ADVANCED`
|
||||
- `EXTREME`
|
||||
- `MASTER`
|
||||
- `BASS BASIC`
|
||||
- `BASS ADVANCED`
|
||||
- `BASS EXTREME`
|
||||
- `BASS MASTER`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | WHITE, ORANGE, ORANGE_GRD, YELLOW, YELLOW_GRD, GREEN, GREEN_GRD, BLUE, BLUE_GRD, PURPLE, PURPLE_GRD, RED, RED_GRD, BRONZE, SILVER, GOLD, RAINBOW
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `konaste` | Konaste |
|
||||
| `fuzzUp` | FUZZ-UP |
|
||||
| `highVoltage` | HIGH-VOLTAGE |
|
||||
| `nextage` | NEX+AGE |
|
||||
| `exchain` | EXCHAIN |
|
||||
| `matixx` | Matixx |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
@@ -1,157 +0,0 @@
|
||||
# beatmania IIDX (DP) Support
|
||||
|
||||
This game has the internal GPTString of `iidx:DP`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in IIDX. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `comboBreak` | Integer | The amount of times combo was broken. |
|
||||
| `gaugeHistory` | Array<Decimal \| null > | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `scoreHistory` | Array<Decimal \| null > | A snapshot of the user's ghost throughout the chart. The values may be null. |
|
||||
| `gsmEasy` | Array<Decimal \| null > | If GSM is used, this stores the easy gauge history. |
|
||||
| `gsmNormal` | Array<Decimal \| null > | If GSM is used, this stores the normal gauge history. |
|
||||
| `gsmHard` | Array<Decimal \| null > | If GSM is used, this stores the hard gauge history. |
|
||||
| `gsmEXHard` | Array<Decimal \| null > | If GSM is used, this stores the ex-hard gauge history. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pgreat`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | A rating system that values your clear lamps on charts. Tierlist information is taken into account. |
|
||||
| `BPI` | A rating system for Kaiden level play. Only applies to 11s and 12s. A BPI of 0 states the score is equal to the Kaiden Average for that chart. A BPI of 100 is equal to the world record. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | An average of the best 10 ktLampRatings this session. |
|
||||
| `BPI` | An average of the best 10 BPIs this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | An average of your best 20 ktLampRatings. |
|
||||
| `BPI` | An average of your best 20 BPIs. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `NORMAL`
|
||||
- `HYPER`
|
||||
- `ANOTHER`
|
||||
- `LEGGENDARIA`
|
||||
- `All Scratch NORMAL`
|
||||
- `All Scratch HYPER`
|
||||
- `All Scratch ANOTHER`
|
||||
- `All Scratch LEGGENDARIA`
|
||||
- `Kichiku NORMAL`
|
||||
- `Kichiku HYPER`
|
||||
- `Kichiku ANOTHER`
|
||||
- `Kichiku LEGGENDARIA`
|
||||
- `Kiraku NORMAL`
|
||||
- `Kiraku HYPER`
|
||||
- `Kiraku ANOTHER`
|
||||
- `Kiraku LEGGENDARIA`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `dan` | PROVIDED | KYU_7, KYU_6, KYU_5, KYU_4, KYU_3, KYU_2, KYU_1, DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, CHUUDEN, KAIDEN
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `20` | tricoro |
|
||||
| `21` | SPADA |
|
||||
| `22` | PENDUAL |
|
||||
| `23` | copula |
|
||||
| `24` | SINOBUZ |
|
||||
| `25` | CANNON BALLERS |
|
||||
| `26` | ROOTAGE |
|
||||
| `27` | HEROIC VERSE |
|
||||
| `28` | BISTROVER |
|
||||
| `29` | CastHour |
|
||||
| `30` | Resident |
|
||||
| `31` | Epolis |
|
||||
| `32` | Pinky Crush |
|
||||
| `33` | Sparkle Shower |
|
||||
| `3-cs` | 3rd Style CS |
|
||||
| `4-cs` | 4th Style CS |
|
||||
| `5-cs` | 5th Style CS |
|
||||
| `6-cs` | 6th Style CS |
|
||||
| `7-cs` | 7th Style CS |
|
||||
| `8-cs` | 8th Style CS |
|
||||
| `9-cs` | 9th Style CS |
|
||||
| `10-cs` | 10th Style CS |
|
||||
| `11-cs` | IIDX RED CS |
|
||||
| `12-cs` | HAPPY SKY CS |
|
||||
| `13-cs` | DISTORTED CS |
|
||||
| `14-cs` | GOLD CS |
|
||||
| `15-cs` | DJ TROOPERS CS |
|
||||
| `16-cs` | EMPRESS CS |
|
||||
| `26-omni` | ROOTAGE Omnimix |
|
||||
| `27-omni` | HEROIC VERSE Omnimix |
|
||||
| `28-omni` | BISTROVER Omnimix |
|
||||
| `29-omni` | CastHour Omnimix |
|
||||
| `30-omni` | Resident Omnimix |
|
||||
| `31-omni` | Epolis Omnimix |
|
||||
| `32-omni` | Pinky Crush Omnimix |
|
||||
| `27-2dxtra` | HEROIC VERSE 2dxtra |
|
||||
| `28-2dxtra` | BISTROVER 2dxtra |
|
||||
| `30-2dxtra` | RESIDENT 2dxtra |
|
||||
| `31-2dxtra` | Epolis 2dxtra |
|
||||
| `bmus` | BEATMANIA US |
|
||||
| `inf` | INFINITAS |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `tachiSongID`
|
||||
- `songTitle`
|
||||
@@ -1,157 +0,0 @@
|
||||
# beatmania IIDX (SP) Support
|
||||
|
||||
This game has the internal GPTString of `iidx:SP`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in IIDX. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `comboBreak` | Integer | The amount of times combo was broken. |
|
||||
| `gaugeHistory` | Array<Decimal \| null > | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `scoreHistory` | Array<Decimal \| null > | A snapshot of the user's ghost throughout the chart. The values may be null. |
|
||||
| `gsmEasy` | Array<Decimal \| null > | If GSM is used, this stores the easy gauge history. |
|
||||
| `gsmNormal` | Array<Decimal \| null > | If GSM is used, this stores the normal gauge history. |
|
||||
| `gsmHard` | Array<Decimal \| null > | If GSM is used, this stores the hard gauge history. |
|
||||
| `gsmEXHard` | Array<Decimal \| null > | If GSM is used, this stores the ex-hard gauge history. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pgreat`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | A rating system that values your clear lamps on charts. Tierlist information is taken into account. |
|
||||
| `BPI` | A rating system for Kaiden level play. Only applies to 11s and 12s. A BPI of 0 states the score is equal to the Kaiden Average for that chart. A BPI of 100 is equal to the world record. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | An average of the best 10 ktLampRatings this session. |
|
||||
| `BPI` | An average of the best 10 BPIs this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
The default rating algorithm is `ktLampRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `ktLampRating` | An average of your best 20 ktLampRatings. |
|
||||
| `BPI` | An average of your best 20 BPIs. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `NORMAL`
|
||||
- `HYPER`
|
||||
- `ANOTHER`
|
||||
- `LEGGENDARIA`
|
||||
- `All Scratch NORMAL`
|
||||
- `All Scratch HYPER`
|
||||
- `All Scratch ANOTHER`
|
||||
- `All Scratch LEGGENDARIA`
|
||||
- `Kichiku NORMAL`
|
||||
- `Kichiku HYPER`
|
||||
- `Kichiku ANOTHER`
|
||||
- `Kichiku LEGGENDARIA`
|
||||
- `Kiraku NORMAL`
|
||||
- `Kiraku HYPER`
|
||||
- `Kiraku ANOTHER`
|
||||
- `Kiraku LEGGENDARIA`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `dan` | PROVIDED | KYU_7, KYU_6, KYU_5, KYU_4, KYU_3, KYU_2, KYU_1, DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, CHUUDEN, KAIDEN
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `20` | tricoro |
|
||||
| `21` | SPADA |
|
||||
| `22` | PENDUAL |
|
||||
| `23` | copula |
|
||||
| `24` | SINOBUZ |
|
||||
| `25` | CANNON BALLERS |
|
||||
| `26` | ROOTAGE |
|
||||
| `27` | HEROIC VERSE |
|
||||
| `28` | BISTROVER |
|
||||
| `29` | CastHour |
|
||||
| `30` | Resident |
|
||||
| `31` | Epolis |
|
||||
| `32` | Pinky Crush |
|
||||
| `33` | Sparkle Shower |
|
||||
| `3-cs` | 3rd Style CS |
|
||||
| `4-cs` | 4th Style CS |
|
||||
| `5-cs` | 5th Style CS |
|
||||
| `6-cs` | 6th Style CS |
|
||||
| `7-cs` | 7th Style CS |
|
||||
| `8-cs` | 8th Style CS |
|
||||
| `9-cs` | 9th Style CS |
|
||||
| `10-cs` | 10th Style CS |
|
||||
| `11-cs` | IIDX RED CS |
|
||||
| `12-cs` | HAPPY SKY CS |
|
||||
| `13-cs` | DISTORTED CS |
|
||||
| `14-cs` | GOLD CS |
|
||||
| `15-cs` | DJ TROOPERS CS |
|
||||
| `16-cs` | EMPRESS CS |
|
||||
| `26-omni` | ROOTAGE Omnimix |
|
||||
| `27-omni` | HEROIC VERSE Omnimix |
|
||||
| `28-omni` | BISTROVER Omnimix |
|
||||
| `29-omni` | CastHour Omnimix |
|
||||
| `30-omni` | Resident Omnimix |
|
||||
| `31-omni` | Epolis Omnimix |
|
||||
| `32-omni` | Pinky Crush Omnimix |
|
||||
| `27-2dxtra` | HEROIC VERSE 2dxtra |
|
||||
| `28-2dxtra` | BISTROVER 2dxtra |
|
||||
| `30-2dxtra` | RESIDENT 2dxtra |
|
||||
| `31-2dxtra` | Epolis 2dxtra |
|
||||
| `bmus` | BEATMANIA US |
|
||||
| `inf` | INFINITAS |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `tachiSongID`
|
||||
- `songTitle`
|
||||
@@ -1,86 +0,0 @@
|
||||
# ITG Support
|
||||
|
||||
This game has the internal GPTString of `itg:Stamina`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `scorePercent` | Decimal | The % value this score was worth. This is a number between 0 and 100. Note that negative %s, although existing in ITG, are not supported. |
|
||||
| `survivedPercent` | Decimal | How far this user survived through the chart. For clears, this should be 100, if the user got halfway through, this should be 50, etc. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "FULL EXCELLENT COMBO", "QUAD", "QUINT" | The type of clear this user got. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "D", "C", "B", "A", "S", "★", "★★", "★★★", "★★★★" | The grade this score was. Note that grades are capped at F if this was a fail. |
|
||||
| `finalPercent` | Decimal | A combination of `survivedPercent` and `scorePercent`. This metric is `survivedPercent` if the player didn't clear the chart. Otherwise, it's their `scorePercent` + 100. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `lifebarHistory` | Array<Decimal> | A snapshot of how much life the player had throughout the chart. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `fantastic+`
|
||||
- `fantastic`
|
||||
- `excellent`
|
||||
- `great`
|
||||
- `decent`
|
||||
- `wayoff`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
The default rating algorithm is `blockRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `blockRating` | How much this clear is worth. |
|
||||
| `fastest32` | The fastest BPM this score streamed 32 measures straight for. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `blockRating` | An average of your best 5 block levels cleared this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
The default rating algorithm is `highestBlock`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `highestBlock` | The highest block level this player has cleared. |
|
||||
| `fastest32` | The fastest BPM this user has streamed 32 unbroken measures at. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
This game uses dynamic difficulties. A difficulty name may be any string, provided `songID` + `playtype` + `difficulty` is unique.
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
|
||||
## Versions
|
||||
|
||||
This game has no versions, and presumably doesn't need to disambiguate its IDs.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `itgChartHash`
|
||||
@@ -1,103 +0,0 @@
|
||||
# jubeat Support
|
||||
|
||||
This game has the internal GPTString of `jubeat:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 1 million. |
|
||||
| `musicRate` | Decimal | The music rate for this score. This is between 0 and 100 on normal difficulties, and 0 and 120 for hard-mode difficulties. This should be submitted to one decimal place. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "EXCELLENT" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "E", "D", "C", "B", "A", "S", "SS", "SSS", "EXC" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `musicBar` | Array<Decimal> | A snapshot of how well the player did in each part of the song |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `perfect`
|
||||
- `great`
|
||||
- `good`
|
||||
- `poor`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `jubility` | Jubility as it's implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `jubility` | The average of your best 10 jubilities this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
The default rating algorithm is `jubility`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `jubility` | Your profile jubility. This takes your best 30 scores on PICK UP songs, and your best 30 elsewhere. |
|
||||
| `naiveJubility` | A naive version of jubility which just adds together your best 60 scores. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BSC`
|
||||
- `ADV`
|
||||
- `EXT`
|
||||
- `HARD BSC`
|
||||
- `HARD ADV`
|
||||
- `HARD EXT`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | BLACK, YELLOW_GREEN, GREEN, LIGHT_BLUE, BLUE, VIOLET, PURPLE, PINK, ORANGE, GOLD
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `jubeat` | jubeat |
|
||||
| `ripples` | ripples |
|
||||
| `knit` | knit |
|
||||
| `copious` | copious |
|
||||
| `saucer` | saucer |
|
||||
| `prop` | prop |
|
||||
| `qubell` | Qubell |
|
||||
| `clan` | clan |
|
||||
| `festo` | festo |
|
||||
| `festo-omni` | festo Omnimix |
|
||||
| `ave` | Ave. |
|
||||
| `beyond` | beyond the Ave. |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `tachiSongID`
|
||||
@@ -1,89 +0,0 @@
|
||||
# maimai Support
|
||||
|
||||
This game has the internal GPTString of `maimai:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | The percent this score was worth. Sometimes called 'rate' in game. This is upper-bounded by how many BREAK notes the chart has. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "ALL PERFECT", "ALL PERFECT+" | The type of clear this score was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `perfect`
|
||||
- `great`
|
||||
- `good`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rate` | Rating as it's implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rate` | The average of your best 10 ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRate` | An average of your 30 best ratings. This is different from the rating in-game, as that is song-based and takes into account recent scores. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `Easy`
|
||||
- `Basic`
|
||||
- `Advanced`
|
||||
- `Expert`
|
||||
- `Master`
|
||||
- `Re:Master`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | WHITE, BLUE, GREEN, YELLOW, RED, PURPLE, BRONZE, SILVER, GOLD, RAINBOW
|
||||
| `dan` | PROVIDED | DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, KAIDEN, SHINDAN_1, SHINDAN_2, SHINDAN_3, SHINDAN_4, SHINDAN_5, SHINDAN_6, SHINDAN_7, SHINDAN_8, SHINDAN_9, SHINDAN_10, SHINKAIDEN
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `finale` | FiNALE |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
- `inGameID`
|
||||
- `inGameStrID`
|
||||
@@ -1,107 +0,0 @@
|
||||
# maimai DX Support
|
||||
|
||||
This game has the internal GPTString of `maimaidx:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `percent` | Decimal | The percent this score was worth. Sometimes called 'rate' in game. This is between 0 and 101. |
|
||||
| `lamp` | "FAILED", "CLEAR", "FULL COMBO", "FULL COMBO+", "ALL PERFECT", "ALL PERFECT+" | The type of clear this score was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "S+", "SS", "SS+", "SSS", "SSS+" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `percentGraph` | Array<Decimal \| null > | The history of the projected achievement, queried in one-second intervals. |
|
||||
| `lifeGraph` | Array<Decimal \| null > | Life count history, queried in one-second intervals. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `pcrit`
|
||||
- `perfect`
|
||||
- `great`
|
||||
- `good`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rate` | Rating as it's implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rate` | The average of your best 10 ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRate` | A naive rating algorithm that just sums your 50 best scores. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `Basic`
|
||||
- `Advanced`
|
||||
- `Expert`
|
||||
- `Master`
|
||||
- `Re:Master`
|
||||
- `DX Basic`
|
||||
- `DX Advanced`
|
||||
- `DX Expert`
|
||||
- `DX Master`
|
||||
- `DX Re:Master`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | WHITE, BLUE, GREEN, YELLOW, RED, PURPLE, BRONZE, SILVER, GOLD, PLATINUM, RAINBOW
|
||||
| `dan` | PROVIDED | DAN_1, DAN_2, DAN_3, DAN_4, DAN_5, DAN_6, DAN_7, DAN_8, DAN_9, DAN_10, SHINDAN_1, SHINDAN_2, SHINDAN_3, SHINDAN_4, SHINDAN_5, SHINDAN_6, SHINDAN_7, SHINDAN_8, SHINDAN_9, SHINDAN_10, SHINKAIDEN, URAKAIDEN
|
||||
| `matchingClass` | PROVIDED | B5, B4, B3, B2, B1, A5, A5, A4, A3, A2, A1, S5, S4, S3, S2, S1, SS5, SS4, SS3, SS2, SS1, SSS5, SSS4, SSS3, SSS2, SSS1, LEGEND
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `universeplus` | UNiVERSE PLUS |
|
||||
| `festival` | FESTiVAL |
|
||||
| `festivalplus` | FESTiVAL PLUS |
|
||||
| `buddies` | BUDDiES |
|
||||
| `buddies-omni` | BUDDiES Omnimix |
|
||||
| `buddiesplus` | BUDDiES PLUS |
|
||||
| `buddiesplus-omni` | BUDDiES PLUS Omnimix |
|
||||
| `prism` | PRiSM |
|
||||
| `prism-omni` | PRiSM Omnimix |
|
||||
| `prismplus` | PRiSM PLUS |
|
||||
| `prismplus-omni` | PRiSM PLUS Omnimix |
|
||||
| `circle` | CiRCLE |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
- `inGameID`
|
||||
@@ -1,83 +0,0 @@
|
||||
# MÚSECA Support
|
||||
|
||||
This game has the internal GPTString of `museca:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. This is between 0 and 1 million. |
|
||||
| `lamp` | "FAILED", "CLEAR", "CONNECT ALL", "PERFECT CONNECT ALL" | The type of clear this score was. **Note:** we define a CLEAR as being >= 800k, and FAILED as anything less. We do not respect MUSECA's story mode for clear/failed types. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "没", "拙", "凡", "佳", "良", "優", "秀", "傑", "傑G" | The grade this score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `critical`
|
||||
- `near`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `curatorSkill` | Curator Skill as it's implemented in-game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `curatorSkill` | The average of your best 10 Curator skills this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `curatorSkill` | The sum of your best 20 Curator Skills. This is identical to how it's calculated in-game. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `Green`
|
||||
- `Yellow`
|
||||
- `Red`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `1.5` | 1 + 1/2 |
|
||||
| `1.5-b` | 1 + 1/2 Rev. B |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `songTitle`
|
||||
- `tachiSongID`
|
||||
- `inGameID`
|
||||
@@ -1,134 +0,0 @@
|
||||
# O.N.G.E.K.I. Support
|
||||
|
||||
This game has the internal GPTString of `ongeki:Single`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | Known in-game as 'Technical Score'. It ranges between 0 and 1,010,000, where notes are worth 950,000, and bells 60,000. |
|
||||
| `noteLamp` | "LOSS", "CLEAR", "FULL COMBO", "ALL BREAK", "ALL BREAK+" | The primary lamp. A clear is a draw or a win in-game. |
|
||||
| `bellLamp` | "NONE", "FULL BELL" | Tracks whether all bells in the chart have been collected. |
|
||||
| `platinumScore` | Integer | The Platinum Score value. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "D", "C", "B", "BB", "BBB", "A", "AA", "AAA", "S", "SS", "SSS", "SSS+" | The grade this score was. |
|
||||
| `platinumStars` | The number of platinum stars of this score |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The number of non-critical mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The number of non-critical mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `damage` | Integer | The number of damage ticks received. |
|
||||
| `bellCount` | Integer | The number of bells collected. |
|
||||
| `totalBellCount` | Integer | The maximum number of bells that could have been obtained at the time of the play's end. |
|
||||
| `scoreGraph` | Array<Decimal \| null > | The history of the projected score, queried in one-second intervals. |
|
||||
| `platinumGraph` | Array<Decimal \| null > | The Platinum Score history, queried in one-second intervals. |
|
||||
| `bellGraph` | Array<Decimal \| null > | The history of the number of bells missed, queried in one-second intervals. |
|
||||
| `lifeGraph` | Array<Decimal \| null > | The life gauge history, queried in one-second intervals. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `cbreak` (critical break)
|
||||
- `break` (red/regular break)
|
||||
- `hit`
|
||||
- `miss`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
The default rating algorithm is `rating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `rating` | A rating value of this score, capping at +2.0 at SSS+. This is identical to the system used in bright MEMORY and earlier versions. |
|
||||
| `scoreRating` | A rating value of this score, capping at +2.7 at 1,010,000. This is identical to the system used in Re:Fresh. |
|
||||
| `starRating` | A rating value of this score, based on stars derived from Platinum Score. This is identical to the system used in Re:Fresh. |
|
||||
### Session Rating Algorithms
|
||||
|
||||
The default rating algorithm is `naiveRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 10 classic ratings this session. |
|
||||
| `naiveScoreRating` | The average of your best 10 score ratings this session. |
|
||||
| `starRating` | The average of your best 10 star ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
The default rating algorithm is `naiveRating`.
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveRating` | The average of your best 45 classic ratings. This is a simpler variant of the rating algorithm used in bright MEMORY and earlier versions, without distinguishing between new and old charts, and without taking recent scores into account. |
|
||||
| `naiveRatingRefresh` | A weighted sum of the average of your best 60 score ratings, and your best 50 star ratings. This is a simpler variant of the rating algorithm used in Re:Fresh, without distinguishing between new and old charts. |
|
||||
| `scoreRating` | The average of your best 60 score ratings. |
|
||||
| `starRating` | The average of your best 50 star ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `BASIC`
|
||||
- `ADVANCED`
|
||||
- `EXPERT`
|
||||
- `MASTER`
|
||||
- `LUNATIC`
|
||||
- `Re:MASTER`
|
||||
|
||||
The `LUNATIC`/`Re:MASTER` split is transparent; your import script must assume every "white" chart is `LUNATIC`.
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `colour` | DERIVED | BLUE, GREEN, ORANGE, RED, PURPLE, COPPER, SILVER, GOLD, PLATINUM, RAINBOW, RAINBOW_SHINY, RAINBOW_EX, RAINBOW_EX_TRUE
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `brightMemory2Omni` | bright MEMORY Act.II Omnimix |
|
||||
| `brightMemory3` | bright MEMORY Act.III |
|
||||
| `brightMemory3Omni` | bright MEMORY Act.III Omnimix |
|
||||
| `refresh` | Re:Fresh |
|
||||
| `refreshOmni` | Re:Fresh Omnimix |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `songTitle`
|
||||
- `tachiSongID` – deprecated; don't use this
|
||||
- `inGameID`
|
||||
|
||||
### Song Title Matching
|
||||
There are several songs with non-unique names:
|
||||
|
||||
- Singularity (technoplanet) `inGameID: 391`
|
||||
- Singularity (ETIA.「Arcaea」) `inGameID: 454`
|
||||
- Singularity (SEGA SOUND STAFF「セガNET麻雀 MJ」) `inGameID: 516`
|
||||
|
||||
- Hand in Hand (livetune) `inGameID: 380`
|
||||
- Hand in Hand (ユーフィリア(CV:高橋 李依)「アンジュ・ヴィエルジュ」) `inGameID: 212`
|
||||
|
||||
- Perfect_Shining!! LUNATIC 0 (Loctest chart) `inGameID: 8003`
|
||||
- Perfect_Shining!! LUNATIC 13+ (Re:Master) `inGameID: 8091`
|
||||
|
||||
Most of these conflicts can be resolved by providing the `artist` field; the only exception is Perfect Shining, which uniquely has two LUNATIC charts and has to be matched by `inGameID`.
|
||||
There are numerous songs with non-unique names (e.g. Singularity, Singularity, Singularity and Xingularity), but this can be resolved by providing the `artist` field. The only exception is Perfect Shining!!, which uniquely has two LUNATIC charts and has to be matched by `inGameID`:
|
||||
|
||||
- LUNATIC 0 (Loctest chart) `inGameID: 8003`
|
||||
- Re:MASTER 13+ `inGameID: 8091`
|
||||
@@ -1,97 +0,0 @@
|
||||
# PMS (Controller) Support
|
||||
|
||||
This game has the internal GPTString of `pms:Controller`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in IIDX. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `gaugeHistory` | Array<Decimal> | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `gaugeHistoryEasy` | Array<Decimal> | The easy gauge history. |
|
||||
| `gaugeHistoryGroove` | Array<Decimal> | The groove gauge history. |
|
||||
| `gaugeHistoryHard` | Array<Decimal> | The hard gauge history. |
|
||||
| `epg` | Integer | The amount of early PGreats in this score. |
|
||||
| `egr` | Integer | The amount of early greats in this score. |
|
||||
| `egd` | Integer | The amount of early goods in this score. |
|
||||
| `ebd` | Integer | The amount of early bads in this score. |
|
||||
| `epr` | Integer | The amount of early poors in this score. |
|
||||
| `lpg` | Integer | The amount of late PGreats in this score. |
|
||||
| `lgr` | Integer | The amount of late greats in this score. |
|
||||
| `lgd` | Integer | The amount of late goods in this score. |
|
||||
| `lbd` | Integer | The amount of late bads in this score. |
|
||||
| `lpr` | Integer | The amount of late poors in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `cool`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 10 sieglinde ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 20 sieglinde ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `CHART`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `dan` | PROVIDED | INSANE_1, INSANE_2, INSANE_3, INSANE_4, INSANE_5, INSANE_6, INSANE_7, INSANE_8, INSANE_9, INSANE_10, INSANE_KAIDEN, OVERJOY, UNDEFINED
|
||||
|
||||
## Versions
|
||||
|
||||
This game has no versions, and presumably doesn't need to disambiguate its IDs.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `bmsChartHash`
|
||||
- `tachiSongID`
|
||||
@@ -1,97 +0,0 @@
|
||||
# PMS (Keyboard) Support
|
||||
|
||||
This game has the internal GPTString of `pms:Keyboard`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | EX Score. This should be between 0 and the maximum possible EX on this chart. |
|
||||
| `lamp` | "NO PLAY", "FAILED", "ASSIST CLEAR", "EASY CLEAR", "CLEAR", "HARD CLEAR", "EX HARD CLEAR", "FULL COMBO" | The type of clear this was. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `grade` | "F", "E", "D", "C", "B", "A", "AA", "AAA", "MAX-", "MAX" | Grades as they are in IIDX. We also add MAX- (94.44...%) and MAX (100%) as their own grades for convenience. |
|
||||
| `percent` | Decimal | EX Score divided by the maximum possible EX Score on this chart. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `bp` | Integer | The total bads + poors in this score. |
|
||||
| `gauge` | Decimal | The life in percent (between 0 and 100) that was on the gauge at the end of the chart. |
|
||||
| `gaugeHistory` | Array<Decimal> | A snapshot of the gauge percent throughout the chart. The values should be null from the point the user dies until the end of the chart. |
|
||||
| `gaugeHistoryEasy` | Array<Decimal> | The easy gauge history. |
|
||||
| `gaugeHistoryGroove` | Array<Decimal> | The groove gauge history. |
|
||||
| `gaugeHistoryHard` | Array<Decimal> | The hard gauge history. |
|
||||
| `epg` | Integer | The amount of early PGreats in this score. |
|
||||
| `egr` | Integer | The amount of early greats in this score. |
|
||||
| `egd` | Integer | The amount of early goods in this score. |
|
||||
| `ebd` | Integer | The amount of early bads in this score. |
|
||||
| `epr` | Integer | The amount of early poors in this score. |
|
||||
| `lpg` | Integer | The amount of late PGreats in this score. |
|
||||
| `lgr` | Integer | The amount of late greats in this score. |
|
||||
| `lgd` | Integer | The amount of late goods in this score. |
|
||||
| `lbd` | Integer | The amount of late bads in this score. |
|
||||
| `lpr` | Integer | The amount of late poors in this score. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `cool`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
- `poor`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | A clearing algorithm that rewards you points based on how difficult an easy clear or hard clear was. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 10 sieglinde ratings this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `sieglinde` | The average of your best 20 sieglinde ratings. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `CHART`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `dan` | PROVIDED | INSANE_1, INSANE_2, INSANE_3, INSANE_4, INSANE_5, INSANE_6, INSANE_7, INSANE_8, INSANE_9, INSANE_10, INSANE_KAIDEN, OVERJOY, UNDEFINED
|
||||
|
||||
## Versions
|
||||
|
||||
This game has no versions, and presumably doesn't need to disambiguate its IDs.
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `bmsChartHash`
|
||||
- `tachiSongID`
|
||||
@@ -1,89 +0,0 @@
|
||||
# pop'n music Support
|
||||
|
||||
This game has the internal GPTString of `popn:9B`.
|
||||
|
||||
!!! note
|
||||
For information on what each section means, please see [Common Config](../common-config/index.md).
|
||||
|
||||
## Metrics
|
||||
|
||||
For more information on what metrics are and how they work, see [TODO]!
|
||||
|
||||
### Provided Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `score` | Integer | The score value. |
|
||||
| `clearMedal` | "failedCircle", "failedDiamond", "failedStar", "easyClear", "clearCircle", "clearDiamond", "clearStar", "fullComboCircle", "fullComboDiamond", "fullComboStar", "perfect" | The clear medal for this score. This is a superset of lamps. |
|
||||
|
||||
### Derived Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `lamp` | "FAILED", "EASY CLEAR", "CLEAR", "FULL COMBO", "PERFECT" | The lamp for this score. This is a subset of clearMedals, and is - as a result - derived from them. We keep both around as lamps are better for grouping up scores. |
|
||||
| `grade` | "E", "D", "C", "B", "A", "AA", "AAA", "S" | The grade this score was worth. Note that scores are capped at a grade of A if they are a fail, regardless of how good the score was. |
|
||||
|
||||
### Optional Metrics
|
||||
|
||||
| Metric Name | Type | Description |
|
||||
| :: | :: | :: |
|
||||
| `fast` | Integer | The amount of mistakes in this score that were a result of hitting early. |
|
||||
| `slow` | Integer | The amount of mistakes in this score that were a result of hitting late. |
|
||||
| `maxCombo` | Integer | The largest combo in this score. |
|
||||
| `gauge` | Decimal | The gauge value this score had at the end. This is a value between 0 and 100. |
|
||||
|
||||
## Judgements
|
||||
|
||||
The following judgements are defined:
|
||||
|
||||
- `cool`
|
||||
- `great`
|
||||
- `good`
|
||||
- `bad`
|
||||
|
||||
## Rating Algorithms
|
||||
|
||||
### Score Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `classPoints` | Class Points as they're implemented in game. |
|
||||
|
||||
### Session Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `classPoints` | The average of your best 10 class points this session. |
|
||||
|
||||
### Profile Rating Algorithms
|
||||
|
||||
| Name | Description |
|
||||
| :: | :: |
|
||||
| `naiveClassPoints` | A naive average of your best 20 scores. This is different to in game class points, as that is affected by recent scores, and not just your best scores. |
|
||||
|
||||
## Difficulties
|
||||
|
||||
- `Easy`
|
||||
- `Normal`
|
||||
- `Hyper`
|
||||
- `EX`
|
||||
|
||||
## Classes
|
||||
|
||||
| Name | Type | Values |
|
||||
| :: | :: | :: |
|
||||
| `class` | DERIVED | KITTY, STUDENT, DELINQUENT, DETECTIVE, IDOL, GENERAL, HERMIT, GOD
|
||||
|
||||
## Versions
|
||||
|
||||
| ID | Pretty Name |
|
||||
| :: | :: |
|
||||
| `peace` | peace |
|
||||
| `kaimei` | Kaimei Riddles |
|
||||
| `unilab` | Unilab |
|
||||
|
||||
## Supported Match Types
|
||||
|
||||
- `inGameID`
|
||||
- `tachiSongID`
|
||||
- `popnChartHash`
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user