├── .gitattributes ├── .github └── dependabot.yml ├── .gitignore ├── README.md ├── api ├── [command].js ├── tcgdeckshare │ ├── decode.mjs │ └── encode.mjs ├── v4 │ └── [command].js └── v5 │ └── [command].js ├── main.js ├── package-lock.json └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for npm 4 | - package-ecosystem: "npm" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | # FuseBox cache 76 | .fusebox/ 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # genshin-db-api 2 | 3 | Web API for [genshin-db](https://www.npmjs.com/package/genshin-db) using Vercel serverless function. 4 | 5 | You can access v4 style data by switch `/v5` with `/v4`. 6 | 7 | ## [folder]?query=[query] 8 | https://genshin-db-api.vercel.app/api/v5/characters?query=hutao 9 | Returns the search result for the specified folder and query. 10 | You'll have to retrieve stats for characters/talents/weapons using the stats API. 11 | 12 | You may include standard genshindb options as url query parameters (case-sensitive): 13 | - dumpResult // The query result will return an object with the properties: { query, folder, match, matchtype, options, filename, result }. 14 | - matchNames // Allows the matching of names. 15 | - matchAltNames // Allows the matching of alternate or custom names. 16 | - matchAliases // Allows the matching of aliases. These are searchable fields that returns the data object the query matched in. 17 | - matchCategories // Allows the matching of categories. If true, then returns an array if it matches. 18 | - verboseCategories // Used if a category is matched. If true, then replaces each string name in the array with the data object instead. 19 | - queryLanguages // Comma separated list of languages that your query will be searched in. 20 | - resultLanguage // Output language that you want your results to be in. 21 | 22 | Examples: 23 | https://genshin-db-api.vercel.app/api/v5/characters?query=hutao 24 | https://genshin-db-api.vercel.app/api/v5/characters?query=胡桃&queryLanguages=chinese&resultLanguage=chinese 25 | https://genshin-db-api.vercel.app/api/v5/talents?query=slime&matchCategories=true&queryLanguages=english,jap 26 | https://genshin-db-api.vercel.app/api/v5/weapons?query=skywardharp&dumpResult=true&resultLanguage=korean 27 | 28 | ### [folder]?query=names&matchCategories=true 29 | https://genshin-db-api.vercel.app/api/v5/characters?query=names&matchCategories=true 30 | Returns a list of names for the specified folder. 31 | 32 | You may also include the option verboseCategories to get a list of data objects instead of a list of names. 33 | 34 | ## config 35 | https://genshin-db-api.vercel.app/api/v5/config 36 | Returns the following: 37 | - list of folders 38 | - list of languages 39 | - the default options used by the api 40 | - list of category values for each folder 41 | 42 | You can use `resultLanguage` to get category values for other languages. 43 | Example: 44 | https://genshin-db-api.vercel.app/api/v5/config?resultLanguage=spanish 45 | 46 | ## folders 47 | https://genshin-db-api.vercel.app/api/v5/folders 48 | Returns the list of folders. 49 | 50 | ## languages 51 | https://genshin-db-api.vercel.app/api/v5/languages 52 | Returns the list of languages. 53 | 54 | ## categories 55 | https://genshin-db-api.vercel.app/api/v5/categories 56 | Returns the category values for every folder. 57 | 58 | You can use `resultLanguage` to get category values for other languages. 59 | Example: 60 | https://genshin-db-api.vercel.app/api/v5/categories?resultLanguage=spanish 61 | 62 | ## stats?folder=[folder]&query=[query] 63 | https://genshin-db-api.vercel.app/api/v5/stats?folder=characters&query=hutao 64 | Returns the stats for every level for the specified folder and query as a JSON map. 65 | Ascended stats are mapped with a '+' like '80+'. 66 | Only for `characters` and `weapons` folder. 67 | 68 | You may include standard genshindb options as url query parameters (case-sensitive). 69 | Adding `dumpResult=true` will allow you to get the data object of the character/weapon being searched. 70 | You may include `level` as a query parameter to get the stats for a specific level. 71 | Examples: 72 | https://genshin-db-api.vercel.app/api/v5/stats?folder=characters&query=胡桃&queryLanguages=chinese 73 | https://genshin-db-api.vercel.app/api/v5/stats?folder=weapons&query=jadespear&level=90 74 | https://genshin-db-api.vercel.app/api/v5/stats?folder=characters&query=hutao&dumpResult=true 75 | https://genshin-db-api.vercel.app/api/v5/stats?folder=characters&query=ganyu&level=60+ 76 | 77 | ## tcgdeckshare/decode?code=[deckcode] 78 | https://genshin-db-api.vercel.app/api/tcgdeckshare/decode?code=A0Bw8TQPARBw8pcPCSBw9cIPDFAg9sgQDAGAAMkQDCGQCdkQDaGQC+MQDrEwDOQQDsAA 79 | Converts a tcg deck share code into an array of card share ids. 80 | 81 | Returns an object with the properties: 82 | - `deckcode` 83 | 84 | ## tcgdeckshare/encode?deck=[cardids] 85 | https://genshin-db-api.vercel.app/api/tcgdeckshare/encode?deck=55,52,23,151,151,194,194,200,200,201,201,217,217,227,227,228,228,241,241,242,242,245,245,246,256,256,258,265,266,267,267,268,268 86 | Converts a comma-separated array of card share ids into a tcg deck share code. 87 | 88 | You can an `offset` query parameter to generate a different deck share code for the same deck. This is useful for the extremely rare case where the deck share code is invalid because it contains a bad word. 89 | Example: 90 | https://genshin-db-api.vercel.app/api/tcgdeckshare/encode?offset=1&deck=55,52,23,151,151,194,194,200,200,201,201,217,217,227,227,228,228,241,241,242,242,245,245,246,256,256,258,265,266,267,267,268,268 91 | 92 | Returns a string. 93 | 94 | ## tcgdeckshare/getdata?code=[deckcode]&deck=[cardids] 95 | https://genshin-db-api.vercel.app/api/tcgdeckshare/decode?code=A0Bw8TQPARBw8pcPCSBw9cIPDFAg9sgQDAGAAMkQDCGQCdkQDaGQC+MQDrEwDOQQDsAA 96 | 97 | -------------------------------------------------------------------------------- /api/[command].js: -------------------------------------------------------------------------------- 1 | let { getData } = require('../main.js'); 2 | 3 | // `/api/[command] 4 | export default function fetchUser(req, res) { 5 | getData(req, res, 4); 6 | } -------------------------------------------------------------------------------- /api/tcgdeckshare/decode.mjs: -------------------------------------------------------------------------------- 1 | import convertXtoYbitarray from "convert-x-to-y-bit-array"; 2 | import { getData, enableCors } from '../../main.js'; 3 | 4 | // `/api/tcgdeckshare/decode 5 | export default function fetchUser(req, res) { 6 | if (!enableCors(req, res)) return; 7 | if (req.query.code === undefined) return; 8 | 9 | const code = decodeURIComponent(req.query.code.replaceAll(' ', '+')); 10 | 11 | const output = decode(code); 12 | 13 | return res.json({ deckcode: code, offset: output.lastByte, cardshareids: output.cards }); 14 | } 15 | 16 | // adapted from https://github.com/gjfLeo/summoners-summit/blob/main/utils/decks/share-code.ts 17 | function decode(str) { 18 | const byteArray = Array.from(atob(str), c => c.codePointAt(0)); 19 | 20 | const lastByte = byteArray.pop(); 21 | 22 | const reordered = [ 23 | ...Array.from({ length: 25 }).map((_, i) => (byteArray[2 * i] - lastByte) & 255), 24 | ...Array.from({ length: 25 }).map((_, i) => (byteArray[2 * i + 1] - lastByte) & 255), 25 | 0, 26 | ]; 27 | 28 | const output = convertXtoYbitarray(8, 12, reordered); 29 | 30 | output.pop(); 31 | 32 | return { cards: output, lastByte: lastByte }; 33 | } -------------------------------------------------------------------------------- /api/tcgdeckshare/encode.mjs: -------------------------------------------------------------------------------- 1 | import convertXtoYbitarray from "convert-x-to-y-bit-array"; 2 | import { getData, enableCors } from '../../main.js'; 3 | 4 | // `/api/tcgdeckshare/encode 5 | export default function fetchUser(req, res) { 6 | if (!enableCors(req, res)) return; 7 | if (req.query.deck === undefined) return; 8 | 9 | const deck = req.query.deck.split(',').map(id => parseInt(id)); 10 | const offset = parseInt(req.query.offset) || 0; 11 | 12 | const output = encode(deck, offset); 13 | 14 | return res.json({ deckcode: output, offset: offset, cardshareids: deck }); 15 | } 16 | 17 | // adapted from https://github.com/gjfLeo/summoners-summit/blob/main/utils/decks/share-code.ts 18 | function encode(deck, offset=0) { 19 | deck.push(0); 20 | 21 | const byteArray = convertXtoYbitarray(12, 8, deck); 22 | 23 | const lastByte = offset; 24 | 25 | const original = Array.from({ length: 25 }) 26 | .fill(0) 27 | .flatMap((_, i) => [(byteArray[i] + lastByte) & 255, (byteArray[i + 25] + lastByte) & 255]); 28 | 29 | const shareCode = btoa(String.fromCodePoint(...original, lastByte)); 30 | 31 | return shareCode; 32 | } -------------------------------------------------------------------------------- /api/v4/[command].js: -------------------------------------------------------------------------------- 1 | let { getData } = require('../../main.js'); 2 | 3 | // `/api/[command] 4 | export default function fetchUser(req, res) { 5 | getData(req, res, 4); 6 | } -------------------------------------------------------------------------------- /api/v5/[command].js: -------------------------------------------------------------------------------- 1 | let { getData } = require('../../main.js'); 2 | 3 | // `/api/[command] 4 | export default function fetchUser(req, res) { 5 | getData(req, res, 5); 6 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | let genshindb = require('genshin-db'); 2 | let genshindbtcg = require('@genshin-db/tcg'); 3 | const { Logtail } = require("@logtail/node"); 4 | 5 | let logtail; 6 | try { 7 | logtail = new Logtail(process.env.LOGTAIL_SECRET); 8 | } catch(e) { 9 | console.log(e); 10 | } 11 | 12 | /* 13 | dumpResult: false, // The query result will return an object with the properties: { query, folder, match, matchtype, options, filename, result }. 14 | matchNames: true, // Allows the matching of names. 15 | matchAltNames: true, // Allows the matching of alternate or custom names. 16 | matchAliases: false, // Allows the matching of aliases. These are searchable fields that returns the data object the query matched in. 17 | matchCategories: false, // Allows the matching of categories. If true, then returns an array if it matches. 18 | verboseCategories: false, // Used if a category is matched. If true, then replaces each string name in the array with the data object instead. 19 | queryLanguages: ["English"], // Array of languages that your query will be searched in. 20 | resultLanguage: "English" // Output language that you want your results to be in. 21 | v4Props: false, // Adds genshin-db v4 data properties to the return result of the API. 22 | v4PropsOnly: false, // Same as v4Props but removes v5 data properties. 23 | */ 24 | function parseOptions(input) { 25 | let opts = {}; 26 | 27 | ['dumpResult', 'matchNames', 'matchAltNames', 'matchAliases', 'matchCategories', 'verboseCategories', 'v4Props', 'v4PropsOnly'].forEach(prop => { 28 | if(input[prop] !== undefined) { 29 | opts[prop] = parseBoolean(input[prop]); 30 | } 31 | }); 32 | 33 | if(input.queryLanguages !== undefined) { 34 | opts.queryLanguages = input.queryLanguages.split(','); 35 | } 36 | 37 | if(input.resultLanguage !== undefined) { 38 | opts.resultLanguage = input.resultLanguage; 39 | } 40 | 41 | return opts; 42 | } 43 | 44 | function parseBoolean(str) { 45 | str = str.toLowerCase(); 46 | if(str === 'true') return true; 47 | else if(str === 'false') return false; 48 | else return undefined; 49 | } 50 | 51 | function log(message, data) { 52 | try { 53 | //logtail.info(message, data); 54 | } catch(e) { 55 | console.log(e); 56 | } 57 | } 58 | 59 | const foldersList = Object.keys(genshindb.Folder); 60 | const languagesList = Object.keys(genshindb.Languages); 61 | 62 | function createConfig(opts, istcg=false) { 63 | const config = {}; 64 | config.folders = foldersList; 65 | config.languages = languagesList; 66 | config.defaultOptions = genshindb.getOptions(); 67 | config.categories = {}; 68 | for (let folder of config.folders) { 69 | config.categories[folder] = {}; 70 | try { 71 | if (folder.startsWith('tcg')) 72 | config.categories[folder].names = genshindbtcg.categories('names', folder, opts); 73 | else 74 | config.categories[folder].names = genshindb.categories('names', folder, opts); 75 | 76 | for (let category of config.categories[folder].names) { 77 | if (folder.startsWith('tcg')) 78 | config.categories[folder][category] = genshindbtcg.categories(category, folder, opts); 79 | else 80 | config.categories[folder][category] = genshindb.categories(category, folder, opts); 81 | } 82 | } catch(e) {} 83 | } 84 | return config; 85 | } 86 | 87 | function getCategories(opts, istcg=false) { 88 | const categories = {}; 89 | for (let folder of foldersList) { 90 | categories[folder] = {}; 91 | try { 92 | if (folder.startsWith('tcg')) 93 | categories[folder].names = genshindbtcg.categories('names', folder, opts); 94 | else 95 | categories[folder].names = genshindb.categories('names', folder, opts); 96 | for (let category of categories[folder].names) { 97 | if (folder.startsWith('tcg')) 98 | categories[folder].names = genshindbtcg.categories('names', folder, opts); 99 | else 100 | categories[folder][category] = genshindb.categories(category, folder, opts); 101 | } 102 | } catch(e) {} 103 | } 104 | return categories; 105 | } 106 | 107 | function getStats(params) { 108 | let opts = parseOptions(params); 109 | const dumpStat = opts.dumpResult; 110 | opts.matchCategories = false; 111 | opts.dumpResult = true; 112 | const queryresult = genshindb[params.folder](params.query, opts); 113 | if (queryresult.result === undefined) { 114 | return undefined; 115 | } 116 | 117 | if (params.level) { 118 | let [level, ascension] = parseLevel(params.level); 119 | if (level !== undefined) { 120 | ascension = ascension.replace(/ /g, '+'); 121 | queryresult.level = ''+level+ascension; 122 | queryresult.stats = queryresult.result.stats(level, ascension); 123 | if (dumpStat) return queryresult; 124 | else return queryresult.stats; 125 | } 126 | } 127 | 128 | // no valid level provided, then return all stats in a map 129 | queryresult.stats = generateAllStats(queryresult.result.stats); 130 | if (dumpStat) return queryresult; 131 | else return queryresult.stats; 132 | } 133 | 134 | // creates a map of stats for all levels 135 | function generateAllStats(statsFunc) { 136 | const statsMap = {}; 137 | for (let tens = 0; tens <= 8; tens++) { 138 | for (let ones = 1; ones <= 10; ones++) { 139 | const level = tens*10 + ones; 140 | const stats = statsFunc(level); 141 | if (stats) statsMap[level] = stats; 142 | if (ones === 10) { 143 | const statsPlus = statsFunc(level, '+'); 144 | if (statsPlus && stats.ascension !== statsPlus.ascension) statsMap[level+'+'] = statsPlus; 145 | } 146 | } 147 | } 148 | return statsMap; 149 | } 150 | 151 | function parseLevel(level) { 152 | const regex = /(\d+)(.*)/; 153 | let m = regex.exec(level); 154 | if (m !== null) { 155 | return [m[1], m[2]]; 156 | } 157 | return [undefined, undefined]; 158 | } 159 | 160 | function getData(req, res, version) { 161 | if (!enableCors(req, res)) return; 162 | 163 | const command = req.query.command ? req.query.command.toLowerCase() : undefined; 164 | if (command === undefined) return; 165 | 166 | if (version === 4) genshindb.setOptions({ v4PropsOnly: true }); 167 | else if (version === 5) genshindb.setOptions({ v4Props: false, v4PropsOnly: false }); 168 | 169 | switch (command) { 170 | case 'default': 171 | case 'config': 172 | log('get config'); 173 | return res.json(createConfig(parseOptions(req.query))); 174 | 175 | case 'language': 176 | case 'languages': 177 | log('get languages'); 178 | return res.json(languagesList); 179 | 180 | case 'folder': 181 | case 'folders': 182 | log('get folders'); 183 | return res.json(foldersList); 184 | 185 | case 'category': 186 | case 'categories': 187 | log('get categories'); 188 | return res.json(getCategories(parseOptions(req.query))); 189 | 190 | case 'stat': 191 | case 'stats': 192 | let statfolder = req.query.folder; 193 | if (statfolder !== 'characters' && statfolder !== 'weapons') { 194 | log("invalid stats folder"); 195 | return res.status(404).send(new Error('Not a valid stat folder.')); 196 | } else { 197 | log('get stats'); 198 | let mystats = getStats(req.query); 199 | if (mystats === undefined) return res.status(404).send(new Error('No data found for query.')); 200 | else return res.json(mystats); 201 | } 202 | } 203 | 204 | if(genshindb.Folders[command]) { 205 | const istcg = command.startsWith('tcg'); 206 | let params = req.query; 207 | let opts = parseOptions(params); 208 | let userDumpResult = opts.dumpResult === true; 209 | opts.dumpResult = true; 210 | 211 | const queryresult = istcg ? genshindbtcg[command](params.query, opts) : genshindb[command](params.query, opts); 212 | queryresult.options.dumpResult = userDumpResult; 213 | log("success "+queryresult.match, { query: queryresult.query, folder: queryresult.folder, match: queryresult.match, options: queryresult.options, filename: queryresult.filename }); 214 | console.log(req.headers.referer); 215 | if(userDumpResult) { 216 | res.json(queryresult); 217 | } else { 218 | res.json(queryresult.result); 219 | } 220 | } else { 221 | log("invalid folder"); 222 | res.status(404).send(new Error('Not a valid search folder.')); 223 | } 224 | } 225 | 226 | function enableCors(req, res) { 227 | res.setHeader('Cache-Control', 's-maxage=86400'); 228 | 229 | res.setHeader('Access-Control-Allow-Credentials', true) 230 | res.setHeader('Access-Control-Allow-Origin', '*') 231 | // another common pattern 232 | // res.setHeader('Access-Control-Allow-Origin', req.headers.origin); 233 | res.setHeader('Access-Control-Allow-Methods', 'GET') 234 | res.setHeader( 235 | 'Access-Control-Allow-Headers', 236 | 'folder-type, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' 237 | ) 238 | if (req.method === 'OPTIONS') { 239 | res.status(200).end(); 240 | return false; 241 | } 242 | return true; 243 | } 244 | 245 | module.exports = { 246 | getData: getData, 247 | enableCors: enableCors 248 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genshin-db-api", 3 | "version": "0.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "genshin-db-api", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "@genshin-db/tcg": "^5.1.7", 12 | "@logtail/node": "^0.4.21", 13 | "convert-x-to-y-bit-array": "^1.0.0", 14 | "genshin-db": "^5.1.7" 15 | } 16 | }, 17 | "node_modules/@genshin-db/tcg": { 18 | "version": "5.1.7", 19 | "resolved": "https://registry.npmjs.org/@genshin-db/tcg/-/tcg-5.1.7.tgz", 20 | "integrity": "sha512-VrM2Gqo1brRzomxQouYcxfeU8gTIrexE5Pzuc9o4ixKGiOiqq3pI3gmQV/sjVaa17XNc3UsYpG6eEB7z4P6vOg==", 21 | "dependencies": { 22 | "fuzzysort": "^1.1.4", 23 | "pako": "^2.0.4" 24 | } 25 | }, 26 | "node_modules/@logtail/core": { 27 | "version": "0.4.21", 28 | "resolved": "https://registry.npmjs.org/@logtail/core/-/core-0.4.21.tgz", 29 | "integrity": "sha512-QDq194+24bwi4e+a/pxyf4X67NewhTvBmh9iwM2NhbSVSQz4Fo8xQn1Ul8zuUrXETycu/Od2D8wT2tZFNFx/7A==", 30 | "dependencies": { 31 | "@logtail/tools": "^0.4.21", 32 | "@logtail/types": "^0.4.20", 33 | "serialize-error": "^8.1.0" 34 | } 35 | }, 36 | "node_modules/@logtail/node": { 37 | "version": "0.4.21", 38 | "resolved": "https://registry.npmjs.org/@logtail/node/-/node-0.4.21.tgz", 39 | "integrity": "sha512-zpwkhJgcYaM+vsjotHRJthc0ot1vP0CAVy+fwrkL8XjfdC3NHiWb6f0agQpHlqdRX8RTsAbcYpWNXKPpFB5U9Q==", 40 | "dependencies": { 41 | "@logtail/core": "^0.4.21", 42 | "@logtail/types": "^0.4.20", 43 | "@msgpack/msgpack": "^2.5.1", 44 | "@types/stack-trace": "^0.0.29", 45 | "cross-fetch": "^3.0.4", 46 | "minimatch": "^3.0.4", 47 | "serialize-error": "^8.1.0", 48 | "stack-trace": "^0.0.10" 49 | } 50 | }, 51 | "node_modules/@logtail/tools": { 52 | "version": "0.4.21", 53 | "resolved": "https://registry.npmjs.org/@logtail/tools/-/tools-0.4.21.tgz", 54 | "integrity": "sha512-xIaolScUwJEikllopGphxBX0lVlN/rA8pLAZiNCMNJXpPbwitoFKLW3w4qRuYdKoFCCJZKwOdwEqU2Fv0i9Cuw==", 55 | "dependencies": { 56 | "@logtail/types": "^0.4.20" 57 | } 58 | }, 59 | "node_modules/@logtail/types": { 60 | "version": "0.4.20", 61 | "resolved": "https://registry.npmjs.org/@logtail/types/-/types-0.4.20.tgz", 62 | "integrity": "sha512-nYsum10eJMTo+ySBlYXvSrvgD1NDCVUeOlxLBbelq3XUmHu9L48VNR3P0BOmhLamYCTEgjatTj0PyPLfjL1W9g==" 63 | }, 64 | "node_modules/@msgpack/msgpack": { 65 | "version": "2.7.1", 66 | "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.7.1.tgz", 67 | "integrity": "sha512-ApwiSL2c9ObewdOE/sqt788P1C5lomBOHyO8nUBCr4ofErBCnYQ003NtJ8lS9OQZc11ximkbmgAZJjB8y6cCdA==", 68 | "engines": { 69 | "node": ">= 10" 70 | } 71 | }, 72 | "node_modules/@types/stack-trace": { 73 | "version": "0.0.29", 74 | "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", 75 | "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==" 76 | }, 77 | "node_modules/balanced-match": { 78 | "version": "1.0.2", 79 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 80 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 81 | }, 82 | "node_modules/brace-expansion": { 83 | "version": "1.1.11", 84 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 85 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 86 | "dependencies": { 87 | "balanced-match": "^1.0.0", 88 | "concat-map": "0.0.1" 89 | } 90 | }, 91 | "node_modules/concat-map": { 92 | "version": "0.0.1", 93 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 94 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 95 | }, 96 | "node_modules/convert-x-to-y-bit-array": { 97 | "version": "1.0.0", 98 | "resolved": "https://registry.npmjs.org/convert-x-to-y-bit-array/-/convert-x-to-y-bit-array-1.0.0.tgz", 99 | "integrity": "sha512-uiy7QTp0+zvLNLQLBNBqIeGE3/uNCwRho5iL17msounDqdPR1k3/TOFSFvQlcTR2tjgyfaFTdmuocynRG+73Nw==" 100 | }, 101 | "node_modules/cross-fetch": { 102 | "version": "3.1.8", 103 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", 104 | "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", 105 | "dependencies": { 106 | "node-fetch": "^2.6.12" 107 | } 108 | }, 109 | "node_modules/fuzzysort": { 110 | "version": "1.1.4", 111 | "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz", 112 | "integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ==" 113 | }, 114 | "node_modules/genshin-db": { 115 | "version": "5.1.7", 116 | "resolved": "https://registry.npmjs.org/genshin-db/-/genshin-db-5.1.7.tgz", 117 | "integrity": "sha512-vh0oF5R0ONZ3OE/KDYIGlfHDwAl54GdBh3FwvRvGPWGNUZ5lLZBB8U8He1wAVhUO2jgATqYemLV+hH6vlQIaiA==", 118 | "dependencies": { 119 | "fuzzysort": "^1.1.4", 120 | "pako": "^2.0.4" 121 | } 122 | }, 123 | "node_modules/minimatch": { 124 | "version": "3.1.2", 125 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 126 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 127 | "dependencies": { 128 | "brace-expansion": "^1.1.7" 129 | }, 130 | "engines": { 131 | "node": "*" 132 | } 133 | }, 134 | "node_modules/node-fetch": { 135 | "version": "2.7.0", 136 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 137 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 138 | "dependencies": { 139 | "whatwg-url": "^5.0.0" 140 | }, 141 | "engines": { 142 | "node": "4.x || >=6.0.0" 143 | }, 144 | "peerDependencies": { 145 | "encoding": "^0.1.0" 146 | }, 147 | "peerDependenciesMeta": { 148 | "encoding": { 149 | "optional": true 150 | } 151 | } 152 | }, 153 | "node_modules/pako": { 154 | "version": "2.0.4", 155 | "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", 156 | "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" 157 | }, 158 | "node_modules/serialize-error": { 159 | "version": "8.1.0", 160 | "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", 161 | "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", 162 | "dependencies": { 163 | "type-fest": "^0.20.2" 164 | }, 165 | "engines": { 166 | "node": ">=10" 167 | }, 168 | "funding": { 169 | "url": "https://github.com/sponsors/sindresorhus" 170 | } 171 | }, 172 | "node_modules/stack-trace": { 173 | "version": "0.0.10", 174 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 175 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", 176 | "engines": { 177 | "node": "*" 178 | } 179 | }, 180 | "node_modules/tr46": { 181 | "version": "0.0.3", 182 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 183 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 184 | }, 185 | "node_modules/type-fest": { 186 | "version": "0.20.2", 187 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 188 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", 189 | "engines": { 190 | "node": ">=10" 191 | }, 192 | "funding": { 193 | "url": "https://github.com/sponsors/sindresorhus" 194 | } 195 | }, 196 | "node_modules/webidl-conversions": { 197 | "version": "3.0.1", 198 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 199 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 200 | }, 201 | "node_modules/whatwg-url": { 202 | "version": "5.0.0", 203 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 204 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 205 | "dependencies": { 206 | "tr46": "~0.0.3", 207 | "webidl-conversions": "^3.0.0" 208 | } 209 | } 210 | }, 211 | "dependencies": { 212 | "@genshin-db/tcg": { 213 | "version": "5.1.7", 214 | "resolved": "https://registry.npmjs.org/@genshin-db/tcg/-/tcg-5.1.7.tgz", 215 | "integrity": "sha512-VrM2Gqo1brRzomxQouYcxfeU8gTIrexE5Pzuc9o4ixKGiOiqq3pI3gmQV/sjVaa17XNc3UsYpG6eEB7z4P6vOg==", 216 | "requires": { 217 | "fuzzysort": "^1.1.4", 218 | "pako": "^2.0.4" 219 | } 220 | }, 221 | "@logtail/core": { 222 | "version": "0.4.21", 223 | "resolved": "https://registry.npmjs.org/@logtail/core/-/core-0.4.21.tgz", 224 | "integrity": "sha512-QDq194+24bwi4e+a/pxyf4X67NewhTvBmh9iwM2NhbSVSQz4Fo8xQn1Ul8zuUrXETycu/Od2D8wT2tZFNFx/7A==", 225 | "requires": { 226 | "@logtail/tools": "^0.4.21", 227 | "@logtail/types": "^0.4.20", 228 | "serialize-error": "^8.1.0" 229 | } 230 | }, 231 | "@logtail/node": { 232 | "version": "0.4.21", 233 | "resolved": "https://registry.npmjs.org/@logtail/node/-/node-0.4.21.tgz", 234 | "integrity": "sha512-zpwkhJgcYaM+vsjotHRJthc0ot1vP0CAVy+fwrkL8XjfdC3NHiWb6f0agQpHlqdRX8RTsAbcYpWNXKPpFB5U9Q==", 235 | "requires": { 236 | "@logtail/core": "^0.4.21", 237 | "@logtail/types": "^0.4.20", 238 | "@msgpack/msgpack": "^2.5.1", 239 | "@types/stack-trace": "^0.0.29", 240 | "cross-fetch": "^3.0.4", 241 | "minimatch": "^3.0.4", 242 | "serialize-error": "^8.1.0", 243 | "stack-trace": "^0.0.10" 244 | } 245 | }, 246 | "@logtail/tools": { 247 | "version": "0.4.21", 248 | "resolved": "https://registry.npmjs.org/@logtail/tools/-/tools-0.4.21.tgz", 249 | "integrity": "sha512-xIaolScUwJEikllopGphxBX0lVlN/rA8pLAZiNCMNJXpPbwitoFKLW3w4qRuYdKoFCCJZKwOdwEqU2Fv0i9Cuw==", 250 | "requires": { 251 | "@logtail/types": "^0.4.20" 252 | } 253 | }, 254 | "@logtail/types": { 255 | "version": "0.4.20", 256 | "resolved": "https://registry.npmjs.org/@logtail/types/-/types-0.4.20.tgz", 257 | "integrity": "sha512-nYsum10eJMTo+ySBlYXvSrvgD1NDCVUeOlxLBbelq3XUmHu9L48VNR3P0BOmhLamYCTEgjatTj0PyPLfjL1W9g==" 258 | }, 259 | "@msgpack/msgpack": { 260 | "version": "2.7.1", 261 | "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.7.1.tgz", 262 | "integrity": "sha512-ApwiSL2c9ObewdOE/sqt788P1C5lomBOHyO8nUBCr4ofErBCnYQ003NtJ8lS9OQZc11ximkbmgAZJjB8y6cCdA==" 263 | }, 264 | "@types/stack-trace": { 265 | "version": "0.0.29", 266 | "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz", 267 | "integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g==" 268 | }, 269 | "balanced-match": { 270 | "version": "1.0.2", 271 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 272 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 273 | }, 274 | "brace-expansion": { 275 | "version": "1.1.11", 276 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 277 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 278 | "requires": { 279 | "balanced-match": "^1.0.0", 280 | "concat-map": "0.0.1" 281 | } 282 | }, 283 | "concat-map": { 284 | "version": "0.0.1", 285 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 286 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 287 | }, 288 | "convert-x-to-y-bit-array": { 289 | "version": "1.0.0", 290 | "resolved": "https://registry.npmjs.org/convert-x-to-y-bit-array/-/convert-x-to-y-bit-array-1.0.0.tgz", 291 | "integrity": "sha512-uiy7QTp0+zvLNLQLBNBqIeGE3/uNCwRho5iL17msounDqdPR1k3/TOFSFvQlcTR2tjgyfaFTdmuocynRG+73Nw==" 292 | }, 293 | "cross-fetch": { 294 | "version": "3.1.8", 295 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", 296 | "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", 297 | "requires": { 298 | "node-fetch": "^2.6.12" 299 | } 300 | }, 301 | "fuzzysort": { 302 | "version": "1.1.4", 303 | "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-1.1.4.tgz", 304 | "integrity": "sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ==" 305 | }, 306 | "genshin-db": { 307 | "version": "5.1.7", 308 | "resolved": "https://registry.npmjs.org/genshin-db/-/genshin-db-5.1.7.tgz", 309 | "integrity": "sha512-vh0oF5R0ONZ3OE/KDYIGlfHDwAl54GdBh3FwvRvGPWGNUZ5lLZBB8U8He1wAVhUO2jgATqYemLV+hH6vlQIaiA==", 310 | "requires": { 311 | "fuzzysort": "^1.1.4", 312 | "pako": "^2.0.4" 313 | } 314 | }, 315 | "minimatch": { 316 | "version": "3.1.2", 317 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 318 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 319 | "requires": { 320 | "brace-expansion": "^1.1.7" 321 | } 322 | }, 323 | "node-fetch": { 324 | "version": "2.7.0", 325 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 326 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 327 | "requires": { 328 | "whatwg-url": "^5.0.0" 329 | } 330 | }, 331 | "pako": { 332 | "version": "2.0.4", 333 | "resolved": "https://registry.npmjs.org/pako/-/pako-2.0.4.tgz", 334 | "integrity": "sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg==" 335 | }, 336 | "serialize-error": { 337 | "version": "8.1.0", 338 | "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", 339 | "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", 340 | "requires": { 341 | "type-fest": "^0.20.2" 342 | } 343 | }, 344 | "stack-trace": { 345 | "version": "0.0.10", 346 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 347 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 348 | }, 349 | "tr46": { 350 | "version": "0.0.3", 351 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 352 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 353 | }, 354 | "type-fest": { 355 | "version": "0.20.2", 356 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", 357 | "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" 358 | }, 359 | "webidl-conversions": { 360 | "version": "3.0.1", 361 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 362 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 363 | }, 364 | "whatwg-url": { 365 | "version": "5.0.0", 366 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 367 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 368 | "requires": { 369 | "tr46": "~0.0.3", 370 | "webidl-conversions": "^3.0.0" 371 | } 372 | } 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genshin-db-api", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "@genshin-db/tcg": "^5.1.12", 10 | "@logtail/node": "^0.4.21", 11 | "convert-x-to-y-bit-array": "^1.0.0", 12 | "genshin-db": "^5.2.1" 13 | } 14 | } 15 | --------------------------------------------------------------------------------