diff --git a/.gitignore b/.gitignore index b639849..90cdd74 100644 --- a/.gitignore +++ b/.gitignore @@ -79,6 +79,6 @@ typings/ bun.lockb assets/ -modules/config.js +core/config.js types.js db-access.js \ No newline at end of file diff --git a/@types/classes/character.d.ts b/@types/classes/character.d.ts deleted file mode 100644 index 78e9791..0000000 --- a/@types/classes/character.d.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { BlueArchiveTemplate } from "./template"; - -export declare class Character extends BlueArchiveTemplate { - static data: Map; - - id: number; - localizeEtcId: string; - name: string; - released: boolean; - playable: boolean; - baseStar: number; - rarity: number; - armorType: string; - bulletType: string; - position: string; - role: string; - squadType: string; - weaponType: string; - club: string; - school: string; - imageIdentifier: string; - equipmentType: string; - tags: string[]; - region: string; - - constructor (data: { - id: number, - localizeEtcId: string, - name: string, - released: boolean, - playable: boolean, - baseStar: number, - rarity: number, - armorType: string, - bulletType: string, - position: string, - role: string, - squadType: string, - weaponType: string, - club: string, - school: string, - imageIdentifier: string, - equipmentType: string, - tags: string[], - region: string - }); - - /** - * Fixes the given armor type by converting it to lowercase and replacing spaces with underscores. - * @param armorType The armor type to fix. - * @returns The fixed armor type. - */ - private fixArmorType (armorType: string): string; - - /** - * Fixes the given role type by converting it to lowercase and replacing spaces with underscores. - * @param roleType The role type to fix. - * @returns The fixed role type. - */ - private fixRoleType (roleType: string): string; - - /** - * Returns the character data that matches the given identifier. - * @param identifier The identifier of the character data to retrieve. - * @returns The character data that matches the given identifier. - */ - static get (identifier: Character | number): Character | null; - - /** - * Loads the character data from the database. - * @returns A promise that resolves when the character data has been loaded. - */ - static loadData (): Promise; - - /** - * Clears the character data. - */ - static destroy (): void; -} diff --git a/@types/classes/drop.d.ts b/@types/classes/drop.d.ts deleted file mode 100644 index 62296dd..0000000 --- a/@types/classes/drop.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { BlueArchiveTemplate } from "./template"; - -export declare class Drops extends BlueArchiveTemplate { - static dataGlobal: Map; - static dataJapan: Map; - - id: number; - tag: string; - stageRewardId: number; - dropAmount: number; - dropChance: number; - - constructor (data: { - id: number, - tag: string, - stageRewardId: number, - dropAmount: number, - dropChance: number - }); - - /** - * Returns the drop data that matches the identifier and region. - * @param identifier The identifier of the drop data to retrieve. - * @param region The region to retrieve the drop data from. - * @returns The drop data that matches the identifier and region. - */ - static get (identifier: Drops | number, region: "global" | "japan"): Drops[] | Drops; - - /** - * Returns the drop data that matches the identifier. - * @param identifier The identifier of the drop data to retrieve. - * @returns The drop data that matches the identifier. - */ - static getDropbyStage (identifier: Drops | number): Drops[] | Drops; - - /** - * Loads the drop data from the database for both the global and Japan regions. - * @returns A promise that resolves when the drop data has been loaded. - */ - static loadData (): Promise; - - /** - * Clears the drop data. - */ - static destroy (): void; -} \ No newline at end of file diff --git a/@types/classes/equipment.d.ts b/@types/classes/equipment.d.ts deleted file mode 100644 index 4317b9a..0000000 --- a/@types/classes/equipment.d.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { BlueArchiveTemplate } from "./template"; - -export declare class Equipment extends BlueArchiveTemplate { - static dataGlobal: Map; - static dataJapan: Map; - - id: number; - localizeId: string; - recipeId: number; - category: string; - rarity: number; - maxLevel: number; - tier: number; - tags: string[]; - - constructor (data: { - id: number, - localizeId: string, - recipeId: number, - category: string, - rarity: number, - maxLevel: number, - tier: number, - tags: string[] - }); - - /** - * Returns the equipment data that matches the identifier and region. - * @param identifier The identifier of the equipment data to retrieve. - * @param region The region to retrieve the equipment data from. - * @returns The equipment data that matches the identifier and region. - */ - static get (identifier: Equipment | number, region: "global" | "japan"): Equipment | null; - - /** - * Returns the equipment data that matches the identifier. - * @param identifier The identifier of the equipment data to retrieve. - * @returns The equipment data that matches the identifier. - */ - static getDatabyTier (identifier: Equipment | number): Equipment[]; - - /** - * Loads the equipment data from the database for both the global and Japan regions. - * @returns A promise that resolves when the equipment data has been loaded. - */ - static loadData (): Promise; - - /** - * Clears the equipment data. - */ - static destroy (): void; -} \ No newline at end of file diff --git a/@types/classes/skill.d.ts b/@types/classes/skill.d.ts deleted file mode 100644 index b45cf5a..0000000 --- a/@types/classes/skill.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { BlueArchiveTemplate } from "./template"; - -export declare class Skill extends BlueArchiveTemplate { - static dataGlobal: Map; - static dataJapan: Map; - - id: number; - skillEx: string; - normal: string; - passive: string; - sub: string; - - constructor (data: { - id: number, - skillEx: string, - normal: string, - passive: string, - sub: string - }); - - /** - * Returns the skill data that matches the identifier and region. - * @param identifier The identifier of the skill data to retrieve. - * @param region The region to retrieve the skill data from. - * @returns The skill data that matches the identifier and region. - */ - static get (identifier: Skill | number, region: "global" | "japan"): Skill | null; - - /** - * Loads the skill data from the database for both the global and Japan regions. - * @returns A promise that resolves when the skill data has been loaded. - */ - static loadData (): Promise; - - /** - * Clears the skill data. - */ - static destroy (): void; - - /** - * Normalizes the given name by converting it to lowercase. - * @param name The name to normalize. - * @returns The normalized name. - */ - static normalizeName (name: string): string; -} \ No newline at end of file diff --git a/@types/classes/stage.d.ts b/@types/classes/stage.d.ts deleted file mode 100644 index 6890156..0000000 --- a/@types/classes/stage.d.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { BlueArchiveTemplate } from "./template"; - -declare type ObjectiveType = { - type: string; - value: number; -}; - -declare type StageInfoType = { - title: string; - description: string; -}; - -declare type StageDataType = { - chara: number; - enemy: number[]; - count: number[]; - wave: number; - boss: number; -}; - -declare type RaidType = { - seasonId: number; - bossName: string; - startAt: Date; - settleAt: Date; - endAt: Date; -}; - -declare type StageDataTypeMain = { - id: number; - minRank: number; - staminaCost: number; - battleDuration: number; - maxTurn: number; - stageInfo: StageInfoType; - objective?: ObjectiveType[]; - stageData?: StageDataType; -}; - -declare type StageDataMapType = Map; - -export declare class Stage extends BlueArchiveTemplate { - static dataGlobal: StageDataMapType; - static dataJapan: StageDataMapType; - - id: number; - minRank: number; - staminaCost: number; - battleDuration: number; - maxTurn: number; - stageInfo: StageInfoType; - objective: ObjectiveType[]; - stageData: StageDataType; - - constructor (data: StageDataTypeMain); - - static get (identifier: number | Stage, region: string): Stage | undefined; - - static getStagebyId (identifier: number | Stage): Stage | undefined; - - static raid (region: string): Promise<{ current: RaidType[]; upcoming: RaidType[]; ended: RaidType[] }>; - - static loadData (): Promise; - - static destroy (): void; - - static normalizeName (name: string): string; -} diff --git a/@types/classes/template.d.ts b/@types/classes/template.d.ts deleted file mode 100644 index a07998c..0000000 --- a/@types/classes/template.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -export declare class BlueArchiveTemplate { - static data: Map; - static dataGlobal: Map; - static dataJapan: Map; - - static initialize (): Promise; - static loadData (): Promise; - - /** - * Cleans up module - * @abstract - */ - destroy (): void; -} diff --git a/@types/index.d.ts b/@types/index.d.ts deleted file mode 100644 index 0ab2c5a..0000000 --- a/@types/index.d.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Character } from "./classes/character"; -import { Drops } from "./classes/drop"; -import { Equipment } from "./classes/equipment"; -import { Skill } from "./classes/skill"; - -import { QuerySingleton } from "./singletons/query"; -import { Utils } from "./singletons/utils"; - -export declare type GlobalBaObject = { - Character: typeof Character; - Drops: typeof Drops; - Equipment: typeof Equipment; - Skill: typeof Skill; - - Query: InstanceType; - Utils: InstanceType; -}; - -declare type ModuleFilePath = "classes/character" - | "classes/drop" - | "classes/equipment" - | "classes/skill" - | "singletons/query" - | "singletons/utils"; - -declare type OptionsObject = { - whitelist?: ModuleFilePath[]; - skip?: ModuleFilePath[]; -}; - -export declare function initialize (options?: OptionsObject): Promise; diff --git a/@types/singletons/query.d.ts b/@types/singletons/query.d.ts deleted file mode 100644 index 338a769..0000000 --- a/@types/singletons/query.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { MongoClient } from "mongodb"; -import { SingletonTemplate } from "./template"; - -declare type QuerySingletonConstructor = { - singleton (): QuerySingleton; - module: QuerySingleton | null; -}; - -export declare class QuerySingleton extends SingletonTemplate { - #pool: MongoClient | null; - - constructor (); - - connect (): Promise; - - initListeners (): void; - - destroy (): void; - - get client (): MongoClient; - get modulePath (): string; -} diff --git a/@types/singletons/template.d.ts b/@types/singletons/template.d.ts deleted file mode 100644 index bd757bd..0000000 --- a/@types/singletons/template.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export declare abstract class SingletonTemplate { - static module: SingletonTemplate; - static singleton (): void | Promise; - - abstract destroy (): void; - abstract get modulePath (): string; -} diff --git a/@types/singletons/utils.d.ts b/@types/singletons/utils.d.ts deleted file mode 100644 index eaa815c..0000000 --- a/@types/singletons/utils.d.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SingletonTemplate } from "./template"; - -declare type TerrainType = { - SS: { - DamageDealt: string; - ShieldBlockRate: string; - }; - S: { - DamageDealt: string; - ShieldBlockRate: string; - }; - A: { - DamageDealt: string; - ShieldBlockRate: string; - }; - B: { - DamageDealt: string; - ShieldBlockRate: string; - }; - C: { - DamageDealt: string; - ShieldBlockRate: string; - }; - D: { - DamageDealt: string; - ShieldBlockRate: string; - }; -}; - -declare type Equipment = { - id: string; - name: string; - description: string; -}; - -declare type CharacterData = { - info: any; - stat: any; - topology: { - urban: TerrainType; - outdoor: TerrainType; - indoor: TerrainType; - }; -}; - -declare type SkillInfo = { - skillDesc: string; - effectDesc: string; - animation: string; -}; - -export declare class Utils implements SingletonTemplate { - static terrainTypes: { - Urban: Record; - Desert: Record; - Indoor: Record; - }; - - static singleton (): Utils; - - isValidRegion (region: "global" | "japan"): boolean; - - getEquipmentData (id: string): Promise; - - getCharacterName (id: string, region: string): Promise; - - getCharacterData (id: string, region: string): Promise; - - getSkillInfo (id: string, region: string): Promise; - - destroy (): void; - - get modulePath (): string; -} diff --git a/LICENSE b/LICENSE index e8efbde..29ebfa5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,172 +1,661 @@ -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of -authorship (the "Original Work") whose owner (the "Licensor") has placed the -following licensing notice adjacent to the copyright notice for the Original -Work: - -Licensed under the Open Software License version 3.0 - -1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, -non-exclusive, sublicensable license, for the duration of the copyright, to do -the following: - - a) to reproduce the Original Work in copies, either alone or as part of a - collective work; - - b) to translate, adapt, alter, transform, modify, or arrange the Original - Work, thereby creating derivative works ("Derivative Works") based upon the - Original Work; - - c) to distribute or communicate copies of the Original Work and Derivative - Works to the public, with the proviso that copies of Original Work or - Derivative Works that You distribute or communicate shall be licensed under - this Open Software License; - - d) to perform the Original Work publicly; and - - e) to display the Original Work publicly. - -2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, -non-exclusive, sublicensable license, under patent claims owned or controlled -by the Licensor that are embodied in the Original Work as furnished by the -Licensor, for the duration of the patents, to make, use, sell, offer for sale, -have made, and import the Original Work and Derivative Works. - -3) Grant of Source Code License. The term "Source Code" means the preferred -form of the Original Work for making modifications to it and all available -documentation describing how to modify the Original Work. Licensor agrees to -provide a machine-readable copy of the Source Code of the Original Work along -with each copy of the Original Work that Licensor distributes. Licensor -reserves the right to satisfy this obligation by placing a machine-readable -copy of the Source Code in an information repository reasonably calculated to -permit inexpensive and convenient access by You for as long as Licensor -continues to distribute the Original Work. - -4) Exclusions From License Grant. Neither the names of Licensor, nor the names -of any contributors to the Original Work, nor any of their trademarks or -service marks, may be used to endorse or promote products derived from this -Original Work without express prior permission of the Licensor. Except as -expressly stated herein, nothing in this License grants any license to -Licensor's trademarks, copyrights, patents, trade secrets or any other -intellectual property. No patent license is granted to make, use, sell, offer -for sale, have made, or import embodiments of any patent claims other than the -licensed claims defined in Section 2. No license is granted to the trademarks -of Licensor even if such marks are included in the Original Work. Nothing in -this License shall be interpreted to prohibit Licensor from licensing under -terms different from this License any Original Work that Licensor otherwise -would have a right to license. - -5) External Deployment. The term "External Deployment" means the use, -distribution, or communication of the Original Work or Derivative Works in any -way such that the Original Work or Derivative Works may be used by anyone -other than You, whether those works are distributed or communicated to those -persons or made available as an application intended for use over a network. -As an express condition for the grants of license hereunder, You must treat -any External Deployment by You of the Original Work or a Derivative Work as a -distribution under section 1(c). - -6) Attribution Rights. You must retain, in the Source Code of any Derivative -Works that You create, all copyright, patent, or trademark notices from the -Source Code of the Original Work, as well as any notices of licensing and any -descriptive text identified therein as an "Attribution Notice." You must cause -the Source Code for any Derivative Works that You create to carry a prominent -Attribution Notice reasonably calculated to inform recipients that You have -modified the Original Work. - -7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that -the copyright in and to the Original Work and the patent rights granted herein -by Licensor are owned by the Licensor or are sublicensed to You under the -terms of this License with the permission of the contributor(s) of those -copyrights and patent rights. Except as expressly stated in the immediately -preceding sentence, the Original Work is provided under this License on an "AS -IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without -limitation, the warranties of non-infringement, merchantability or fitness for -a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK -IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this -License. No license to the Original Work is granted by this License except -under this disclaimer. - -8) Limitation of Liability. Under no circumstances and under no legal theory, -whether in tort (including negligence), contract, or otherwise, shall the -Licensor be liable to anyone for any indirect, special, incidental, or -consequential damages of any character arising as a result of this License or -the use of the Original Work including, without limitation, damages for loss -of goodwill, work stoppage, computer failure or malfunction, or any and all -other commercial damages or losses. This limitation of liability shall not -apply to the extent applicable law prohibits such limitation. - -9) Acceptance and Termination. If, at any time, You expressly assented to this -License, that assent indicates your clear and irrevocable acceptance of this -License and all of its terms and conditions. If You distribute or communicate -copies of the Original Work or a Derivative Work, You must make a reasonable -effort under the circumstances to obtain the express assent of recipients to -the terms of this License. This License conditions your rights to undertake -the activities listed in Section 1, including your right to create Derivative -Works based upon the Original Work, and doing so without honoring these terms -and conditions is prohibited by copyright law and international treaty. -Nothing in this License is intended to affect copyright exceptions and -limitations (including "fair use" or "fair dealing"). This License shall -terminate immediately and You may no longer exercise any of the rights granted -to You by this License upon your failure to honor the conditions in Section -1(c). - -10) Termination for Patent Action. This License shall terminate automatically -and You may no longer exercise any of the rights granted to You by this -License as of the date You commence an action, including a cross-claim or -counterclaim, against Licensor or any licensee alleging that the Original Work -infringes a patent. This termination provision shall not apply for an action -alleging patent infringement by combinations of the Original Work with other -software or hardware. - -11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this -License may be brought only in the courts of a jurisdiction wherein the -Licensor resides or in which Licensor conducts its primary business, and under -the laws of that jurisdiction excluding its conflict-of-law provisions. The -application of the United Nations Convention on Contracts for the -International Sale of Goods is expressly excluded. Any use of the Original -Work outside the scope of this License or after its termination shall be -subject to the requirements and penalties of copyright or patent law in the -appropriate jurisdiction. This section shall survive the termination of this -License. - -12) Attorneys' Fees. In any action to enforce the terms of this License or -seeking damages relating thereto, the prevailing party shall be entitled to -recover its costs and expenses, including, without limitation, reasonable -attorneys' fees and costs incurred in connection with such action, including -any appeal of such action. This section shall survive the termination of this -License. - -13) Miscellaneous. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent necessary -to make it enforceable. - -14) Definition of "You" in This License. "You" throughout this License, -whether in upper or lower case, means an individual or a legal entity -exercising rights under, and complying with all of the terms of, this License. -For legal entities, "You" includes any entity that controls, is controlled by, -or is under common control with you. For purposes of this definition, -"control" means (i) the power, direct or indirect, to cause the direction or -management of such entity, whether by contract or otherwise, or (ii) ownership -of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial -ownership of such entity. - -15) Right to Use. You may use the Original Work in all ways not otherwise -restricted or conditioned by this License or by law, and Licensor promises not -to interfere with or be responsible for such uses by You. - -16) Modification of This License. This License is Copyright © 2005 Lawrence -Rosen. Permission is granted to copy, distribute, or communicate this License -without modification. Nothing in this License permits You to modify this -License as applied to the Original Work or to Derivative Works. However, You -may modify the text of this License and copy, distribute or communicate your -modified version (the "Modified License") and apply it to other original works -of authorship subject to the following conditions: (i) You may not indicate in -any way that your Modified License is the "Open Software License" or "OSL" and -you may not use those names in the name of your Modified License; (ii) You -must replace the notice specified in the first paragraph above with the notice -"Licensed under " or with a notice of your own -that is not confusingly similar to the notice in this License; and (iii) You -may not claim that your original works are open source software unless your -Modified License has been approved by Open Source Initiative (OSI) and You -comply with its license review and certification process. \ No newline at end of file + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md index b8cb77d..0bd7fe6 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,4 @@ If you want to have the API running on a different port, change the port on `con # REST API - DOCUMENTATION -Moved to [docs](https://github.com/torikushiii/BlueArchiveAPI/tree/main/docs) folder - -### License - -Licensed under Open Software License v3.0 +Moved to [docs](https://github.com/torikushiii/BlueArchiveAPI/tree/main/docs) folder. diff --git a/app.js b/app.js deleted file mode 100644 index 79d44d3..0000000 --- a/app.js +++ /dev/null @@ -1,49 +0,0 @@ -const fastify = require("./lib/fastify"); -const logger = require("./lib/logger"); - -(async function () { - require("./db-access"); - globalThis.ba = await require("./modules/index")(); - const subroutes = [ - "character", - "equipment", - "stage", - "raid", - "banner", - "image" - ]; - - const config = ba.Config; - if (!config.host || !config.port) { - logger.error("Config file is missing host or port"); - process.exit(1); - } - - fastify.get("/robots.txt", (req, res) => { - res.type("text/plain"); - res.send("User-agent: *\nDisallow: /"); - }); - - fastify.get("/buruaka/", async (req, res) => { - res.send({ - status: 200, - version: config.version, - uptime: Math.round(Date.now() - process.uptime() * 1000), - endpoints: subroutes.filter((route) => route !== "image" && route !== "stage") - }); - }); - - for (const route of subroutes) { - fastify.register(require(`./routes/${route}`), { prefix: `buruaka/${route}` }); - } - - fastify.get("*", (req, res) => { - res.notFound("That endpoint does not exist"); - }); - - fastify.get("/buruaka", (req, res) => { - res.redirect(302, "/buruaka/"); - }); - - fastify.listen({ port: config.port, host: config.host }); -})(); diff --git a/core/classes/character.js b/core/classes/character.js new file mode 100644 index 0000000..f6b0229 --- /dev/null +++ b/core/classes/character.js @@ -0,0 +1,341 @@ +module.exports = class Character extends require("./template.js") { + static data = new Map(); + + constructor (data) { + super(); + + this.id = data.id; + + this.name = data.name; + + this.localizeEtcId = data.localizeEtcId; + + this.released = data.released; + + this.playable = data.playable; + + this.baseStar = data.baseStar; + + this.rarity = data.rarity; + + this.armorType = this.fixArmor(data.armorType); + + this.bulletType = this.fixBulletType(data.bulletType); + + this.position = data.position; + + this.role = this.fixRoleType(data.role); + + this.squadType = data.squadType; + + this.weaponType = data.weaponType; + + this.club = data.club; + + this.school = data.school; + + this.imageIdentifier = data.imageIdentifier; + + this.equipmentType = data.equipmentType; + + this.tags = data.tags; + + this.region = data.region; + } + + static async get (identifier, options = {}) { + if (identifier instanceof Character) { + return identifier; + } + else if (typeof identifier === "number") { + const region = options.region ?? "global"; + + const character = Character.data.get(`${region}.${identifier}`); + if (!character) { + return null; + } + + return character; + } + else if (typeof identifier === "string") { + const region = options.region ?? "global"; + + const values = [...Character.data.values()]; + const character = values.find( + i => i.name.toLowerCase() === identifier.toLowerCase() + && i.region === region + ); + + if (!character) { + return null; + } + + return character; + } + else { + if (options.getAll) { + const region = options.region ?? "global"; + + const characters = [...Character.data.values()] + .filter(i => i.region === region) + .filter(i => i.released === true); + + if (characters.length === 0) { + return null; + } + + const data = []; + for (const character of characters) { + const parsedCharacter = await this.buildCharacterObject(character, options); + if (!parsedCharacter) { + ba.Logger.warn(`Failed to parse character ${character.id}`, character); + continue; + } + + data.push(parsedCharacter); + } + + return data; + } + + ba.Logger.error("Invalid identifier provided. Must be a number or string.", { identifier, options }); + } + } + + static async getCharacterByQuery (query, options = {}) { + const region = options.region ?? "global"; + + const { + armor, + attack, + weapon, + position, + role, + school, + type + } = query; + + const characters = [...Character.data.values()] + .filter(i => i.region === region) + .filter(i => i.released === true); + + const filteredCharacters = characters.filter(i => { + if (armor && i.armorType.toLowerCase() !== armor) { + return false; + } + + if (attack && i.bulletType.toLowerCase() !== attack) { + return false; + } + + if (weapon && i.weaponType.toLowerCase() !== weapon) { + return false; + } + + if (position && i.position.toLowerCase() !== position) { + return false; + } + + if (role && i.role.toLowerCase() !== role) { + return false; + } + + if (school && i.school.toLowerCase() !== school) { + return false; + } + + if (type && i.squadType.toLowerCase() !== type) { + return false; + } + + return true; + }); + + if (filteredCharacters.length === 0) { + return null; + } + + return filteredCharacters.map(i => ({ + id: i.id, + name: i.name + })); + } + + static async loadData () { + const regions = [ + "global", + "japan" + ]; + + for (const region of regions) { + const characterData = await ba.Query.collection(`${region}.CharacterData`).find({}).toArray(); + if (characterData.length === 0) { + ba.Logger.error(`No character data found for ${region}`); + } + + for (const character of characterData) { + if (character.playable === false) { + continue; + } + + const char = await ba.Utils.getCharacterName(character.localizeEtcId, region); + if (!char || char.name === "LocalizeError") { + continue; + } + + const object = new Character({ ...character, name: char.name, region }); + Character.data.set(`${region}.${character.id}`, object); + } + } + } + + static async buildCharacterObject (character, options = {}) { + const region = options.region ?? "global"; + + if (options.getAll) { + const charData = await ba.Utils.getCharacterData(character, region); + if (!charData) { + return null; + } + + return { + id: character.id, + name: charData.name, + profile: charData.info.introduction, + rarity: character.rarity, + baseStar: character.baseStar, + position: character.position, + role: character.role, + armorType: character.armorType, + bulletType: character.bulletType, + weaponType: character.weaponType, + squadType: character.squadType, + school: character.school, + terrain: charData.topology + }; + } + + const skills = { + ex: [], + normal: [], + passive: [], + sub: [] + }; + + const skillData = ba.Skill.get(character.id, { region }); + if (skillData) { + if (skillData.skillEx) { + const exSkill = await ba.Utils.getSkillData(skillData.skillEx, region); + if (exSkill) { + skills.ex.push(exSkill); + } + } + + if (skillData.normal) { + const normalSkill = await ba.Utils.getSkillData(skillData.normal, region); + if (normalSkill) { + skills.normal.push(normalSkill); + } + } + + if (skillData.passive) { + const passiveSkill = await ba.Utils.getSkillData(skillData.passive, region); + if (passiveSkill) { + skills.passive.push(passiveSkill); + } + } + + if (skillData.sub) { + const subSkill = await ba.Utils.getSkillData(skillData.sub, region); + if (subSkill) { + skills.sub.push(subSkill); + } + } + } + + const charData = await ba.Utils.getCharacterData(character, region); + if (!charData) { + return null; + } + + delete charData.stat._id; + + let image = {}; + if (ba.Config.domain && ba.Config.domain !== null) { + image = this.getImage(character.imageIdentifier, ba.Config.domain); + } + + return { + id: character.id, + isReleased: character.released, + isPlayable: character.playable, + character: { + name: character.name, + baseStar: character.baseStar, + rarity: character.rarity, + armorType: character.armorType, + bulletType: character.bulletType, + position: character.position, + role: character.role, + squadType: character.squadType, + weaponType: character.weaponType, + profile: charData.info.introduction + }, + info: { + age: charData.info.age, + birthDate: charData.info.birthDate, + height: charData.info.height, + artist: charData.info.artistName, + club: character.club, + school: character.school, + schoolYear: charData.info.schoolYear, + voiceActor: charData.info.voiceActor + }, + image, + stat: charData.stat, + terrain: charData.topology, + skills + }; + } + + fixRoleType (roleType) { + const types = { + DamageDealer: "Dealer", + Tanker: "Tank", + Healer: "Healer", + Supporter: "Support", + Vehicle: "T.S." + }; + + return types[roleType] ?? "???"; + } + + fixBulletType (bulletType) { + const types = { + Explosion: "Explosive", + Mystic: "Mystic", + Penetration: "Piercing" + }; + + return types[bulletType] ?? bulletType; + } + + fixArmor (armor) { + const types = { + Unarmed: "Special", + HeavyArmor: "Heavy", + LightArmor: "Light", + ElasticArmor: "Elastic" + }; + + return types[armor] ?? armor; + } + + static getImage (identifier, domain) { + return { + icon: `${domain}/image/icon/${identifier}`, + lobby: `${domain}/image/lobby/${identifier}`, + portrait: `${domain}/image/portrait/${identifier}` + }; + } +}; diff --git a/core/classes/skill.js b/core/classes/skill.js new file mode 100644 index 0000000..2e2daa8 --- /dev/null +++ b/core/classes/skill.js @@ -0,0 +1,65 @@ +module.exports = class Skill extends require("./template.js") { + static data = new Map(); + + constructor (data) { + super(); + + this.id = data.id; + + this.skillEx = data.skillEx; + + this.normal = data.normal; + + this.passive = data.passive; + + this.sub = data.sub; + } + + static get (identifier, options = {}) { + if (identifier instanceof Skill) { + return identifier; + } + else if (typeof identifier === "number") { + const region = options.region ?? "global"; + + const skill = Skill.data.get(`${region}.${identifier}`); + if (!skill) { + return null; + } + + const values = [...Skill.data.values()]; + const index = values.findIndex(i => i.id === skill.id); + if (index === -1) { + return null; + } + + return values[index]; + } + else { + ba.Logger.error("Invalid identifier type passed to Skill.get()", { + identifier, + type: typeof identifier, + options + }); + } + } + + static async loadData () { + const regions = [ + "global", + "japan" + ]; + + for (const region of regions) { + const skillData = await ba.Query.collection(`${region}.SkillListData`).find({}).toArray(); + if (skillData.length === 0) { + ba.Logger.error(`No skill data found for ${region}`); + } + + for (const skill of skillData) { + const skillObj = new Skill(skill); + Skill.data.set(`${region}.${skillObj.id}`, skillObj); + } + } + } +}; diff --git a/modules/classes/template.js b/core/classes/template.js similarity index 83% rename from modules/classes/template.js rename to core/classes/template.js index 221c744..017fe9b 100644 --- a/modules/classes/template.js +++ b/core/classes/template.js @@ -1,7 +1,5 @@ module.exports = class BlueArchiveTemplate { static data = new Map(); - static dataGlobal = new Map(); - static dataJapan = new Map(); static async initialize () { await this.loadData(); diff --git a/modules/example.config.js b/core/example.config.js similarity index 61% rename from modules/example.config.js rename to core/example.config.js index 48271bc..4e0350f 100644 --- a/modules/example.config.js +++ b/core/example.config.js @@ -3,6 +3,5 @@ module.exports = { host: "0.0.0.0", port: 80, domain: null, - hostname: "example.com", - redis_configuration: {} + hostname: "example.com" }; diff --git a/modules/index.js b/core/index.js similarity index 73% rename from modules/index.js rename to core/index.js index 63d32f0..7f46c79 100644 --- a/modules/index.js +++ b/core/index.js @@ -1,20 +1,14 @@ module.exports = (async function (options = {}) { - /** - * Global namespace - * @namespace - * @type {Object} - */ globalThis.ba = {}; const files = [ + "singleton/logger", + "singleton/query", "singleton/utils", "classes/character", - "classes/drop", - "classes/equipment", - "classes/skill", - "classes/stage" + "classes/skill" ]; const { @@ -38,21 +32,20 @@ module.exports = (async function (options = {}) { if (type === "singleton") { switch (moduleName) { case "query": { - const Component = require("./singleton/query"); + const Component = require("./singletons/query"); ba.Query = Component.singleton().client; - await new Promise(resolve => setTimeout(resolve, 500)); break; } - case "cache": { - const Component = require("./singleton/cache"); - ba.Cache = Component.singleton(); + case "utils": { + const Component = require("./singletons/utils"); + ba.Utils = Component.singleton(); break; } - case "utils": { - const Component = require("./singleton/utils"); - ba.Utils = Component.singleton(); + case "logger": { + const Component = require("./singletons/logger"); + ba.Logger = Component.singleton(); break; } } diff --git a/core/singletons/logger.js b/core/singletons/logger.js new file mode 100644 index 0000000..4cf8cd9 --- /dev/null +++ b/core/singletons/logger.js @@ -0,0 +1,53 @@ +const Debug = require("debug"); + +module.exports = class LoggerSingleton extends require("./template.js") { + /** + * @returns {LoggerSingleton} + */ + static singleton () { + if (!LoggerSingleton.module) { + LoggerSingleton.module = new LoggerSingleton(); + } + + return LoggerSingleton.module; + } + + /** + * @hideconstructor + */ + constructor () { + super(); + + const logger = Debug("buruaka"); + + const log = logger.extend("[ LOG ]:"); + log.log = console.log.bind(console); + log.enabled = true; + this.log = log; + + const info = logger.extend("[ INFO ]:"); + info.log = console.info.bind(console); + info.color = log.color; + info.enabled = true; + this.info = info; + + const warn = logger.extend("[ WARN ]:"); + warn.log = console.warn.bind(console); + warn.color = "9"; + warn.enabled = true; + this.warn = warn; + + const error = logger.extend("[ ERROR ]:"); + error.log = console.error.bind(console); + error.color = "196"; + error.enabled = true; + this.error = error; + } + + log (message) { this.log(message); } + info (message) { this.info(message); } + warn (message) { this.warn(message); } + error (message) { this.error(message); } + + get modulePath () { return "logger"; } +}; diff --git a/modules/singleton/query.js b/core/singletons/query.js similarity index 78% rename from modules/singleton/query.js rename to core/singletons/query.js index d0987e7..694b58c 100644 --- a/modules/singleton/query.js +++ b/core/singletons/query.js @@ -1,7 +1,7 @@ const { MongoClient } = require("mongodb"); const url = `mongodb://${process.env.MONGO_IP}:${process.env.MONGO_PORT}`; -module.exports = class QuerySingleton extends require("./template") { +module.exports = class QuerySingleton extends require("./template.js") { /** @type {MongoClient} */ #pool = null; @@ -21,13 +21,12 @@ module.exports = class QuerySingleton extends require("./template") { super(); if (!process.env.MONGO_IP || !process.env.MONGO_PORT) { - throw new Error("Missing MongoDB credentials"); + throw new Error("MONGO_IP and MONGO_PORT environment variables must be set."); } else { this.#pool = new MongoClient(url, { useUnifiedTopology: true, - useNewUrlParser: true, - keepAlive: true + useNewUrlParser: true }); } @@ -36,8 +35,7 @@ module.exports = class QuerySingleton extends require("./template") { async connect () { await this.#pool.connect() - .then(() => console.log("Connected to MongoDB")) - .catch(e => console.error(e)); + .catch(e => ba.Logger.error(e)); this.initListeners(); } @@ -46,15 +44,15 @@ module.exports = class QuerySingleton extends require("./template") { const pool = this.#pool; pool.on("serverHeartbeatFailed", () => { - console.log("Server heartbeat failed"); + ba.Logger.error("MongoDB server heartbeat failed."); }); pool.on("topologyOpening", () => { - console.log("Topology opening"); + ba.Logger.info("MongoDB topology opening."); }); pool.on("topologyClosed", () => { - console.log("Topology closed"); + ba.Logger.info("MongoDB topology closed."); }); } diff --git a/core/singletons/template.js b/core/singletons/template.js new file mode 100644 index 0000000..680881d --- /dev/null +++ b/core/singletons/template.js @@ -0,0 +1,13 @@ +module.exports = class SingletonTemplate { + destroy () { + throw new Error("Module.destroy is not implemented"); + } + + static singleton () { + throw new Error("Module.singleton is not implemented"); + } + + get modulePath () { + throw new Error("Module.modulePath is not implemented"); + } +}; diff --git a/core/singletons/utils.js b/core/singletons/utils.js new file mode 100644 index 0000000..2b36222 --- /dev/null +++ b/core/singletons/utils.js @@ -0,0 +1,270 @@ +module.exports = class UtilsSingleton extends require("./template.js") { + static terrainTypes = { + Urban: { + SS: { + DamageDealt: "130%(1.3x)", + ShieldBlockRate: "75%" + }, + S: { + DamageDealt: "120%(1.2x)", + ShieldBlockRate: "60%" + }, + A: { + DamageDealt: "110%(1.1x)", + ShieldBlockRate: "45%" + }, + B: { + DamageDealt: "100%(1x)", + ShieldBlockRate: "30%" + }, + C: { + DamageDealt: "90%(0.9x)", + ShieldBlockRate: "15%" + }, + D: { + DamageDealt: "80%(0.8x)", + ShieldBlockRate: "0%" + } + }, + Desert: { + SS: { + DamageDealt: "130%(1.3x)", + ShieldBlockRate: "75%" + }, + S: { + DamageDealt: "120%(1.2x)", + ShieldBlockRate: "60%" + }, + A: { + DamageDealt: "110%(1.1x)", + ShieldBlockRate: "45%" + }, + B: { + DamageDealt: "100%(1x)", + ShieldBlockRate: "30%" + }, + C: { + DamageDealt: "90%(0.9x)", + ShieldBlockRate: "15%" + }, + D: { + DamageDealt: "80%(0.8x)", + ShieldBlockRate: "0%" + } + }, + Indoor: { + SS: { + DamageDealt: "130%(1.3x)", + ShieldBlockRate: "75%" + }, + S: { + DamageDealt: "120%(1.2x)", + ShieldBlockRate: "60%" + }, + A: { + DamageDealt: "110%(1.1x)", + ShieldBlockRate: "45%" + }, + B: { + DamageDealt: "100%(1x)", + ShieldBlockRate: "30%" + }, + C: { + DamageDealt: "90%(0.9x)", + ShieldBlockRate: "15%" + }, + D: { + DamageDealt: "80%(0.8x)", + ShieldBlockRate: "0%" + } + } + }; + + static data = new Map(); + + /** + * @inheritdoc + */ + static singleton () { + if (!UtilsSingleton.module) { + UtilsSingleton.module = new UtilsSingleton(); + } + + return UtilsSingleton.module; + } + + async getCharacterName (identifier, region) { + if (!UtilsSingleton.data.has(`${region}.LocalizeEtc`)) { + const characterNames = await ba.Query.collection(`${region}.LocalizeEtc`).find({}).toArray(); + if (!characterNames) { + throw new Error(`No character names found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.LocalizeEtc`, characterNames); + } + + const characterMap = UtilsSingleton.data.get(`${region}.LocalizeEtc`); + const characterData = characterMap.find(i => i.key === identifier); + if (!characterData) { + return null; + } + + return characterData; + } + + async getCharacterData (character, region) { + if (!UtilsSingleton.data.has(`${region}.CharacterLocalize`)) { + const characterLocalize = await ba.Query.collection(`${region}.CharacterLocalize`).find({}).toArray(); + if (!characterLocalize) { + throw new Error(`No character localize data found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.CharacterLocalize`, characterLocalize); + } + + if (!UtilsSingleton.data.has(`${region}.CharacterStat`)) { + const characterStat = await ba.Query.collection(`${region}.CharacterStat`).find({}).toArray(); + if (!characterStat) { + throw new Error(`No character stat data found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.CharacterStat`, characterStat); + } + + const info = UtilsSingleton.data.get(`${region}.CharacterLocalize`).find(i => i.id === character.id); + const statData = UtilsSingleton.data.get(`${region}.CharacterStat`).find(i => i.id === character.id); + if (!info || !statData) { + return null; + } + + const charName = await this.getCharacterName(character.localizeEtcId, region); + if (!charName) { + return null; + } + + return { + info, + name: charName.name, + stat: statData, + topology: { + urban: UtilsSingleton.terrainTypes.Urban[statData.streetMood], + outdoor: UtilsSingleton.terrainTypes.Desert[statData.outdoorMood], + indoor: UtilsSingleton.terrainTypes.Indoor[statData.indoorMood] + } + }; + } + + async getSkillInfo (identifier, region) { + if (!UtilsSingleton.data.has(`${region}.SkillLocalize`)) { + const skillLocalize = await ba.Query.collection(`${region}.SkillLocalize`).find({}).toArray(); + if (!skillLocalize) { + throw new Error(`No skill localize data found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.SkillLocalize`, skillLocalize); + } + + const skillLocalize = UtilsSingleton.data.get(`${region}.SkillLocalize`); + const skillData = skillLocalize.find(i => i.id === identifier); + if (!skillData) { + return null; + } + + return { + id: skillData.id, + name: skillData.name, + description: skillData.description + }; + } + + async getSkillData (identifier, region) { + if (!UtilsSingleton.data.has(`${region}.SkillListTable`)) { + const skillListTable = await ba.Query.collection(`${region}.SkillListTable`).find({}).toArray(); + if (!skillListTable) { + throw new Error(`No skill list table data found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.SkillListTable`, skillListTable); + } + + const stuff = []; + const skillListTable = UtilsSingleton.data.get(`${region}.SkillListTable`); + const skillData = skillListTable.filter(i => i.groupId === identifier); + if (skillData.length !== 0) { + for (const skill of skillData) { + const skillInfo = await this.getSkillInfo(skill.localizeSkillId, region); + if (skillInfo) { + stuff.push(skillInfo); + } + } + } + + return stuff; + } + + async getBannerData (region) { + if (!UtilsSingleton.data.has(`${region}.GachaData`)) { + const bannerData = await ba.Query.collection(`${region}.GachaData`).find({}).toArray(); + if (!bannerData) { + throw new Error(`No banner data found for ${region}`); + } + + UtilsSingleton.data.set(`${region}.GachaData`, bannerData); + } + + const current = []; + const upcoming = []; + const ended = []; + + const bannerData = UtilsSingleton.data.get(`${region}.GachaData`); + for (const banner of bannerData) { + const now = Date.now(); + const start = banner.startAt; + const end = banner.endAt; + + const bannerInfo = await this.parseBannerData(banner, region); + + if (now >= start && now <= end) { + current.push(bannerInfo); + } + else if (now < start) { + upcoming.push(bannerInfo); + } + else if (now > end) { + ended.push(bannerInfo); + } + } + + return { + current, + upcoming, + ended + }; + } + + async parseBannerData (data, region) { + const rateups = []; + for (const rateup of data.rateup) { + const charData = await ba.Character.get(rateup, { region }); + if (!charData) { + ba.Logger.error(`Character rate-up ${rateup} not found`); + continue; + } + + const characterData = await this.getCharacterName(charData.localizeEtcId, region); + if (!characterData) { + ba.Logger.error(`Character name rate-up ${rateup} not found`); + continue; + } + + rateups.push(characterData.name); + } + + return { + gachaType: data.type, + startAt: data.startAt, + endAt: data.endAt, + rateups + }; + } +}; diff --git a/docs/equpiment.md b/docs/equpiment.md deleted file mode 100644 index da2979a..0000000 --- a/docs/equpiment.md +++ /dev/null @@ -1,67 +0,0 @@ -# Equipment API - -**BASE URL:** `https://api.ennead.cc/buruaka/` - -### Get Equipment -Get Equipment by Tier or ID - -> [https://api.ennead.cc/buruaka/equipment/](https://api.ennead.cc/buruaka/equipment) - -> GET `equipment/:equipment` | `equipment/t1%20hairpin` | `equipment/Tennis%20Headband` - -> GET `equipment/6000?id=true` You need to pass `?id=true` to get equipment by ID - -> Returns: `Equipment Object` -
-View Payload Example - -```json -{ - "data": { - "id": 6000, - "localizeId": 1494732916, - "recipeId": 600, - "category": "Hairpin", - "rarity": "N", - "maxLevel": 10, - "tier": 1, - "tags": [ - "Equipment", - "Hairpin" - ] - }, - "drops": [ - { - "stageName": "CHAPTER01_Normal_Main_Stage04", - "dropAmount": 1, - "dropChance": 30 - }, - { - "stageName": "CHAPTER01_Hard_Main_Stage03", - "dropAmount": 1, - "dropChance": 60 - }, - { - "stageName": "CHAPTER02_Normal_Main_Stage01", - "dropAmount": 1, - "dropChance": 40 - }, - { - "stageName": "CHAPTER02_Normal_Main_Stage02", - "dropAmount": 1, - "dropChance": 40 - }, - { - "stageName": "CHAPTER02_Normal_Main_Stage03", - "dropAmount": 1, - "dropChance": 30 - }, - { - "stageName": "CHAPTER02_Hard_Main_Stage03", - "dropAmount": 1, - "dropChance": 80 - } - ] -} -``` -
\ No newline at end of file diff --git a/docs/query.md b/docs/query.md index db8e1fa..a07344a 100644 --- a/docs/query.md +++ b/docs/query.md @@ -4,68 +4,283 @@ ## Character Query list example -### Get characters by role +### Get characters by their role -> https://api.ennead.cc/buruaka/character/query?role=attacker +> https://api.ennead.cc/buruaka/character/query?role=dealer Role list: - - Attacker + - Tank + - Dealer - Healer - - Supporter - - Tanker + - Support + - T.S.
View Payload Example ```json -{ - [ - "Akari", - "Aris", - "Aru", - "Asuna", - "Azusa", - "Azusa (Swimsuit)", - "Cherino", - "Chise", - "Haruna", - "Hasumi", - "Hibiki", - "Hina", - "Hina (Swimsuit)", - "Iori", - "Iori (Swimsuit)", - "Izumi", - "Izuna", - "Junko", - "Karin", - "Maki", - "Mashiro", - "Mashiro (Swimsuit)", - "Midori", - "Momoi", - "Mutsuki", - "Neru", - "Nonomi", - "Pina", - "Saya", - "Serika", - "Shiroko", - "Shiroko (Cycling)", - "Shun", - "Shun (Small)", - "Sumire", - "Tsurugi", - "Tsurugi (Swimsuit)", - "Utaha", - "Yoshimi", - "Yuzu" - ] -} +[ + { + "id": 10000, + "name": "Aru" + }, + { + "id": 10002, + "name": "Haruna" + }, + { + "id": 10004, + "name": "Hina" + }, + { + "id": 10006, + "name": "Iori" + }, + { + "id": 10007, + "name": "Maki" + }, + { + "id": 10008, + "name": "Neru" + }, + { + "id": 10009, + "name": "Izumi" + }, + { + "id": 10010, + "name": "Shiroko" + }, + { + "id": 10011, + "name": "Shun" + }, + { + "id": 10012, + "name": "Sumire" + }, + { + "id": 10013, + "name": "Tsurugi" + }, + { + "id": 10014, + "name": "Izuna" + }, + { + "id": 10015, + "name": "Aris" + }, + { + "id": 10016, + "name": "Midori" + }, + { + "id": 10017, + "name": "Cherino" + }, + { + "id": 10018, + "name": "Yuzu" + }, + { + "id": 10019, + "name": "Azusa" + }, + { + "id": 10021, + "name": "Azusa (Swimsuit)" + }, + { + "id": 10022, + "name": "Hina (Swimsuit)" + }, + { + "id": 10023, + "name": "Iori (Swimsuit)" + }, + { + "id": 10024, + "name": "Shiroko (Cycling)" + }, + { + "id": 10025, + "name": "Shun (Small)" + }, + { + "id": 10027, + "name": "Karin (Bunny)" + }, + { + "id": 10031, + "name": "Aru (New Year)" + }, + { + "id": 10032, + "name": "Mutsuki (New Year)" + }, + { + "id": 10033, + "name": "Wakamo" + }, + { + "id": 10036, + "name": "Hinata" + }, + { + "id": 10041, + "name": "Misaki" + }, + { + "id": 10043, + "name": "Wakamo (Swimsuit)" + }, + { + "id": 10044, + "name": "Nonomi (Swimsuit)" + }, + { + "id": 10046, + "name": "Izuna (Swimsuit)" + }, + { + "id": 10048, + "name": "Saori" + }, + { + "id": 10049, + "name": "Kazusa" + }, + { + "id": 10051, + "name": "Utaha (Cheer Squad)" + }, + { + "id": 10055, + "name": "Shigure" + }, + { + "id": 10057, + "name": "Haruna (New Year)" + }, + { + "id": 13001, + "name": "Chise" + }, + { + "id": 13002, + "name": "Akari" + }, + { + "id": 13003, + "name": "Hasumi" + }, + { + "id": 13004, + "name": "Nonomi" + }, + { + "id": 13006, + "name": "Mutsuki" + }, + { + "id": 13007, + "name": "Junko" + }, + { + "id": 13008, + "name": "Serika" + }, + { + "id": 13011, + "name": "Momoi" + }, + { + "id": 16001, + "name": "Asuna" + }, + { + "id": 16004, + "name": "Pina" + }, + { + "id": 16005, + "name": "Tsurugi (Swimsuit)" + }, + { + "id": 16008, + "name": "Fubuki" + }, + { + "id": 16009, + "name": "Michiru" + }, + { + "id": 16010, + "name": "Hibiki (Cheer Squad)" + }, + { + "id": 16011, + "name": "Hasumi (Track)" + }, + { + "id": 16012, + "name": "Junko (New Year)" + }, + { + "id": 20000, + "name": "Hibiki" + }, + { + "id": 20001, + "name": "Karin" + }, + { + "id": 20002, + "name": "Saya" + }, + { + "id": 20003, + "name": "Mashiro" + }, + { + "id": 20004, + "name": "Mashiro (Swimsuit)" + }, + { + "id": 20006, + "name": "Saya (Casual)" + }, + { + "id": 20013, + "name": "Chihiro" + }, + { + "id": 20014, + "name": "Saki" + }, + { + "id": 20018, + "name": "Moe" + }, + { + "id": 20019, + "name": "Akane (Bunny)" + }, + { + "id": 23004, + "name": "Utaha" + }, + { + "id": 26005, + "name": "Yoshimi" + } +] ```
-### Get characters by type +### Get characters by their type > https://api.ennead.cc/buruaka/character/query?type=special Type list: @@ -76,66 +291,240 @@ View Payload Example ```json -{ - [ - "Airi", - "Ayane", - "Chinatsu", - "Fuuka", - "Hanae", - "Hanako", - "Hare", - "Hibiki", - "Hifumi (Swimsuit)", - "Juri", - "Karin", - "Kotama", - "Mashiro", - "Mashiro (Swimsuit)", - "Nodoka", - "Saya", - "Saya (Casual)", - "Serina", - "Shimiko", - "Shizuko", - "Utaha", - "Yoshimi" - ] -} - +[ + { + "id": 20000, + "name": "Hibiki" + }, + { + "id": 20001, + "name": "Karin" + }, + { + "id": 20002, + "name": "Saya" + }, + { + "id": 20003, + "name": "Mashiro" + }, + { + "id": 20004, + "name": "Mashiro (Swimsuit)" + }, + { + "id": 20005, + "name": "Hifumi (Swimsuit)" + }, + { + "id": 20006, + "name": "Saya (Casual)" + }, + { + "id": 20007, + "name": "Hatsune Miku" + }, + { + "id": 20008, + "name": "Ako" + }, + { + "id": 20009, + "name": "Cherino (Hot Spring)" + }, + { + "id": 20010, + "name": "Nodoka (Hot Spring)" + }, + { + "id": 20011, + "name": "Serika (New Year)" + }, + { + "id": 20012, + "name": "Sena" + }, + { + "id": 20013, + "name": "Chihiro" + }, + { + "id": 20014, + "name": "Saki" + }, + { + "id": 20015, + "name": "Kaede" + }, + { + "id": 20016, + "name": "Iroha" + }, + { + "id": 20017, + "name": "Hiyori" + }, + { + "id": 20018, + "name": "Moe" + }, + { + "id": 20019, + "name": "Akane (Bunny)" + }, + { + "id": 20020, + "name": "Himari" + }, + { + "id": 20021, + "name": "Hanae (Christmas)" + }, + { + "id": 20022, + "name": "Fuuka (New Year)" + }, + { + "id": 23000, + "name": "Airi" + }, + { + "id": 23001, + "name": "Fuuka" + }, + { + "id": 23002, + "name": "Hanae" + }, + { + "id": 23003, + "name": "Hare" + }, + { + "id": 23004, + "name": "Utaha" + }, + { + "id": 23005, + "name": "Ayane" + }, + { + "id": 23006, + "name": "Shizuko" + }, + { + "id": 23007, + "name": "Hanako" + }, + { + "id": 23008, + "name": "Mari" + }, + { + "id": 26000, + "name": "Chinatsu" + }, + { + "id": 26001, + "name": "Kotama" + }, + { + "id": 26002, + "name": "Juri" + }, + { + "id": 26003, + "name": "Serina" + }, + { + "id": 26004, + "name": "Shimiko" + }, + { + "id": 26005, + "name": "Yoshimi" + }, + { + "id": 26006, + "name": "Nodoka" + }, + { + "id": 26007, + "name": "Ayane (Swimsuit)" + }, + { + "id": 26008, + "name": "Shizuko (Swimsuit)" + } +] ``` -### Get chracters by school +### Get chracters by their school > https://api.ennead.cc/buruaka/character/query?school=abydos School list: - Abydos + - Arius - Gehenna - Hyakkiyako - Millennium + - Red Winter - Shanhaijing + - SRT - Trinity + - Valkyrie
View Payload Example ```json -{ - [ - "Ayane", - "Hoshino", - "Nonomi", - "Serika", - "Shiroko", - "Shiroko (Cycling)" - ] -} - +[ + { + "id": 10005, + "name": "Hoshino" + }, + { + "id": 10010, + "name": "Shiroko" + }, + { + "id": 10024, + "name": "Shiroko (Cycling)" + }, + { + "id": 10044, + "name": "Nonomi (Swimsuit)" + }, + { + "id": 10045, + "name": "Hoshino (Swimsuit)" + }, + { + "id": 13004, + "name": "Nonomi" + }, + { + "id": 13008, + "name": "Serika" + }, + { + "id": 20011, + "name": "Serika (New Year)" + }, + { + "id": 23005, + "name": "Ayane" + }, + { + "id": 26007, + "name": "Ayane (Swimsuit)" + } +] ```
-### Get characters by position +### Get characters by their position > https://api.ennead.cc/buruaka/character/query?position=front Position list: @@ -147,28 +536,102 @@ View Payload Example ```json -{ - [ - "Eimi", - "Haruka", - "Hoshino", - "Izuna", - "Neru", - "Sumire", - "Tsubaki", - "Tsurugi", - "Tsurugi (Swimsuit)", - "Yuuka" - ] -} - +[ + { + "id": 10001, + "name": "Eimi" + }, + { + "id": 10005, + "name": "Hoshino" + }, + { + "id": 10008, + "name": "Neru" + }, + { + "id": 10012, + "name": "Sumire" + }, + { + "id": 10013, + "name": "Tsurugi" + }, + { + "id": 10014, + "name": "Izuna" + }, + { + "id": 10026, + "name": "Neru (Bunny)" + }, + { + "id": 10029, + "name": "Natsu" + }, + { + "id": 10037, + "name": "Marina" + }, + { + "id": 10038, + "name": "Miyako" + }, + { + "id": 10040, + "name": "Tsukuyo" + }, + { + "id": 10042, + "name": "Atsuko" + }, + { + "id": 10045, + "name": "Hoshino (Swimsuit)" + }, + { + "id": 10046, + "name": "Izuna (Swimsuit)" + }, + { + "id": 10051, + "name": "Utaha (Cheer Squad)" + }, + { + "id": 10053, + "name": "Yuuka (Track)" + }, + { + "id": 10058, + "name": "Mine" + }, + { + "id": 13009, + "name": "Tsubaki" + }, + { + "id": 13010, + "name": "Yuuka" + }, + { + "id": 16000, + "name": "Haruka" + }, + { + "id": 16005, + "name": "Tsurugi (Swimsuit)" + }, + { + "id": 16009, + "name": "Michiru" + } +] ``` -### Get characters by weapon +### Get characters by their weapon type > https://api.ennead.cc/buruaka/character/query?weapon=ar - Weapon list: - AR - GL @@ -181,141 +644,550 @@ - SG - SMG - SR + - FT
View Payload Example ```json -{ - [ - "Akari", - "Asuna", - "Azusa", - "Azusa (Swimsuit)", - "Hanae", - "Hanako", - "Hare", - "Hifumi", - "Hifumi (Swimsuit)", - "Junko", - "Momoi", - "Serika", - "Serina", - "Shimiko", - "Shiroko", - "Shiroko (Cycling)", - "Suzumi", - "Yoshimi" - ] -} - +[ + { + "id": 10003, + "name": "Hifumi" + }, + { + "id": 10010, + "name": "Shiroko" + }, + { + "id": 10019, + "name": "Azusa" + }, + { + "id": 10021, + "name": "Azusa (Swimsuit)" + }, + { + "id": 10024, + "name": "Shiroko (Cycling)" + }, + { + "id": 10028, + "name": "Asuna (Bunny)" + }, + { + "id": 10048, + "name": "Saori" + }, + { + "id": 10050, + "name": "Kokona" + }, + { + "id": 10056, + "name": "Serina (Christmas)" + }, + { + "id": 13002, + "name": "Akari" + }, + { + "id": 13007, + "name": "Junko" + }, + { + "id": 13008, + "name": "Serika" + }, + { + "id": 13011, + "name": "Momoi" + }, + { + "id": 16001, + "name": "Asuna" + }, + { + "id": 16003, + "name": "Suzumi" + }, + { + "id": 16012, + "name": "Junko (New Year)" + }, + { + "id": 20005, + "name": "Hifumi (Swimsuit)" + }, + { + "id": 20011, + "name": "Serika (New Year)" + }, + { + "id": 20013, + "name": "Chihiro" + }, + { + "id": 20021, + "name": "Hanae (Christmas)" + }, + { + "id": 23002, + "name": "Hanae" + }, + { + "id": 23003, + "name": "Hare" + }, + { + "id": 23007, + "name": "Hanako" + }, + { + "id": 26003, + "name": "Serina" + }, + { + "id": 26004, + "name": "Shimiko" + }, + { + "id": 26005, + "name": "Yoshimi" + } +] ```
-### Get characters by damage -> https://api.ennead.cc/buruaka/character/query?damage=explosion +### Get characters by their attack type +> https://api.ennead.cc/buruaka/character/query?damage=explosive Damage list: - - Explosion + - Explosive - Mystic - - Penetration + - Piercing
View Payload Example ```json -{ - [ - "Airi", - "Akari", - "Aru", - "Azusa", - "Eimi", - "Fuuka", - "Hanae", - "Hare", - "Haruka", - "Hibiki", - "Hina", - "Hina (Swimsuit)", - "Iori (Swimsuit)", - "Izumi", - "Izumi (Swimsuit)", - "Juri", - "Kayoko", - "Kirino", - "Koharu", - "Kotama", - "Mashiro", - "Mutsuki", - "Nodoka", - "Saya", - "Serika", - "Shimiko", - "Shiroko", - "Shun", - "Shun (Small)", - "Suzumi", - "Yuuka" - ] -} - +[ + { + "id": 10000, + "name": "Aru" + }, + { + "id": 10001, + "name": "Eimi" + }, + { + "id": 10004, + "name": "Hina" + }, + { + "id": 10009, + "name": "Izumi" + }, + { + "id": 10010, + "name": "Shiroko" + }, + { + "id": 10011, + "name": "Shun" + }, + { + "id": 10019, + "name": "Azusa" + }, + { + "id": 10020, + "name": "Koharu" + }, + { + "id": 10022, + "name": "Hina (Swimsuit)" + }, + { + "id": 10023, + "name": "Iori (Swimsuit)" + }, + { + "id": 10025, + "name": "Shun (Small)" + }, + { + "id": 10026, + "name": "Neru (Bunny)" + }, + { + "id": 10035, + "name": "Ui" + }, + { + "id": 10041, + "name": "Misaki" + }, + { + "id": 10042, + "name": "Atsuko" + }, + { + "id": 10044, + "name": "Nonomi (Swimsuit)" + }, + { + "id": 10045, + "name": "Hoshino (Swimsuit)" + }, + { + "id": 10048, + "name": "Saori" + }, + { + "id": 10055, + "name": "Shigure" + }, + { + "id": 10057, + "name": "Haruna (New Year)" + }, + { + "id": 10058, + "name": "Mine" + }, + { + "id": 13002, + "name": "Akari" + }, + { + "id": 13005, + "name": "Kayoko" + }, + { + "id": 13006, + "name": "Mutsuki" + }, + { + "id": 13008, + "name": "Serika" + }, + { + "id": 13010, + "name": "Yuuka" + }, + { + "id": 13012, + "name": "Kirino" + }, + { + "id": 16000, + "name": "Haruka" + }, + { + "id": 16003, + "name": "Suzumi" + }, + { + "id": 16006, + "name": "Izumi (Swimsuit)" + }, + { + "id": 16010, + "name": "Hibiki (Cheer Squad)" + }, + { + "id": 20000, + "name": "Hibiki" + }, + { + "id": 20002, + "name": "Saya" + }, + { + "id": 20003, + "name": "Mashiro" + }, + { + "id": 20007, + "name": "Hatsune Miku" + }, + { + "id": 20009, + "name": "Cherino (Hot Spring)" + }, + { + "id": 20010, + "name": "Nodoka (Hot Spring)" + }, + { + "id": 20015, + "name": "Kaede" + }, + { + "id": 20017, + "name": "Hiyori" + }, + { + "id": 23000, + "name": "Airi" + }, + { + "id": 23001, + "name": "Fuuka" + }, + { + "id": 23002, + "name": "Hanae" + }, + { + "id": 23003, + "name": "Hare" + }, + { + "id": 26001, + "name": "Kotama" + }, + { + "id": 26002, + "name": "Juri" + }, + { + "id": 26004, + "name": "Shimiko" + }, + { + "id": 26006, + "name": "Nodoka" + } +] ```
-### Get characters by armor -> https://api.ennead.cc/buruaka/character/query?armor=heavy%20armor +### Get characters by their armor type +> https://api.ennead.cc/buruaka/character/query?armor=heavy Armor list: - - Heavy Armor - - Light Armor - - Special Armor + - Heavy + - Light + - Special + - Elastic
View Payload Example ```json -{ - [ - "Akari", - "Azusa", - "Chise", - "Fuuka", - "Hanae", - "Haruna", - "Hasumi", - "Hibiki", - "Hifumi (Swimsuit)", - "Hina", - "Hina (Swimsuit)", - "Hoshino", - "Iori", - "Karin", - "Kayoko", - "Koharu", - "Mashiro", - "Nodoka", - "Shiroko (Cycling)", - "Suzumi", - "Tsurugi", - "Utaha", - "Yoshimi", - "Yuuka" - ] -} - +[ + { + "id": 10002, + "name": "Haruna" + }, + { + "id": 10004, + "name": "Hina" + }, + { + "id": 10005, + "name": "Hoshino" + }, + { + "id": 10006, + "name": "Iori" + }, + { + "id": 10013, + "name": "Tsurugi" + }, + { + "id": 10019, + "name": "Azusa" + }, + { + "id": 10020, + "name": "Koharu" + }, + { + "id": 10022, + "name": "Hina (Swimsuit)" + }, + { + "id": 10024, + "name": "Shiroko (Cycling)" + }, + { + "id": 10026, + "name": "Neru (Bunny)" + }, + { + "id": 10027, + "name": "Karin (Bunny)" + }, + { + "id": 10029, + "name": "Natsu" + }, + { + "id": 10032, + "name": "Mutsuki (New Year)" + }, + { + "id": 10036, + "name": "Hinata" + }, + { + "id": 10038, + "name": "Miyako" + }, + { + "id": 10043, + "name": "Wakamo (Swimsuit)" + }, + { + "id": 10049, + "name": "Kazusa" + }, + { + "id": 10055, + "name": "Shigure" + }, + { + "id": 13001, + "name": "Chise" + }, + { + "id": 13002, + "name": "Akari" + }, + { + "id": 13003, + "name": "Hasumi" + }, + { + "id": 13005, + "name": "Kayoko" + }, + { + "id": 13010, + "name": "Yuuka" + }, + { + "id": 16003, + "name": "Suzumi" + }, + { + "id": 16008, + "name": "Fubuki" + }, + { + "id": 16012, + "name": "Junko (New Year)" + }, + { + "id": 20000, + "name": "Hibiki" + }, + { + "id": 20001, + "name": "Karin" + }, + { + "id": 20003, + "name": "Mashiro" + }, + { + "id": 20005, + "name": "Hifumi (Swimsuit)" + }, + { + "id": 20008, + "name": "Ako" + }, + { + "id": 20009, + "name": "Cherino (Hot Spring)" + }, + { + "id": 20013, + "name": "Chihiro" + }, + { + "id": 20016, + "name": "Iroha" + }, + { + "id": 20019, + "name": "Akane (Bunny)" + }, + { + "id": 23001, + "name": "Fuuka" + }, + { + "id": 23002, + "name": "Hanae" + }, + { + "id": 23004, + "name": "Utaha" + }, + { + "id": 26005, + "name": "Yoshimi" + }, + { + "id": 26006, + "name": "Nodoka" + }, + { + "id": 26008, + "name": "Shizuko (Swimsuit)" + } +] ```
## Get character with multiple queries Support multiple queries -> https://api.ennead.cc/buruaka/character/query?armor=special%20armor&position=front&damage=penetration +> https://api.ennead.cc/buruaka/character/query?armor=special&position=front&damage=penetration
View Payload Example ```json -{ ["Sumire", "Tsubaki"] } +[ + { + "id": 10012, + "name": "Sumire" + }, + { + "id": 10042, + "name": "Atsuko" + }, + { + "id": 10045, + "name": "Hoshino (Swimsuit)" + }, + { + "id": 10046, + "name": "Izuna (Swimsuit)" + }, + { + "id": 10051, + "name": "Utaha (Cheer Squad)" + }, + { + "id": 10053, + "name": "Yuuka (Track)" + }, + { + "id": 13009, + "name": "Tsubaki" + }, + { + "id": 16005, + "name": "Tsurugi (Swimsuit)" + } +] ```
\ No newline at end of file diff --git a/images/icon/ch0188.png b/images/icon/ch0188.png deleted file mode 100644 index a291bc1..0000000 Binary files a/images/icon/ch0188.png and /dev/null differ diff --git a/images/lobby/ch0188.png b/images/lobby/ch0188.png deleted file mode 100644 index 8d65029..0000000 Binary files a/images/lobby/ch0188.png and /dev/null differ diff --git a/images/portrait/ch0188.png b/images/portrait/ch0188.png deleted file mode 100644 index 181d25d..0000000 Binary files a/images/portrait/ch0188.png and /dev/null differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..7fa7b65 --- /dev/null +++ b/index.js @@ -0,0 +1,52 @@ +const fastify = require("./lib/fastify"); + +(async function () { + require("./db-access.js"); + + const initGlobals = require("./core/index.js"); + globalThis.ba = await initGlobals(); + + const subroutes = [ + ["character", true], + ["raid", true], + ["banner", true], + ["image", false] + ]; + + const config = ba.Config; + if (!config.host || !config.port) { + console.error("Config file is missing host or port"); + process.exit(1); + } + + fastify.get("/robots.txt", (req, res) => { + res.type("text/plain"); + res.send("User-agent: *\nDisallow: /"); + }); + + fastify.get("/buruaka/", (req, res) => { + // eslint-disable-next-line max-nested-callbacks + const endpoints = subroutes.filter((route) => route[1]).map((route) => route[0]); + + res.send({ + status: 200, + uptime: Math.round(Date.now() - process.uptime() * 1000), + endpoints + }); + }); + + for (const route of subroutes) { + const routeName = route[0]; + fastify.register(require(`./routes/${routeName}.js`), { prefix: `buruaka/${routeName}` }); + } + + fastify.get("*", (req, res) => { + res.notFound(); + }); + + fastify.get("/buruaka", (req, res) => { + res.redirect(301, "/buruaka/"); + }); + + fastify.listen({ port: config.port, host: config.host }); +})(); diff --git a/lib/fastify.js b/lib/fastify.js index 0f39c30..ab277cb 100644 --- a/lib/fastify.js +++ b/lib/fastify.js @@ -1,5 +1,3 @@ -const logger = require("./logger"); - /** * @type {import('fastify').FastifyInstance} */ @@ -48,7 +46,7 @@ fastify.setErrorHandler(async (error, request, reply) => { }); } catch (e) { - logger.error("Error while trying to log error", e); + ba.Logger.error("Error while trying to log error", e); } fastify.log.error(error); diff --git a/lib/logger.js b/lib/logger.js deleted file mode 100644 index 9b2c7db..0000000 --- a/lib/logger.js +++ /dev/null @@ -1,12 +0,0 @@ -const Logger = require("pino")({ - level: "info", - transport: { - target: "pino-pretty", - options: { - translateTime: "HH:MM:ss", - ignore: "pid,hostname" - } - } -}); - -module.exports = Logger; diff --git a/modules/classes/character.js b/modules/classes/character.js deleted file mode 100644 index 371156b..0000000 --- a/modules/classes/character.js +++ /dev/null @@ -1,314 +0,0 @@ -const chalk = require("chalk"); -const { domain } = require("../config.js"); - -module.exports = class Character extends require("./template") { - static data = new Map(); - - constructor (data) { - super(); - - this.id = data.id; - - this.localizeEtcId = data.localizeEtcId; - - this.name = data.name; - - this.released = data.released; - - this.playable = data.playable; - - this.baseStar = data.baseStar; - - this.rarity = data.rarity; - - this.armorType = this.fixArmorType(data.armorType); - - this.bulletType = data.bulletType; - - this.position = data.position; - - this.role = this.fixRoleType(data.role); - - this.squadType = data.squadType; - - this.weaponType = data.weaponType; - - this.club = data.club; - - this.school = data.school; - - this.imageIdentifier = data.imageIdentifier; - - this.equipmentType = data.equipmentType; - - this.tags = data.tags; - - this.region = data.region; - } - - static get (identifier, region = "global") { - if (identifier instanceof Character) { - return identifier; - } - else if (typeof identifier === "number") { - return Character.data.get(`${region}-${identifier}`); - } - else if (typeof identifier === "string") { - const normalized = Character.normalizeName(identifier); - for (const character of Character.data.values()) { - if (Character.normalizeName(character.name) === normalized) { - return character; - } - } - - return null; - } - else { - console.error(chalk `{red Invalid identifier for Character.get(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static getCharacterbyQuery (identifier, region = "global") { - if (identifier instanceof Character) { - return identifier; - } - else if (typeof identifier === "object") { - const { type, armor, damage, school, role, position, weapon } = identifier; - if (type === undefined - && armor === undefined - && damage === undefined - && school === undefined - && role === undefined - && position === undefined - && weapon === undefined) { - return null; - } - - const values = [...Character.data.values()]; - const data = values.filter(value => ((type) ? Character.normalizeName(value.squadType) === Character.normalizeName(type) : true) - && ((armor) ? Character.normalizeName(value.armorType) === Character.normalizeName(armor) : true) - && ((damage) ? Character.normalizeName(value.bulletType) === Character.normalizeName(damage) : true) - && ((school) ? Character.normalizeName(value.school) === Character.normalizeName(school) : true) - && ((role) ? Character.normalizeName(value.role) === Character.normalizeName(role) : true) - && ((position) ? Character.normalizeName(value.position) === Character.normalizeName(position) : true) - && ((weapon) ? Character.normalizeName(value.weaponType) === Character.normalizeName(weapon) : true) - && value.playable - && value.name !== "???" - && value.name !== "LocalizeError" - && value.region === region - ); - - if (data.length === 0) { - return null; - } - - return data.map(i => i.name).sort(); - } - else { - console.error(chalk `{red Invalid identifier for Character.get(). Expected object!}`, { - identifier, - type: typeof identifier - }); - } - } - - static async getAll (region = "global") { - const data = []; - - const values = [...Character.data.values()]; - const character = values.filter(i => i.playable - && i.name !== "???" - && i.name !== "LocalizeError" - && i.region === region - ); - - for (const char of character) { - const charData = await this.parseCharacterData(char, { allChars: true, region }); - if (!charData) { - continue; - } - - data.push(charData); - } - - return data; - } - - static async loadData () { - const regions = ["global", "japan"]; - - for (const region of regions) { - const data = await ba.Query.collection(`${region}.CharacterData`).find({}).toArray(); - if (data.length === 0) { - throw new Error(`No character data found for region ${region}!`); - } - - for (const character of data) { - const charData = await ba.Utils.getCharacterName(character.localizeEtcId, region); - if (!charData) { - continue; - } - - const characterData = new Character({ ...character, name: charData.name, region }); - Character.data.set(`${region}-${characterData.id}`, characterData); - } - } - } - - static destroy () { - Character.data.clear(); - } - - fixArmorType (armorType) { - const types = { - Unarmed: "Special Armor", - HeavyArmor: "Heavy Armor", - LightArmor: "Light Armor", - ElasticArmor: "Elastic Armor" - }; - - return types[armorType] ?? "???"; - } - - fixRoleType (roleType) { - const types = { - DamageDealer: "Attacker", - Tanker: "Tanker", - Healer: "Healer", - Supporter: "Supporter", - Vehicle: "Tactical" - }; - - return types[roleType] ?? "???"; - } - - static async parseCharacterData (data, options = {}) { - const skills = { - ex: [], - normal: [], - passive: [], - sub: [] - }; - - if (options.allChars) { - const charData = await ba.Utils.getCharacterData(data.id, options.region); - if (!charData) { - return null; - } - - return { - id: data.id, - baseStar: data.baseStar, - rarity: data.rarity, - name: data.name, - profile: charData.info.introduction, - armorType: data.armorType, - bulletType: data.bulletType, - position: data.position, - role: data.role, - squadType: data.squadType, - weaponType: data.weaponType, - terrain: charData.topology, - school: data.school - }; - } - - const region = options.region ?? "global"; - const skillData = ba.Skill.get(data.id, { region }); - if (skillData) { - for (const [type, name] of Object.entries(skillData)) { - if (type === "id") { - continue; - } - - switch (type) { - case "skillEx": { - const exInfo = await ba.Utils.getSkillData(name, region); - skills.ex.push(...exInfo); - break; - } - - case "normal": { - const normalInfo = await ba.Utils.getSkillData(name, region); - skills.normal.push(...normalInfo); - break; - } - - case "passive": { - const passiveInfo = await ba.Utils.getSkillData(name, region); - skills.passive.push(...passiveInfo); - break; - } - - case "sub": { - const subInfo = await ba.Utils.getSkillData(name, region); - skills.sub.push(...subInfo); - break; - } - - default: - return null; - } - } - } - - const charData = await ba.Utils.getCharacterData(data.id, region); - if (!charData) { - return null; - } - - let image = {}; - if (domain && domain !== null) { - image = this.getImage(data.imageIdentifier, domain); - } - - return { - id: data.id, - isReleased: data.released, - isPlayable: data.playable, - character: { - armorType: data.armorType, - baseStar: data.baseStar, - bulletType: data.bulletType, - name: data.name, - position: data.position, - profile: charData.info.introduction, - rarity: data.rarity, - role: data.role, - squadType: data.squadType, - weaponType: data.weaponType - }, - info: { - age: charData.info.age, - height: charData.info.height, - artist: charData.info.artistName, - birthDate: charData.info.birthDate, - club: data.club, - school: data.school, - schoolYear: charData.info.schoolYear, - voiceActor: charData.info.voiceActor - }, - image, - stat: charData.stat, - terrain: charData.topology, - skills - }; - } - - static getImage (identifier, domain) { - return { - icon: `${domain}/image/icon/${identifier}`, - lobby: `${domain}/image/lobby/${identifier}`, - portrait: `${domain}/image/portrait/${identifier}` - }; - } - - static normalizeName (name) { - return name - .toLowerCase() - .replace(/\s+/g, "_"); - } -}; diff --git a/modules/classes/drop.js b/modules/classes/drop.js deleted file mode 100644 index 65930fd..0000000 --- a/modules/classes/drop.js +++ /dev/null @@ -1,81 +0,0 @@ -const chalk = require("chalk"); - -module.exports = class Drops extends require("./template") { - static dataGlobal = new Map(); - static dataJapan = new Map(); - - constructor (data) { - super(); - - this.id = data.id; - - this.tag = data.tag; - - this.stageRewardId = data.stageRewardId; - - this.dropAmount = data.dropAmount; - - this.dropChance = data.dropChance; - } - - static get (identifier, region) { - if (identifier instanceof Drops) { - return identifier; - } - else if (typeof identifier === "number") { - const data = region === "global" ? Drops.dataGlobal : Drops.dataJapan; - const values = [...data.values()]; - const dropData = values.filter(i => i.stageRewardId === identifier); - return dropData; - } - else { - console.error(chalk `{red Invalid identifier for Drops.get(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static getDropbyStage (identifier) { - if (identifier instanceof Drops) { - return identifier; - } - else if (typeof identifier === "number") { - const values = [...Drops.data.values()]; - const dropData = values.filter(i => i.id === identifier); - return dropData; - } - else { - console.error(chalk `{red Invalid identifier for Drops.getDropbyStage(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static async loadData () { - const regions = ["global", "japan"]; - - for (const region of regions) { - const data = await ba.Query.collection(`${region}.DropDataMain`).find({}).toArray(); - if (data.length === 0) { - throw new Error(`No drop data found for region ${region}!`); - } - - for (const drop of data) { - const dropData = new Drops(drop); - if (region === "global") { - Drops.dataGlobal.set(Symbol(dropData.id), dropData); - } - else if (region === "japan") { - Drops.dataJapan.set(Symbol(dropData.id), dropData); - } - } - } - } - - - static destroy () { - Drops.data.clear(); - } -}; diff --git a/modules/classes/equipment.js b/modules/classes/equipment.js deleted file mode 100644 index 3dacc0a..0000000 --- a/modules/classes/equipment.js +++ /dev/null @@ -1,137 +0,0 @@ -const chalk = require("chalk"); - -module.exports = class Equipment extends require("./template") { - static dataGlobal = new Map(); - static dataJapan = new Map(); - - constructor (data) { - super(); - - this.id = data.id; - - this.localizeId = data.localizeId; - - this.recipeId = data.recipeId; - - this.category = data.category; - - this.rarity = data.rarity; - - this.maxLevel = data.maxLevel; - - this.tier = data.tier; - - this.tags = data.tags; - } - - static get (identifier, region) { - if (identifier instanceof Equipment) { - return identifier; - } - else if (typeof identifier === "number") { - const data = region === "global" ? Equipment.dataGlobal : Equipment.dataJapan; - const values = [...data.values()]; - const equipmentData = values.filter(i => i.id === identifier); - if (equipmentData.length === 0) { - return null; - } - - return equipmentData[0]; - } - else { - console.error(chalk `{red Invalid identifier for Equipment.get(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static getDatabyTier (identifier) { - if (identifier instanceof Equipment) { - return identifier; - } - else if (typeof identifier === "string") { - const tierTypes = { - t1: 1, - t2: 2, - t3: 3, - t4: 4, - t5: 5, - t6: 6, - t7: 7, - t8: 8, - t9: 9 - }; - - const [tier, name] = identifier.split(" "); - const values = [...Equipment.dataGlobal.values()]; - if (tierTypes[tier] === 1) { - return this.parseEquipmentData(values.find(i => i.tier === tierTypes[tier] - && Equipment.normalizeName(i.category) === Equipment.normalizeName(name)) - ); - } - else { - const tierData = values.find(i => i.tier === tierTypes[tier] - && Equipment.normalizeName(i.category) === Equipment.normalizeName(name) - ); - - if (typeof tierData === "undefined") { - return null; - } - - const recipeId = Number(`10${tierData.id}`); - return values.find(i => i.id === recipeId); - } - } - else { - console.error(chalk `{red Invalid identifier for Equipment.get(). Expected string!}`, { - identifier, - type: typeof identifier - }); - } - } - - static async loadData () { - const regions = ["global", "japan"]; - - for (const region of regions) { - const data = await ba.Query.collection(`${region}.EquipmentData`).find({}).toArray(); - if (data.length === 0) { - throw new Error(`No equipment data found for region ${region}`); - } - - for (const equipment of data) { - const equipmentData = new Equipment(equipment); - if (region === "global") { - Equipment.dataGlobal.set(Symbol(equipmentData.id), equipmentData); - } - else if (region === "japan") { - Equipment.dataJapan.set(Symbol(equipmentData.id), equipmentData); - } - } - } - } - - static async parseEquipmentData (data) { - const equipData = await ba.Utils.getEquipmentData(data.id); - return { - id: data.id, - name: equipData.name, - description: equipData.description, - category: data.category, - rarity: data.rarity, - maxLevel: data.maxLevel, - recipeId: data.recipeId, - tier: data.tier, - tags: data.tags - }; - } - - static destroy () { - Equipment.data.clear(); - } - - static normalizeName (name) { - return name.toLowerCase(); - } -}; diff --git a/modules/classes/skill.js b/modules/classes/skill.js deleted file mode 100644 index 165d7ab..0000000 --- a/modules/classes/skill.js +++ /dev/null @@ -1,66 +0,0 @@ -const chalk = require("chalk"); - -module.exports = class Skill extends require("./template") { - static dataGlobal = new Map(); - static dataJapan = new Map(); - - constructor (data) { - super(); - - this.id = data.id; - - this.skillEx = data.skillEx; - - this.normal = data.normal; - - this.passive = data.passive; - - this.sub = data.sub; - } - - static get (identifier, region) { - if (identifier instanceof Skill) { - return identifier; - } - else if (typeof identifier === "number") { - const data = region === "global" ? Skill.dataGlobal : Skill.dataJapan; - const values = [...data.values()]; - return values.find(i => i.id === identifier); - } - else { - console.error(chalk `{red Invalid identifier for Skill.get(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static async loadData () { - const regions = ["global", "japan"]; - - for (const region of regions) { - const data = await ba.Query.collection(`${region}.SkillListData`).find({}).toArray(); - if (data.length === 0) { - throw new Error(`No skill data found for region ${region}!`); - } - - for (const skill of data) { - const skillData = new Skill(skill); - if (region === "global") { - Skill.dataGlobal.set(Symbol(skillData.id), skillData); - } - else if (region === "japan") { - Skill.dataJapan.set(Symbol(skillData.id), skillData); - } - } - } - } - - static destroy () { - Skill.data.clear(); - } - - static normalizeName (name) { - return name.toLowerCase(); - } -}; diff --git a/modules/classes/stage.js b/modules/classes/stage.js deleted file mode 100644 index efa4758..0000000 --- a/modules/classes/stage.js +++ /dev/null @@ -1,121 +0,0 @@ -const chalk = require("chalk"); - -module.exports = class Stage extends require("./template") { - static dataGlobal = new Map(); - static dataJapan = new Map(); - - constructor (data) { - super(); - - this.id = data.id; - - this.minRank = data.minRank; - - this.staminaCost = data.staminaCost; - - this.battleDuration = data.battleDuration; - - this.maxTurn = data.maxTurn; - - this.stageInfo = data.stageInfo; - - this.objective = data.objective ?? []; - - this.stageData = data.stageData ?? {}; - } - - static get (identifier, region) { - if (identifier instanceof Stage) { - return identifier; - } - else if (typeof identifier === "number") { - return Stage.data.get(`${region}-${identifier}`); - } - else { - console.error(chalk `{red Invalid identifier for Stage.get(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static getStagebyId (identifier, region = "global") { - if (identifier instanceof Stage) { - return identifier; - } - else if (typeof identifier === "number") { - return Stage.data.get(`${region}-${identifier}`); - } - else { - console.error(chalk `{red Invalid identifier for Stage.getStagebyId(). Expected number!}`, { - identifier, - type: typeof identifier - }); - } - } - - static async raid (region) { - const stages = { - current: [], - upcoming: [], - ended: [] - }; - - const raidData = await ba.Query.collection(`${region}.RaidData`).find({}).toArray(); - for (const raid of raidData) { - const startAt = raid.startAt; - const endAt = raid.endAt; - const now = new Date(); - - if (now > startAt && now < endAt) { - stages.current.push({ - seasonId: raid.id, - bossName: raid.boss, - startAt: raid.startAt, - settleAt: raid.settleAt, - endAt: raid.endAt - }); - } - else if (now < startAt) { - stages.upcoming.push({ - seasonId: raid.id, - bossName: raid.boss, - startAt: raid.startAt, - settleAt: raid.settleAt, - endAt: raid.endAt - }); - } - else if (now > endAt) { - stages.ended.push({ - seasonId: raid.id, - bossName: raid.boss, - startAt: raid.startAt, - settleAt: raid.settleAt, - endAt: raid.endAt - }); - } - } - - return stages; - } - - static async loadData () { - const regions = ["global", "japan"]; - - for (const region of regions) { - const data = await ba.Query.collection(`${region}.StageDataMain`).find({}).toArray(); - if (data.length === 0) { - throw new Error(`No stage data found for region ${region}!`); - } - - for (const stage of data) { - const stageData = new Stage(stage); - Stage.data.set(`${region}-${stageData.id}`, stageData); - } - } - } - - static destroy () { - Stage.data.clear(); - } -}; diff --git a/modules/singleton/template.js b/modules/singleton/template.js deleted file mode 100644 index ceae460..0000000 --- a/modules/singleton/template.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = class SingletonTemplate { - /** - * Cleans up the module. - * All submodules must implement this method. - * @abstract - */ - destroy () { - throw new Error("SingletonTemplate::destroy() is not implemented"); - } - - /** - * Construct the singleton instance. - * @return {Promise} - */ - static singleton () { - throw new Error("SingletonTemplate::singleton() is not implemented"); - } - - /** - * File name of the module. - * All submodules must implement this method. - */ - get modulePath () { - throw new Error("SingletonTemplate::modulePath() is not implemented"); - } -}; diff --git a/modules/singleton/utils.js b/modules/singleton/utils.js deleted file mode 100644 index 1e32b62..0000000 --- a/modules/singleton/utils.js +++ /dev/null @@ -1,253 +0,0 @@ -module.exports = class Utils extends require("./template") { - #localizeEtc = {}; - #statData = {}; - #characterLocalize = {}; - #skillLocalize = {}; - #skillList = {}; - #bannerData = {}; - - static terrainTypes = { - Urban: { - SS: { - DamageDealt: "130%(1.3x)", - ShieldBlockRate: "75%" - }, - S: { - DamageDealt: "120%(1.2x)", - ShieldBlockRate: "60%" - }, - A: { - DamageDealt: "110%(1.1x)", - ShieldBlockRate: "45%" - }, - B: { - DamageDealt: "100%(1x)", - ShieldBlockRate: "30%" - }, - C: { - DamageDealt: "90%(0.9x)", - ShieldBlockRate: "15%" - }, - D: { - DamageDealt: "80%(0.8x)", - ShieldBlockRate: "0%" - } - }, - Desert: { - SS: { - DamageDealt: "130%(1.3x)", - ShieldBlockRate: "75%" - }, - S: { - DamageDealt: "120%(1.2x)", - ShieldBlockRate: "60%" - }, - A: { - DamageDealt: "110%(1.1x)", - ShieldBlockRate: "45%" - }, - B: { - DamageDealt: "100%(1x)", - ShieldBlockRate: "30%" - }, - C: { - DamageDealt: "90%(0.9x)", - ShieldBlockRate: "15%" - }, - D: { - DamageDealt: "80%(0.8x)", - ShieldBlockRate: "0%" - } - }, - Indoor: { - SS: { - DamageDealt: "130%(1.3x)", - ShieldBlockRate: "75%" - }, - S: { - DamageDealt: "120%(1.2x)", - ShieldBlockRate: "60%" - }, - A: { - DamageDealt: "110%(1.1x)", - ShieldBlockRate: "45%" - }, - B: { - DamageDealt: "100%(1x)", - ShieldBlockRate: "30%" - }, - C: { - DamageDealt: "90%(0.9x)", - ShieldBlockRate: "15%" - }, - D: { - DamageDealt: "80%(0.8x)", - ShieldBlockRate: "0%" - } - } - }; - - /** - * @inheritdoc - * @returns {Utils} - */ - static singleton () { - if (!Utils.module) { - Utils.module = new Utils(); - } - - return Utils.module; - } - - isValidRegion (region) { - return ["global", "japan"].includes(region); - } - - async getEquipmentData (id, region = "global") { - if (!this.#localizeEtc[region]) { - this.#localizeEtc[region] = await ba.Query.collection(`${region}.LocalizeEtc`).find({}).toArray(); - } - - const equipmentData = ba.Equipment.get(id); - if (!equipmentData) { - return null; - } - - const equipmentLoc = this.#localizeEtc[region].find(i => i.key === equipmentData.localizeId); - const equipment = { - id: equipmentData.id, - name: equipmentLoc.name, - description: equipmentLoc.description - }; - - return equipment; - } - - async getCharacterName (id, region) { - if (!this.#localizeEtc[region]) { - this.#localizeEtc[region] = await ba.Query.collection(`${region}.LocalizeEtc`).find({}).toArray(); - } - - return this.#localizeEtc[region].find(i => i.key === id); - } - - async getCharacterData (id, region) { - if (!this.#characterLocalize[region]) { - this.#characterLocalize[region] = await ba.Query.collection(`${region}.CharacterLocalize`).find({}).toArray(); - } - - if (!this.#statData[region]) { - this.#statData[region] = await ba.Query.collection(`${region}.CharacterStat`).find({}).toArray(); - } - - const info = this.#characterLocalize[region].find(i => i.id === id); - const statData = this.#statData[region].find(i => i.id === id); - - if (!info || !statData) { - return null; - } - - if (statData) { - delete statData._id; // not sorry for this xd - } - - return { - info, - stat: statData, - topology: { - urban: Utils.terrainTypes.Urban[statData.streetMood], - outdoor: Utils.terrainTypes.Desert[statData.outdoorMood], - indoor: Utils.terrainTypes.Indoor[statData.indoorMood] - } - }; - } - - async getSkillInfo (id, region) { - if (!this.#skillLocalize[region]) { - this.#skillLocalize[region] = await ba.Query.collection(`${region}.SkillLocalize`).find({}).toArray(); - } - - const skill = this.#skillLocalize[region].find(i => i.id === id); - if (!skill) { - return null; - } - - return { - id: skill.id, - name: skill.name, - description: skill.description - }; - } - - async getSkillData (name, region) { - if (!this.#skillList[region]) { - this.#skillList[region] = await ba.Query.collection(`${region}.SkillListTable`).find({}).toArray(); - } - - const stuff = []; - const skills = this.#skillList[region].filter(i => i.groupId === name); - if (skills.length !== 0) { - for (const skill of skills) { - const skillInfo = await this.getSkillInfo(skill.localizeSkillId, region); - if (skillInfo) { - stuff.push({ - level: skill.level, - name: skillInfo.name, - description: skillInfo.description, - skillCost: skill.skillCost, - bulletType: skill.bulletType - }); - } - } - } - - return stuff; - } - - async getBannerData (region) { - if (!this.#bannerData[region]) { - this.#bannerData[region] = await ba.Query.collection(`${region}.GachaData`).find({}).toArray(); - } - - const current = []; - const upcoming = []; - const ended = []; - - for (const banner of this.#bannerData[region]) { - const now = Date.now(); - const startAt = banner.startAt; - const endAt = banner.endAt; - - if (now >= startAt && now <= endAt) { - current.push(Utils.parseBannerData(banner, region)); - } - else if (now < startAt) { - upcoming.push(Utils.parseBannerData(banner, region)); - } - else if (now > endAt) { - ended.push(Utils.parseBannerData(banner, region)); - } - } - - return { - current, - upcoming, - ended - }; - } - - static parseBannerData (data, region) { - const rateups = []; - for (const rateup of data.rateup) { - const charData = ba.Character.get(rateup, region); - rateups.push(charData.name); - } - - return { - gachaType: data.type, - startAt: data.startAt, - endAt: data.endAt, - rateups - }; - } -}; diff --git a/package.json b/package.json index 0f356c1..ca756d1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "BlueArchiveAPI", + "name": "blue-archive-api", "version": "1.3.0", "description": "API that serves data for the game Blue Archive", "main": "app.js", @@ -16,20 +16,21 @@ }, "homepage": "https://api.ennead.cc/buruaka", "author": "torikushiii", - "license": "OSL-3.0", + "license": "AGPL-3.0", "dependencies": { - "@fastify/cors": "^8.1.0", - "@fastify/sensible": "^5.1.0", - "fastify": "^4.3.0", - "mongodb": "^4.8.1", - "pino": "^8.4.0", - "pino-pretty": "^8.1.0" + "@fastify/cors": "^8.3.0", + "@fastify/sensible": "^5.2.0", + "debug": "^4.3.4", + "fastify": "^4.20.0", + "mongodb": "^5.7.0", + "pino": "^8.14.1", + "pino-pretty": "^10.1.0" }, "devDependencies": { - "@babel/core": "^7.18.10", - "@babel/eslint-parser": "^7.18.9", - "eslint": "^8.21.0", - "eslint-plugin-unicorn": "^43.0.2" + "@babel/core": "^7.22.9", + "@babel/eslint-parser": "^7.22.9", + "eslint": "^8.45.0", + "eslint-plugin-unicorn": "^48.0.0" }, "types": "./@types/index.d.ts" } \ No newline at end of file diff --git a/routes/banner.js b/routes/banner.js index a61c032..a6f3e86 100644 --- a/routes/banner.js +++ b/routes/banner.js @@ -3,16 +3,13 @@ module.exports = function (fastify, opts, done) { Router.get("/", async (req, res) => { const region = req.query.region || "global"; - if (!ba.Utils.isValidRegion(region)) { + if (!["global", "japan"].includes(region)) { return res.badRequest("Invalid region"); } - - const data = await ba.Utils.getBannerData(region); - if (data.current.length === 0 && data.ended.length === 0) { - return res.notFound("No banners found"); - } - - res.send(data); + + const bannerData = await ba.Utils.getBannerData(region); + + return res.send(bannerData); }); done(); diff --git a/routes/character.js b/routes/character.js index 08a9d8c..cbb9d11 100644 --- a/routes/character.js +++ b/routes/character.js @@ -1,64 +1,89 @@ module.exports = function (fastify, opts, done) { const Router = fastify; - + Router.get("/", async (req, res) => { const region = req.query.region || "global"; - if (!ba.Utils.isValidRegion(region)) { + if (!["global", "japan"].includes(region)) { return res.badRequest("Invalid region"); } - const data = await ba.Character.getAll(region); + const data = await ba.Character.get(null, { getAll: true, region }); if (!data) { - return res.notFound("No data found (?)"); + return res.notFound(); + } + + if (req.query.star) { + const star = Number(req.query.star); + if (isNaN(star)) { + return res.badRequest("Invalid character rarity"); + } + + const filteredData = data.filter(i => i.baseStar === star); + if (filteredData.length === 0) { + return res.notFound(); + } + + return res.send(filteredData); } - return res.send({ data }); + return res.send(data); }); Router.get("/query", async (req, res) => { - if (Object.keys(req.query).length === 0) { - res.badRequest("No query parameters are given!"); - - return; + const region = req.query.region || "global"; + if (!["global", "japan"].includes(region)) { + return res.badRequest("Invalid region"); } - - const data = ba.Character.getCharacterbyQuery(req.query); - if (data) { - res.send(data); + + for (const key in req.query) { + req.query[key] = req.query[key].toLowerCase(); } - else { - res.notFound("No character found with that matching query!"); + + const data = await ba.Character.getCharacterByQuery(req.query, { region }); + if (!data) { + return res.notFound(); } + + return res.send(data); }); - Router.get("/:id", async (req, res) => { + Router.get("/:character", async (req, res) => { const region = req.query.region || "global"; - if (!ba.Utils.isValidRegion(region)) { + if (!["global", "japan"].includes(region)) { return res.badRequest("Invalid region"); } if (req.query.id) { - const isId = Boolean(req.query.id === "true"); - if (isId) { - const data = ba.Character.get(Number(req.params.id), region); - if (data) { - return res.send(data); + const characterId = Boolean(req.query.id === "true"); + if (characterId) { + const data = await ba.Character.get(Number(req.params.character), { region }); + if (!data) { + return res.notFound(); } - } - } - else { - const data = ba.Character.get(req.params.id, region); - if (data) { - const parsedCharacter = await ba.Character.parseCharacterData(data, { region }); + + const parsedCharacter = await ba.Character.buildCharacterObject(data, { region }); if (!parsedCharacter) { - return res.notFound("No character with such ID/name was found!"); + return res.notFound("No character data found with that identifier"); } - + return res.send(parsedCharacter); } } + else { + const data = await ba.Character.get(req.params.character, { region }); + if (!data) { + return res.notFound(); + } + + const parsedCharacter = await ba.Character.buildCharacterObject(data, { region }); + if (!parsedCharacter) { + return res.notFound("No character data found with that identifier"); + } + + return res.send(parsedCharacter); + } - res.notFound("No character with such ID/name was found!"); + return res.notFound(); }); done(); diff --git a/routes/equipment.js b/routes/equipment.js deleted file mode 100644 index 953ea84..0000000 --- a/routes/equipment.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = function (fastify, opts, done) { - const Router = fastify; - - Router.get("/", (req, res) => { - res.badRequest("No equipment found"); - }); - - Router.get("/:id", async (req, res) => { - if (req.query.id) { - const isId = Boolean(req.query.id === "true"); - if (isId) { - const data = ba.Equipment.get(Number(req.params.id)); - if (data) { - const droplist = []; - - const drops = ba.Drops.get(data.id); - for (const drop of drops) { - const stageData = ba.Stage.getStagebyId(drop.id); - droplist.push({ - stageName: stageData.stageInfo.fullName, - dropAmount: drop.dropAmount, - dropChance: drop.dropChance / 100 - }); - } - - return res.send({ data, drops: droplist }); - } - } - } - else { - const droplist = []; - const item = req.params.id.toLowerCase(); - - const tierRegex = /^t[1-9] (.*)$/; - const tierMatch = item.match(tierRegex); - if (tierMatch) { - const equipData = await ba.Equipment.getDatabyTier(item); - if (equipData) { - const drops = ba.Drops.get(equipData.id); - if (drops) { - for (const drop of drops) { - const stageData = ba.Stage.getStagebyId(drop.id); - droplist.push({ - stageName: stageData.stageInfo.fullName, - dropAmount: drop.dropAmount, - dropChance: drop.dropChance / 100 - }); - } - - return res.send({ data: equipData, drops: droplist }); - } - } - } - } - - res.notFound("No equipment exists with this ID / Name!"); - }); - - done(); -}; diff --git a/routes/raid.js b/routes/raid.js index 4a233f4..1498835 100644 --- a/routes/raid.js +++ b/routes/raid.js @@ -3,17 +3,56 @@ module.exports = function (fastify, opts, done) { Router.get("/", async (req, res) => { const region = req.query.region || "global"; - if (!ba.Utils.isValidRegion(region)) { + if (!["global", "japan"].includes(region)) { return res.badRequest("Invalid region"); } - - const data = await ba.Stage.raid(region); - if (data.upcoming.length !== 0 || data.current.length !== 0 || data.ended.length !== 0) { - res.send(data); + + const raidData = await ba.Query.collection(`${region}.RaidData`).find({}).toArray(); + if (!raidData || raidData.length === 0) { + return res.notFound(); } - else { - res.notFound("No raid data found"); + + const stages = { + current: [], + upcoming: [], + ended: [] + }; + + for (const raid of raidData) { + const startAt = raid.startAt; + const endAt = raid.endAt; + const now = new Date(); + + if (now > startAt && now < endAt) { + stages.current.push({ + seasonId: raid.id, + bossName: raid.boss, + startAt: raid.startAt, + settleAt: raid.settleAt, + endAt: raid.endAt + }); + } + else if (now < startAt) { + stages.upcoming.push({ + seasonId: raid.id, + bossName: raid.boss, + startAt: raid.startAt, + settleAt: raid.settleAt, + endAt: raid.endAt + }); + } + else if (now > endAt) { + stages.ended.push({ + seasonId: raid.id, + bossName: raid.boss, + startAt: raid.startAt, + settleAt: raid.settleAt, + endAt: raid.endAt + }); + } } + + return res.send(stages); }); done(); diff --git a/routes/stage.js b/routes/stage.js deleted file mode 100644 index 87328a9..0000000 --- a/routes/stage.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = function (fastify, opts, done) { - const Router = fastify; - - Router.get("/", (req, res) => { - res.send({ data: "No stage ID is given!" }); - }); - - Router.get("/:id", async (req, res) => { - const isNumber = Number.isInteger(Number(req.params.id)); - if (!isNumber) { - res.badRequest("Stage ID must be an number!"); - - return; - } - - const stageId = Number(req.params.id); - const data = ba.Stage.get(stageId); - if (data) { - const drops = []; - const dropData = ba.Drops.getDropbyStage(stageId); - if (dropData.length !== 0) { - for (const drop of dropData) { - const loc = await ba.Utils.getEquipmentData(drop.stageRewardId); - if (loc) { - drops.push({ - id: drop.id, - ...loc, - droprate: drop.dropChance / 100 - }); - } - } - } - - res.send({ data, drops }); - } - else { - res.notFound("No stage found with this ID!"); - - return; - } - }); - - done(); -};