diff --git a/.eslintignore b/.eslintignore index e8f73169c..ee37d83e1 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,4 +4,4 @@ public/missing-locales public/images/custom public/images/uicons server/src/configs/ -packages/types/**/*.d.ts +packages/**/*.d.ts diff --git a/package.json b/package.json index 85e160f72..30813ae76 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "packages/*" ], "scripts": { + "postinstall": "yarn masterfile", "build": "vite build", "config:check": "yarn workspace @rm/config run check", "config:env": "yarn workspace @rm/config run generate", @@ -45,7 +46,7 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ - "eslint --fix" + "eslint \"**/*.{js,jsx}\" --fix" ], "**/*": [ "prettier --write --ignore-unknown" @@ -155,7 +156,7 @@ "morgan": "^1.10.0", "mysql2": "^3.4.0", "node-cache": "^5.1.2", - "node-fetch": "2", + "node-fetch": "2.6.7", "node-geocoder": "^4.2.0", "nodes2ts": "^2.0.0", "objection": "^3.0.1", @@ -186,6 +187,7 @@ "@sentry/vite-plugin": "2.10.3", "@types/dlv": "^1.1.2", "@types/node": "^18", + "@types/node-fetch": "2.6.1", "@types/react": "^18.2.20", "@types/react-dom": "^18.0.9", "@vitejs/plugin-react": "4.2.1", @@ -206,6 +208,7 @@ "prettier": "^2.8.8", "rollup-plugin-delete": "^2.0.0", "semantic-release": "^19.0.5", + "typescript": "^5.3.3", "vite": "5.0.12", "vite-plugin-checker": "0.6.0" }, diff --git a/packages/locales/lib/create.js b/packages/locales/lib/create.js index f613f652e..ff74b16d7 100644 --- a/packages/locales/lib/create.js +++ b/packages/locales/lib/create.js @@ -1,4 +1,6 @@ // @ts-check +const { default: fetch } = require('node-fetch') + const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') diff --git a/packages/locales/lib/utils.js b/packages/locales/lib/utils.js index df322f834..93e54e31d 100644 --- a/packages/locales/lib/utils.js +++ b/packages/locales/lib/utils.js @@ -1,6 +1,7 @@ // @ts-check const { promises: fs, readdirSync } = require('fs') const { resolve } = require('path') +const { default: fetch } = require('node-fetch') const { log, HELPERS } = require('@rm/logger') diff --git a/packages/locales/package.json b/packages/locales/package.json index 794e288c9..53417e4c3 100644 --- a/packages/locales/package.json +++ b/packages/locales/package.json @@ -18,6 +18,10 @@ "@rm/config": "*", "@rm/logger": "*", "dotenv": "^16.3.1", + "node-fetch": "2.6.7", "openai": "^4.24.1" + }, + "devDependencies": { + "@types/node-fetch": "2.6.1" } } diff --git a/packages/logger/lib/index.js b/packages/logger/lib/index.js index a7ef4c0eb..e360ab640 100644 --- a/packages/logger/lib/index.js +++ b/packages/logger/lib/index.js @@ -39,6 +39,7 @@ const HELPERS = /** @type {const} */ ({ fetch: chalk.hex('#880e4f')('[FETCH]'), scanner: chalk.hex('#b39ddb')('[SCANNER]'), build: chalk.hex('#ef6c00')('[BUILD]'), + ReactMap: chalk.hex('#ff3d00')('[ReactMap]'), pokemon: chalk.hex('#f44336')('[POKEMON]'), pokestops: chalk.hex('#e91e63')('[POKESTOPS]'), diff --git a/packages/masterfile/lib/index.d.ts b/packages/masterfile/lib/index.d.ts new file mode 100644 index 000000000..a30407522 --- /dev/null +++ b/packages/masterfile/lib/index.d.ts @@ -0,0 +1,85 @@ +import type { AdvCategories, Rarity } from '@rm/types' + +export interface MasterfileForm { + name: string + rarity?: string + isCostume?: boolean + category?: AdvCategories +} + +export interface MasterfilePokemon { + name: string + pokedexId: number + defaultFormId: number + types: number[] + quickMoves: number[] + chargedMoves: number[] + genId: number + forms: { + [formId: string]: MasterfileForm + } + height?: number + weight?: number + family?: number + legendary?: boolean + mythical?: boolean + ultraBeast?: boolean + rarity?: string + historic?: string + tempEvolutions?: { + [evolutionId: string]: {} + } +} + +export interface MasterfileObject { + [typeId: string]: string +} + +export interface MasterfileMove { + name: string + type: number +} + +export interface InvasionPokemon { + id: number + form: number +} + +export interface InvasionRewards { + first: InvasionPokemon[] + second: InvasionPokemon[] + third: InvasionPokemon[] +} + +export interface Invasion { + type: string + gender: number + grunt: string + firstReward: boolean + secondReward: boolean + thirdReward: boolean + encounters: InvasionRewards +} + +export interface MasterfileWeather { + name: string + types: number[] +} + +export interface Masterfile { + pokemon: Record + types: MasterfileObject + items: MasterfileObject + questRewardTypes: MasterfileObject + moves: Record + invasions: Record + weather: Record +} + +export declare function generate( + save?: boolean, + historicRarity?: Rarity, + dbRarity?: Rarity, +): Promise + +export declare function read(): Masterfile diff --git a/packages/masterfile/lib/index.js b/packages/masterfile/lib/index.js index 9f0dca0ea..518226d60 100644 --- a/packages/masterfile/lib/index.js +++ b/packages/masterfile/lib/index.js @@ -1,6 +1,7 @@ // @ts-check const fs = require('fs') const { resolve } = require('path') +const { default: fetch } = require('node-fetch') const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') @@ -20,13 +21,7 @@ Object.entries(defaultRarity).forEach(([tier, pokemon]) => { } }) -/** - * - * @param {boolean} save - * @param {import('@rm/types').Rarity} historicRarity - * @param {import('@rm/types').Rarity} dbRarity - * @returns - */ +/** @type {import('.').generate} */ const generate = async (save = false, historicRarity = {}, dbRarity = {}) => { log.info(HELPERS.masterfile, 'generating masterfile') try { @@ -91,6 +86,7 @@ if (require.main === module) { generate(true).then(() => log.info(HELPERS.masterfile, 'OK')) } +/** @type {import('.').read} */ const read = () => { try { return JSON.parse( @@ -101,7 +97,7 @@ const read = () => { HELPERS.masterfile, 'Unable to read masterfile, generating a new one for you now', ) - return generate(true) + generate(true) } } diff --git a/packages/masterfile/package.json b/packages/masterfile/package.json index f48d18381..9c5dfb769 100644 --- a/packages/masterfile/package.json +++ b/packages/masterfile/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "main": "./lib/index.js", + "types": "./lib/index.d.ts", "scripts": { "generate": "node ./lib", "sort": "npx sort-package-json", @@ -15,6 +16,10 @@ "dependencies": { "@rm/config": "*", "@rm/logger": "*", - "@rm/types": "*" + "@rm/types": "*", + "node-fetch": "2.6.7" + }, + "devDependencies": { + "@types/node-fetch": "2.6.1" } } diff --git a/packages/types/lib/augmentations.d.ts b/packages/types/lib/augmentations.d.ts index 14b781e41..1ea6c14ed 100644 --- a/packages/types/lib/augmentations.d.ts +++ b/packages/types/lib/augmentations.d.ts @@ -1,6 +1,6 @@ import { ButtonProps } from '@mui/material' import { Config, GetSafeConfig } from './config' -import { ExpressUser } from './server' +import { ExpressUser, Permissions } from './server' import { Request } from 'express' declare module 'config' { @@ -50,6 +50,13 @@ declare module '@mui/material/styles' { } } +declare module 'express-session' { + interface SessionData { + cooldown?: number + perms?: Permissions + } +} + // TODO // declare module '@mui/material/Button' { // interface ExtendButtonTypeMap { diff --git a/packages/types/lib/general.d.ts b/packages/types/lib/general.d.ts index 434de68db..779808f1f 100644 --- a/packages/types/lib/general.d.ts +++ b/packages/types/lib/general.d.ts @@ -27,12 +27,9 @@ export type RMGeoJSON = { features: RMFeature[] } -import masterfile = require('packages/masterfile/lib/data/masterfile.json') import { Config } from './config' import { SliderProps } from '@mui/material' -export type Masterfile = typeof masterfile - export type Strategy = 'discord' | 'telegram' | 'local' export type S2Polygon = [number, number][] @@ -88,9 +85,7 @@ export interface UICONS { egg: UiconImage[] } reward: { - [ - key: Masterfile['questRewardTypes'][keyof Masterfile['questRewardTypes']] - ]: UiconImage[] + [key: string]: UiconImage[] } spawnpoint: UiconImage[] team: UiconImage[] diff --git a/packages/vite-plugins/lib/favicon.js b/packages/vite-plugins/lib/favicon.js index ee6ce515b..108d1c4d1 100644 --- a/packages/vite-plugins/lib/favicon.js +++ b/packages/vite-plugins/lib/favicon.js @@ -2,36 +2,43 @@ const { resolve } = require('path') const fs = require('fs') +const { log, HELPERS } = require('@rm/logger') + /** * @param {boolean} isDevelopment * @returns {import('vite').Plugin} */ const faviconPlugin = (isDevelopment) => { - const favicon = fs.existsSync( - resolve(__dirname, '../../../public/favicon/favicon.ico'), - ) - ? resolve(__dirname, '../../../public/favicon/favicon.ico') - : resolve(__dirname, '../../../public/favicon/fallback.ico') - return { - name: 'vite-plugin-locales', - generateBundle() { - if (isDevelopment) return - this.emitFile({ - type: 'asset', - fileName: 'favicon.ico', - source: fs.readFileSync(favicon), - }) - }, - configureServer(server) { - server.middlewares.use((req, res, next) => { - if (req.url === '/favicon.ico') { - res.writeHead(200, { 'Content-Type': 'image/x-icon' }) - res.end(fs.readFileSync(favicon)) - return - } - next() - }) - }, + try { + const favicon = fs.existsSync( + resolve(__dirname, '../../../public/favicon/favicon.ico'), + ) + ? resolve(__dirname, '../../../public/favicon/favicon.ico') + : resolve(__dirname, '../../../public/favicon/fallback.ico') + return { + name: 'vite-plugin-favicon', + generateBundle() { + if (isDevelopment) return + this.emitFile({ + type: 'asset', + fileName: 'favicon.ico', + source: fs.readFileSync(favicon), + }) + }, + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (req.url === '/favicon.ico') { + res.writeHead(200, { 'Content-Type': 'image/x-icon' }) + res.end(fs.readFileSync(favicon)) + return + } + next() + }) + }, + } + } catch (e) { + log.error(HELPERS.build, 'Error loading favicon', e) + return { name: 'vite-plugin-favicon' } } } diff --git a/packages/vite-plugins/lib/muteWarnings.js b/packages/vite-plugins/lib/muteWarnings.js index b8ba65013..79036fcc9 100644 --- a/packages/vite-plugins/lib/muteWarnings.js +++ b/packages/vite-plugins/lib/muteWarnings.js @@ -7,7 +7,7 @@ const muteWarningsPlugin = (warningsToIgnore) => { const mutedMessages = new Set() return { - name: 'mute-warnings', + name: 'vite-mute-warnings', enforce: 'pre', config: (userConfig) => ({ build: { diff --git a/packages/vite-plugins/package.json b/packages/vite-plugins/package.json index 37a252f39..798668a17 100644 --- a/packages/vite-plugins/package.json +++ b/packages/vite-plugins/package.json @@ -12,7 +12,8 @@ "prettier:fix": "prettier --write \"**/*.{css,html,js,jsx,yml}\"" }, "dependencies": { - "@rm/locales": "*" + "@rm/locales": "*", + "@rm/logger": "*" }, "devDependencies": { "vite": "5.0.12" diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index 7351fd43a..c9d0f2211 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -497,7 +497,7 @@ const resolvers = { category, status, ) - if (category === 'pokemon') { + if (category === 'pokemon' && result.pokemon) { result.pokemon = result.pokemon.map((x) => ({ ...x, allForms: !x.form, @@ -506,13 +506,13 @@ const resolvers = { xl: x.min_weight !== 0, })) } - if (category === 'invasion') { + if (category === 'invasion' && result.invasion) { result.invasion = result.invasion.map((x) => ({ ...x, real_grunt_id: PoracleAPI.getRealGruntId(x, Event.invasions), })) } - if (category === 'raid') { + if (category === 'raid' && result.raid) { result.raid = result.raid.map((x) => ({ ...x, allMoves: x.move === 9000, @@ -656,7 +656,7 @@ const resolvers = { status, data, ) - if (category === 'pokemon') { + if (category === 'pokemon' && result.pokemon) { result.pokemon = result.pokemon.map((x) => ({ ...x, allForms: !x.form, @@ -665,13 +665,13 @@ const resolvers = { xl: x.min_weight !== 0, })) } - if (category === 'invasion') { + if (category === 'invasion' && result.invasion) { result.invasion = result.invasion.map((x) => ({ ...x, real_grunt_id: PoracleAPI.getRealGruntId(x, Event.invasions), })) } - if (category === 'raid') { + if (category === 'raid' && result.raid) { result.raid = result.raid.map((x) => ({ ...x, allMoves: x.move === 9000, diff --git a/server/src/index.js b/server/src/index.js index 15ececc5d..973b8da26 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -304,6 +304,12 @@ connection.migrate .then(() => Db.getDbContext()) .then(async () => { httpServer.listen(config.getSafe('port'), config.getSafe('interface')) + log.info( + HELPERS.ReactMap, + `Server is now listening at http://${config.getSafe( + 'interface', + )}:${config.getSafe('port')}`, + ) await Promise.all([ Db.historicalRarity(), Db.getFilterContext(), @@ -325,11 +331,7 @@ connection.migrate .toISOString() .split('.')[0] .split('T') - .join( - ' ', - )} [ReactMap] Server is now listening at http://${config.getSafe( - 'interface', - )}:${config.getSafe('port')}`, + .join(' ')} [ReactMap] has fully started`, ) setTimeout(() => text.stop(), 1_000) }) diff --git a/server/src/routes/api/v1/config.js b/server/src/routes/api/v1/config.js index 372209d88..b246f21cd 100644 --- a/server/src/routes/api/v1/config.js +++ b/server/src/routes/api/v1/config.js @@ -1,29 +1,31 @@ +// @ts-check const path = require('path') const router = require('express').Router() const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') +const api = config.getSafe('api') + router.get('/', (req, res) => { try { if ( - config.api.reactMapSecret && - req.headers['react-map-secret'] === config.api.reactMapSecret + api.reactMapSecret && + req.headers['react-map-secret'] === api.reactMapSecret ) { res.status(200).json({ + ...config, api: { - ...config.api, + ...api, reactMapSecret: undefined, }, ...config, database: { ...config.database, - schemas: config.api.showSchemasInConfigApi - ? config.database.schemas - : [], + schemas: api.showSchemasInConfigApi ? config.database.schemas : [], }, authentication: { ...config.authentication, - strategies: config.api.showStrategiesInConfigApi + strategies: api.showStrategiesInConfigApi ? config.authentication.strategies : [], }, diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index 4cafdaac2..98f08a54c 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -11,6 +11,9 @@ const { version } = require('../../../package.json') const areaPerms = require('../services/functions/areaPerms') const getServerSettings = require('../services/functions/getServerSettings') +const scanner = config.getSafe('scanner') +const api = config.getSafe('api') + const rootRouter = express.Router() rootRouter.use('/', clientRouter) @@ -116,10 +119,7 @@ rootRouter.post('/api/error/client', async (req, res) => { rootRouter.get('/area/:area/:zoom?', (req, res) => { const { area, zoom } = req.params try { - const { scanAreas } = config.areas - const validScanAreas = scanAreas[req.headers.host.replaceAll('.', '_')] - ? scanAreas[req.headers.host.replaceAll('.', '_')] - : scanAreas.main + const validScanAreas = config.getAreas(req, 'scanAreas') if (validScanAreas.features.length) { const foundArea = validScanAreas.features.find( (a) => a.properties.name.toLowerCase() === area.toLowerCase(), @@ -137,30 +137,32 @@ rootRouter.get('/area/:area/:zoom?', (req, res) => { }) rootRouter.get('/api/settings', async (req, res, next) => { + const authentication = config.getSafe('authentication') + const mapConfig = config.getMapConfig(req) try { if ( - config.authentication.alwaysEnabledPerms.length || - !config.authentication.methods.length + authentication.alwaysEnabledPerms.length || + !authentication.methods.length ) { if (req.session.tutorial === undefined) { - req.session.tutorial = !config.map.forceTutorial + req.session.tutorial = !mapConfig.forceTutorial } req.session.perms = { ...Object.fromEntries( - Object.keys(config.authentication.perms).map((p) => [p, false]), + Object.keys(authentication.perms).map((p) => [p, false]), ), areaRestrictions: areaPerms(['none']), webhooks: [], - scanner: Object.keys(config.scanner).filter( + scanner: Object.keys(scanner).filter( (key) => key !== 'backendConfig' && - config.scanner[key].enabled && - !config.scanner[key].discordRoles.length && - !config.scanner[key].telegramGroups.length, + scanner[key].enabled && + !scanner[key].discordRoles.length && + !scanner[key].telegramGroups.length, ), } - config.authentication.alwaysEnabledPerms.forEach((perm) => { - if (config.authentication.perms[perm]) { + authentication.alwaysEnabledPerms.forEach((perm) => { + if (authentication.perms[perm]) { req.session.perms[perm] = true } else { log.warn( @@ -175,7 +177,7 @@ rootRouter.get('/api/settings', async (req, res, next) => { } req.session.save() - if (config.authentication.methods.length && req.user) { + if (authentication.methods.length && req.user) { try { const user = await Db.query('User', 'getOne', req.user.id) if (user) { @@ -202,20 +204,17 @@ rootRouter.get('/api/settings', async (req, res, next) => { const settings = getServerSettings(req) if ('perms' in settings.user) { - if ( - settings.user.perms.pokemon && - config.api.queryOnSessionInit.pokemon - ) { + if (settings.user.perms.pokemon && api.queryOnSessionInit.pokemon) { Event.setAvailable('pokemon', 'Pokemon', Db, false) } if ( - config.api.queryOnSessionInit.raids && + api.queryOnSessionInit.raids && (settings.user.perms.raids || settings.user.perms.gyms) ) { Event.setAvailable('gyms', 'Gym', Db, false) } if ( - config.api.queryOnSessionInit.quests && + api.queryOnSessionInit.quests && (settings.user.perms.quests || settings.user.perms.pokestops || settings.user.perms.invasions || @@ -223,10 +222,10 @@ rootRouter.get('/api/settings', async (req, res, next) => { ) { Event.setAvailable('pokestops', 'Pokestop', Db, false) } - if (settings.user.perms.nests && config.api.queryOnSessionInit.nests) { + if (settings.user.perms.nests && api.queryOnSessionInit.nests) { Event.setAvailable('nests', 'Nest', Db, false) } - if (Object.values(config.api.queryOnSessionInit).some(Boolean)) { + if (Object.values(api.queryOnSessionInit).some(Boolean)) { Event.addAvailable() } } diff --git a/server/src/services/DbCheck.js b/server/src/services/DbCheck.js index 598c0bdd8..0222f7385 100644 --- a/server/src/services/DbCheck.js +++ b/server/src/services/DbCheck.js @@ -6,7 +6,7 @@ const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') const { getBboxFromCenter } = require('./functions/getBbox') -const { setCache, getCache } = require('./cache') +const { getCache } = require('./cache') const softLimit = config.getSafe('api.searchSoftKmLimit') const hardLimit = config.getSafe('api.searchHardKmLimit') @@ -248,7 +248,7 @@ module.exports = class DbCheck { * @param {boolean} historical * @returns {void} */ - async setRarity(results, historical = false) { + setRarity(results, historical = false) { const base = {} const mapKey = historical ? 'historical' : 'rarity' let total = 0 @@ -279,7 +279,6 @@ module.exports = class DbCheck { this[mapKey][id] = 'common' } }) - await setCache(`${mapKey}.json`, this[mapKey]) } async historicalRarity() { @@ -295,7 +294,7 @@ module.exports = class DbCheck { .groupBy('pokemon_id'), ), ) - await this.setRarity( + this.setRarity( results.map((result) => Object.fromEntries( result.map((pkmn) => [`${pkmn.pokemon_id}`, +pkmn.total]), @@ -595,10 +594,9 @@ module.exports = class DbCheck { Object.values(titles), ]), ) - await setCache('questConditions.json', this.questConditions) } if (model === 'Pokemon') { - await this.setRarity(results, false) + this.setRarity(results, false) } if (results.length === 1) return results[0].available if (results.length > 1) { @@ -642,7 +640,6 @@ module.exports = class DbCheck { ...results.map((result) => result.max_duration), ) log.info(HELPERS.db, 'Updating filter context for routes') - await setCache('filterContext.json', this.filterContext) } catch (e) { log.error( HELPERS.db, diff --git a/server/src/services/EventManager.js b/server/src/services/EventManager.js index ef8e13260..a19f2d18a 100644 --- a/server/src/services/EventManager.js +++ b/server/src/services/EventManager.js @@ -3,19 +3,19 @@ const { promises: fs } = require('fs') const path = require('path') const Ohbem = require('ohbem') const { default: fetch } = require('node-fetch') -const config = require('@rm/config') +const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') const { generate, read } = require('@rm/masterfile') const PoracleAPI = require('./api/Poracle') -const { getCache, setCache } = require('./cache') +const { getCache } = require('./cache') class EventManager { constructor() { - /** @type {import("@rm/types").Masterfile} */ + /** @type {import("@rm/masterfile").Masterfile} */ this.masterfile = read() - /** @type {import("@rm/types").Masterfile['invasions'] | {}} */ + /** @type {import("@rm/masterfile").Masterfile['invasions'] | {}} */ this.invasions = 'invasions' in this.masterfile ? this.masterfile.invasions : {} @@ -104,7 +104,6 @@ class EventManager { return 0 }) - await setCache('available.json', this.available) } /** @@ -313,7 +312,6 @@ class EventManager { } } this[type] = Object.values(this[`${type}Backup`]) - await setCache(`${type}.json`, this[type]) } /** @@ -351,18 +349,23 @@ class EventManager { } addAvailable() { - Object.entries(this.available).forEach(([category, entries]) => { + Object.entries(this.available).forEach(([c, entries]) => { + const category = /** @type {keyof EventManager['available']} */ (c) entries.forEach((item) => { if (!Number.isNaN(parseInt(item.charAt(0)))) { const [id, form] = item.split('-') if (!this.masterfile.pokemon[id]) { this.masterfile.pokemon[id] = { + name: '', pokedexId: +id, types: [], quickMoves: [], - chargeMoves: [], + chargedMoves: [], + defaultFormId: +form, + forms: {}, + genId: 0, } - log.info(HELPERS.event, `Added ${id} to Pokemon`) + log.warn(HELPERS.event, `Added ${id} to Pokemon, seems suspicious`) } if (!this.masterfile.pokemon[id].forms) { this.masterfile.pokemon[id].forms = {} diff --git a/server/src/services/api/Poracle.js b/server/src/services/api/Poracle.js index 1ca622c53..c45d3ea2a 100644 --- a/server/src/services/api/Poracle.js +++ b/server/src/services/api/Poracle.js @@ -1235,7 +1235,7 @@ class PoracleAPI { /** * * @param {import('@rm/types').PoracleInvasion} item - * @param {import('@rm/types').Masterfile['invasions']} invasions + * @param {import('@rm/masterfile').Masterfile['invasions']} invasions */ static getRealGruntId(item, invasions) { return ( diff --git a/server/src/services/api/fetchJson.js b/server/src/services/api/fetchJson.js index 5f7ed839c..dc18f02e9 100644 --- a/server/src/services/api/fetchJson.js +++ b/server/src/services/api/fetchJson.js @@ -2,7 +2,7 @@ const fs = require('fs') const { resolve } = require('path') -const { default: fetch } = require('node-fetch') +const { default: fetch, Response } = require('node-fetch') const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') @@ -31,6 +31,12 @@ async function fetchJson(url, options = undefined) { return response.json() } catch (e) { if (e instanceof Error) { + if ( + e.cause instanceof Response && + e.cause.status === 404 && + url.includes('/api/pokemon/id') + ) + return e.cause log.error(HELPERS.fetch, `Unable to fetch ${url}`, '\n', e) if (options) { fs.writeFileSync( diff --git a/server/src/services/api/scannerApi.js b/server/src/services/api/scannerApi.js index 38053d748..e72b8ae88 100644 --- a/server/src/services/api/scannerApi.js +++ b/server/src/services/api/scannerApi.js @@ -1,11 +1,10 @@ // @ts-check const { default: fetch } = require('node-fetch') -const NodeCache = require('node-cache') const config = require('@rm/config') const { log, HELPERS } = require('@rm/logger') -const { getCache, setCache } = require('../cache') +const { userCache } = require('../initialization') const Clients = require('../Clients') const TelegramClient = require('../TelegramClient') const DiscordClient = require('../DiscordClient') @@ -15,37 +14,7 @@ const scannerQueue = { scanZone: {}, } -const userCache = new NodeCache({ stdTTL: 60 * 60 * 24 }) - -const onShutdown = async () => { - const cacheObj = {} - userCache.keys().forEach((key) => { - cacheObj[key] = userCache.get(key) - }) - await setCache('scanUserHistory.json', cacheObj) -} -process.on('SIGINT', async () => { - await onShutdown() - process.exit(0) -}) -process.on('SIGTERM', async () => { - await onShutdown() - process.exit(0) -}) -process.on('SIGUSR1', async () => { - await onShutdown() - process.exit(0) -}) -process.on('SIGUSR2', async () => { - await onShutdown() - process.exit(0) -}) - -Object.entries(getCache('scanUserHistory.json', {})).forEach(([k, v]) => - userCache.set(k, v), -) - -const backendConfig = config.getSafe('scanner.backendConfig') +const { backendConfig, ...scanModes } = config.getSafe('scanner') const scanNextOptions = { routes: config.getSafe('scanner.scanNext.routes'), @@ -84,7 +53,7 @@ async function scannerApi( const timeout = setTimeout(() => { controller.abort() - }, config.api.fetchTimeoutMs) + }, config.getSafe('api.fetchTimeoutMs')) const coords = backendConfig.platform === 'mad' @@ -161,9 +130,9 @@ async function scannerApi( url: `${ backendConfig.apiEndpoint }/send_gps?origin=${encodeURIComponent( - config.scanner.scanNext.scanNextDevice, + scanModes.scanNext.scanNextDevice, )}&coords=${JSON.stringify(coords)}&sleeptime=${ - config.scanner.scanNext.scanNextSleeptime + scanModes.scanNext.scanNextSleeptime }`, options: { method, headers }, }) @@ -173,7 +142,7 @@ async function scannerApi( url: `${ backendConfig.apiEndpoint }/set_data?scan_next=true&instance=${encodeURIComponent( - config.scanner.scanNext.scanNextInstance, + scanModes.scanNext.scanNextInstance, )}&coords=${JSON.stringify(coords)}`, options: { method, headers }, }) @@ -249,7 +218,7 @@ async function scannerApi( url: `${ backendConfig.apiEndpoint }/set_data?scan_next=true&instance=${encodeURIComponent( - config.scanner.scanZone.scanZoneInstance, + scanModes.scanZone.scanZoneInstance, )}&coords=${JSON.stringify(coords)}`, options: { method, headers }, }) @@ -283,7 +252,7 @@ async function scannerApi( url: `${backendConfig.apiEndpoint}/get_data?${ data.type }=true&queue_size=true&instance=${encodeURIComponent( - config.scanner[data.typeName][`${data.typeName}Instance`], + scanModes[data.typeName][`${data.typeName}Instance`], )}`, options: { method, headers }, }) @@ -395,9 +364,10 @@ async function scannerApi( }, thumbnail: { url: - config.authentication.strategies.find( - (strategy) => strategy.name === user.rmStrategy, - )?.thumbnailUrl ?? + config + .getSafe('authentication.strategies') + .find((strategy) => strategy.name === user.rmStrategy) + ?.thumbnailUrl ?? `https://user-images.githubusercontent.com/58572875/167069223-745a139d-f485-45e3-a25c-93ec4d09779c.png`, }, description: `<@${user.discordId}>\n${capitalized} Size: ${data.scanSize}\nCoordinates: ${coords.length}\n`, @@ -413,10 +383,10 @@ async function scannerApi( name: 'Instance', value: `${ backendConfig.platform === 'mad' - ? `Device: ${config.scanner.scanNext.scanNextDevice}` + ? `Device: ${scanModes.scanNext.scanNextDevice}` : '' }\nName: ${ - config.scanner[category]?.[`${category}Instance`] || '-' + scanModes[category]?.[`${category}Instance`] || '-' }\nQueue: ${scannerQueue[category]?.queue || 0}`, inline: true, }, @@ -454,7 +424,7 @@ async function scannerApi( log.info( HELPERS.scanner, `Error: instance ${ - config.scanner[category]?.[`${category}Instance`] + scanModes[category]?.[`${category}Instance`] } does not exist`, ) return { status: 'error', message: 'scanner_no_instance' } @@ -462,7 +432,7 @@ async function scannerApi( log.info( HELPERS.scanner, `Error: instance ${ - config.scanner[category]?.[`${category}Instance`] + scanModes[category]?.[`${category}Instance`] } has no device assigned`, ) return { status: 'error', message: 'scanner_no_device_assigned' } @@ -470,7 +440,7 @@ async function scannerApi( log.info( HELPERS.scanner, `Error: device ${ - config.scanner[category]?.[`${category}Device`] + scanModes[category]?.[`${category}Device`] } does not exist`, ) return { status: 'error', message: 'scanner_no_device' } diff --git a/server/src/services/areas.js b/server/src/services/areas.js index fd3798646..cbed30be7 100644 --- a/server/src/services/areas.js +++ b/server/src/services/areas.js @@ -38,6 +38,27 @@ const manualGeojson = { }), } +/** + * @param {string} fileName + * @returns {import("@rm/types").RMGeoJSON} + */ +const loadFromFile = (fileName) => { + try { + if (fileName.startsWith('http')) { + return getCache(fileName, DEFAULT_RETURN) + } + if (fs.existsSync(resolve(__dirname, `../configs/${fileName}`))) { + return JSON.parse( + fs.readFileSync(resolve(__dirname, `../configs/${fileName}`), 'utf-8'), + ) + } + return DEFAULT_RETURN + } catch (e) { + log.warn(HELPERS.areas, `Failed to load ${fileName} from file system`, e) + return DEFAULT_RETURN + } +} + /** * @param {string} location * @returns {Promise} @@ -57,7 +78,7 @@ const getGeojson = async (location) => { .then(async (res) => { if (res?.data) { log.info(HELPERS.areas, 'Caching', location, 'from Kōji') - await setCache(`${location.replace(/\//g, '__')}.json`, res.data) + await setCache(location, res.data) return res.data } return DEFAULT_RETURN @@ -68,7 +89,7 @@ const getGeojson = async (location) => { 'Failed to fetch Kōji geojson, attempting to read from backup', err, ) - const cached = getCache(`${location.replace(/\//g, '__')}.json`) + const cached = getCache(location) if (cached) { log.info(HELPERS.areas, 'Reading from koji_backups for', location) return cached @@ -77,11 +98,7 @@ const getGeojson = async (location) => { return DEFAULT_RETURN }) } - if (fs.existsSync(resolve(__dirname, `../configs/${location}`))) { - return JSON.parse( - fs.readFileSync(resolve(__dirname, `../configs/${location}`), 'utf-8'), - ) - } + return loadFromFile(location) } catch (e) { log.warn(HELPERS.areas, 'Issue with getting the geojson', e) } @@ -111,7 +128,7 @@ const loadScanPolygons = async (fileName, domain) => { ? `${f.properties.parent}-${f.properties.name}` : f.properties.name, center: /** @type {[number,number]} */ ( - center(f).geometry.coordinates.reverse() + center(f).geometry.coordinates.slice().reverse() ), }, })), @@ -335,13 +352,13 @@ const loadCachedAreas = () => { /** @type {Record} */ const scanAreas = { - main: getCache(`${fileName.replace(/\//g, '__')}.json`, DEFAULT_RETURN), + main: loadFromFile(fileName), ...Object.fromEntries( config .getSafe('multiDomains') .map((d) => [ d.general?.geoJsonFileName ? d.domain.replaceAll('.', '_') : 'main', - getCache(d.general?.geoJsonFileName || fileName, DEFAULT_RETURN), + loadFromFile(d.general?.geoJsonFileName || fileName), ]), ), } diff --git a/server/src/services/cache.js b/server/src/services/cache.js index e8b1e6d1b..648bf0de6 100644 --- a/server/src/services/cache.js +++ b/server/src/services/cache.js @@ -1,3 +1,4 @@ +// @ts-check const fs = require('fs') const path = require('path') @@ -5,13 +6,18 @@ const { log, HELPERS } = require('@rm/logger') const CACHE_DIR = path.join(__dirname, '../../.cache') +/** @param {string} str */ +const fsFriendlyName = (str) => + str.startsWith('http') ? `${str.replace(/\//g, '__')}.json` : str + /** * @template T - * @param {string} fileName + * @param {string} unsafeName * @param {T} [fallback] * @returns {T} */ -const getCache = (fileName, fallback = null) => { +const getCache = (unsafeName, fallback = null) => { + const fileName = fsFriendlyName(unsafeName) try { if (!fs.existsSync(path.resolve(CACHE_DIR, fileName))) return fallback const data = JSON.parse( @@ -30,10 +36,11 @@ const getCache = (fileName, fallback = null) => { } /** - * @param {string} fileName + * @param {string} unsafeName * @param {object | string} data */ -const setCache = async (fileName, data) => { +const setCache = async (unsafeName, data) => { + const fileName = fsFriendlyName(unsafeName) try { if (!fs.existsSync(CACHE_DIR)) await fs.promises.mkdir(CACHE_DIR) await fs.promises.writeFile( diff --git a/server/src/services/functions/getServerSettings.js b/server/src/services/functions/getServerSettings.js index 7acf10629..0761721e8 100644 --- a/server/src/services/functions/getServerSettings.js +++ b/server/src/services/functions/getServerSettings.js @@ -1,3 +1,4 @@ +// @ts-check const config = require('@rm/config') const clientOptions = require('../ui/clientOptions') @@ -17,43 +18,48 @@ function getServerSettings(req) { const { clientValues, clientMenus } = clientOptions(user.perms) - const validConfig = config.getMapConfig(req) + const mapConfig = config.getMapConfig(req) + const api = config.getSafe('api') + const authentication = config.getSafe('authentication') + const database = config.getSafe('database') + const serverSettings = { api: { - polling: config.api.polling, - gymValidDataLimit: - Date.now() / 1000 - config.api.gymValidDataLimit * 86400, + polling: api.polling, + gymValidDataLimit: Date.now() / 1000 - api.gymValidDataLimit * 86400, }, user, authReferences: { - areaRestrictions: config.authentication.areaRestrictions.length, - webhooks: config.webhooks.filter((w) => w.enabled).length, - scanner: Object.values(config.scanner).filter((s) => s.enabled).length, + areaRestrictions: authentication.areaRestrictions.length, + webhooks: config.getSafe('webhooks').filter((w) => w.enabled).length, + scanner: Object.values(config.getSafe('scanner')).filter( + (s) => 'enabled' in s && s.enabled, + ).length, }, map: { - ...validConfig, + ...mapConfig, general: { - ...validConfig.general, + ...mapConfig.general, geoJsonFileName: undefined, }, - loginPage: !!config.map.loginPage.components.length, + loginPage: !!mapConfig.loginPage.components.length, donationPage: undefined, messageOfTheDay: undefined, customFloatingIcons: undefined, }, authentication: { loggedIn: !!req.user, - excludeList: config.authentication.excludeFromTutorial, - methods: config.authentication.methods, + excludeList: authentication.excludeFromTutorial, + methods: authentication.methods, }, database: { settings: { - extraUserFields: config.database.settings.extraUserFields, - userBackupLimits: config.database.settings.userBackupLimits, + extraUserFields: database.settings.extraUserFields, + userBackupLimits: database.settings.userBackupLimits, }, }, - tileServers: config.tileServers, - navigation: config.navigation, + tileServers: config.getSafe('tileServers'), + navigation: config.getSafe('navigation'), menus: advMenus(), userSettings: clientValues, clientMenus, diff --git a/server/src/services/initialization.js b/server/src/services/initialization.js index 4eeb3b33c..094b99154 100644 --- a/server/src/services/initialization.js +++ b/server/src/services/initialization.js @@ -1,9 +1,13 @@ // @ts-check +const NodeCache = require('node-cache') + const config = require('@rm/config') +const { log, HELPERS } = require('@rm/logger') const DbCheck = require('./DbCheck') const EventManager = require('./EventManager') const PvpWrapper = require('./PvpWrapper') +const { getCache, setCache } = require('./cache') const Db = new DbCheck() const Pvp = config.getSafe('api.pvp.reactMapHandlesPvp') @@ -11,10 +15,58 @@ const Pvp = config.getSafe('api.pvp.reactMapHandlesPvp') : null const Event = new EventManager() +const userCache = new NodeCache({ stdTTL: 60 * 60 * 24 }) + +Object.entries(getCache('scanUserHistory.json', {})).forEach(([k, v]) => + userCache.set(k, v), +) + Event.setTimers(Db, Pvp) +/** @param {NodeJS.Signals} e */ +const onShutdown = async (e) => { + log.info(HELPERS.ReactMap, 'received signal', e, 'writing cache...') + const cacheObj = {} + userCache.keys().forEach((key) => { + cacheObj[key] = userCache.get(key) + }) + await Promise.all([ + setCache('scanUserHistory.json', cacheObj), + setCache('rarity.json', Db.rarity), + setCache('historical.json', Db.historical), + setCache('available.json', Event.available), + setCache('filterContext.json', Db.filterContext), + setCache('questConditions.json', Db.questConditions), + setCache('uaudio.json', Event.uaudio), + setCache('uicons.json', Event.uicons), + ]) + log.info(HELPERS.ReactMap, 'exiting...') +} + +process.on('SIGINT', async (e) => { + await onShutdown(e) + process.exit(0) +}) +process.on('SIGTERM', async (e) => { + await onShutdown(e) + process.exit(0) +}) +process.on('SIGUSR1', async (e) => { + await onShutdown(e) + process.exit(0) +}) +process.on('SIGUSR2', async (e) => { + await onShutdown(e) + process.exit(0) +}) +process.on('uncaughtException', async () => { + await onShutdown('SIGBREAK') + process.exit(99) +}) + module.exports = { Db, Pvp, Event, + userCache, } diff --git a/src/assets/mui/global.jsx b/src/assets/mui/global.jsx index 327cb5017..df39a0e2e 100644 --- a/src/assets/mui/global.jsx +++ b/src/assets/mui/global.jsx @@ -1,3 +1,4 @@ +// @ts-check import * as React from 'react' import GlobalStyles from '@mui/material/GlobalStyles' import { darken, lighten } from '@mui/material/styles' diff --git a/src/components/ClearStorage.jsx b/src/components/ClearStorage.jsx index 1a9b744ec..292ec3785 100644 --- a/src/components/ClearStorage.jsx +++ b/src/components/ClearStorage.jsx @@ -6,20 +6,22 @@ import { useMemory } from '@hooks/useMemory' import { useStorage } from '@hooks/useStorage' export default function ClearStorage() { - localStorage.clear() - sessionStorage.clear() - React.useEffect(() => { + localStorage.clear() + sessionStorage.clear() + + const { filters, menus } = useMemory.getState() useStorage.setState({ - filters: {}, - menus: {}, + filters: structuredClone(filters), + menus: structuredClone(menus), location: [ CONFIG.map.general.startLat || 0, CONFIG.map.general.startLon || 0, ], zoom: CONFIG.map.general.startZoom, }) - useMemory.setState({ Icons: null }) + useMemory.setState({ Icons: null, Audio: null }) }, []) + return } diff --git a/src/components/layout/dialogs/filters/FilterMenu.jsx b/src/components/layout/dialogs/filters/FilterMenu.jsx index 7f04f1efd..c6c6e246e 100644 --- a/src/components/layout/dialogs/filters/FilterMenu.jsx +++ b/src/components/layout/dialogs/filters/FilterMenu.jsx @@ -30,6 +30,7 @@ export default function FilterMenu() { setTempFilters(filters?.filter) }, [category, filters?.filter]) + if (!category || !type) return null return ( React.ReactNode + * tempFilters: import('@rm/types').AllFilters[T] + * children: (index: number, key: string) => React.ReactNode * categories?: import('@rm/types').Available[] * title: string * titleAction: () => void @@ -51,7 +52,7 @@ export default function Menu({ const { t } = useTranslation() const menus = useStorage((state) => state.menus) - const [filterDrawer, setFilterDrawer] = useState(false) + const [filterDrawer, setFilterDrawer] = React.useState(false) const { filteredArr, count } = useFilter( tempFilters, @@ -74,55 +75,57 @@ export default function Menu({ ) const footerButtons = React.useMemo( - () => [ - { - name: 'help', - action: () => - useLayoutStore.setState({ help: { open: true, category } }), - icon: 'HelpOutline', - }, - { - name: '', - disabled: true, - }, - { - name: 'apply_to_all', - action: () => - (webhookCategory ? useWebhookStore : useLayoutStore).setState({ - [webhookCategory ? 'advanced' : 'advancedFilter']: { - open: true, - id: 'global', - category: webhookCategory || category, - selectedIds: filteredArr, - }, - }), - icon: category === 'pokemon' || webhookCategory ? 'Tune' : 'FormatSize', - }, - { - name: 'disable_all', - action: () => - webhookCategory - ? applyToAllWebhooks(false, filteredArr) - : applyToAll({ enabled: false }, category, filteredArr), - icon: 'Clear', - color: 'error', - }, - { - name: 'enable_all', - action: () => - webhookCategory - ? applyToAllWebhooks(true, filteredArr) - : applyToAll( - { enabled: true }, - category, - filteredArr, - !webhookCategory, - ), - icon: 'Check', - color: 'success', - }, - ...(extraButtons ?? []), - ], + () => + /** @type {import('@components/layout/general/Footer').FooterButton[]} */ ([ + { + name: 'help', + action: () => + useLayoutStore.setState({ help: { open: true, category } }), + icon: 'HelpOutline', + }, + { + name: '', + disabled: true, + }, + { + name: 'apply_to_all', + action: () => + (webhookCategory ? useWebhookStore : useLayoutStore).setState({ + [webhookCategory ? 'advanced' : 'advancedFilter']: { + open: true, + id: 'global', + category: webhookCategory || category, + selectedIds: filteredArr, + }, + }), + icon: + category === 'pokemon' || webhookCategory ? 'Tune' : 'FormatSize', + }, + { + name: 'disable_all', + action: () => + webhookCategory + ? applyToAllWebhooks(false, filteredArr) + : applyToAll({ enabled: false }, category, filteredArr), + icon: 'Clear', + color: 'error', + }, + { + name: 'enable_all', + action: () => + webhookCategory + ? applyToAllWebhooks(true, filteredArr) + : applyToAll( + { enabled: true }, + category, + filteredArr, + !webhookCategory, + ), + icon: 'Check', + color: 'success', + }, + ...(extraButtons ?? []), + ]), [category, webhookCategory, extraButtons, filteredArr, tempFilters], ) diff --git a/src/hooks/useLayoutStore.js b/src/hooks/useLayoutStore.js index 77bb1c9e8..4bfcf80bb 100644 --- a/src/hooks/useLayoutStore.js +++ b/src/hooks/useLayoutStore.js @@ -25,7 +25,7 @@ import { useStorage } from './useStorage' * }, * dialog: { * open: boolean, - * category: string, + * category: import('@rm/types').AdvCategories | '', * type: string, * }, * gymBadge: { diff --git a/src/hooks/useLocation.js b/src/hooks/useLocation.js index 44ddde7b3..44faeb493 100644 --- a/src/hooks/useLocation.js +++ b/src/hooks/useLocation.js @@ -10,7 +10,9 @@ import 'leaflet.locatecontrol' */ export default function useLocation() { const map = useMap() - const [color, setColor] = useState('inherit') + const [color, setColor] = useState( + /** @type {import('@mui/material').ButtonProps['color']} */ ('inherit'), + ) const { t } = useTranslation() const lc = useMemo(() => { diff --git a/src/hooks/useMemory.js b/src/hooks/useMemory.js index ad2d57581..5994bf2b8 100644 --- a/src/hooks/useMemory.js +++ b/src/hooks/useMemory.js @@ -30,8 +30,9 @@ import { create } from 'zustand' * scanner: number, * }, * }, + * menus: Record * filters: import('@rm/types').AllFilters, - * masterfile: import('@rm/types').Masterfile + * masterfile: import('@rm/masterfile').Masterfile * polling: Record * gymValidDataLimit: number * settings: Record @@ -96,7 +97,7 @@ export const useMemory = create((set) => ({ }, config: {}, filters: {}, - menus: undefined, + menus: {}, menuFilters: {}, userSettings: undefined, settings: undefined, diff --git a/src/services/Icons.js b/src/services/Icons.js index c81b5ad90..743938934 100644 --- a/src/services/Icons.js +++ b/src/services/Icons.js @@ -29,7 +29,7 @@ class UAssets { /** * * @param {import("@rm/types").Config['icons']} iconsConfig - * @param {import("@rm/types").Masterfile['questRewardTypes']} questRewardTypes + * @param {import("@rm/masterfile").Masterfile['questRewardTypes']} questRewardTypes * @param {'uicons' | 'uaudio'} assetType */ constructor({ customizable, sizes }, questRewardTypes, assetType) { diff --git a/src/services/filtering/genPokemon.js b/src/services/filtering/genPokemon.js index c86c2d888..594e26ef9 100644 --- a/src/services/filtering/genPokemon.js +++ b/src/services/filtering/genPokemon.js @@ -1,6 +1,11 @@ import { t } from 'i18next' -/* eslint-disable no-nested-ternary */ +/** + * + * @param {import('@rm/masterfile').Masterfile} pokemon + * @param {string[]} categories + * @returns + */ export default function genPokemon(pokemon, categories) { const tempObj = Object.fromEntries( categories.map((x) => [ @@ -25,7 +30,10 @@ export default function genPokemon(pokemon, categories) { (x) => `poke_type_${x}`, ) const name = - form.name && form.name !== 'Normal' && j != 0 && j != pkmn.defaultFormId + form.name && + form.name !== 'Normal' && + j !== '0' && + j != pkmn.defaultFormId ? formName : pokeName tempObj.pokemon[id] = { diff --git a/src/services/filtering/genPokestops.js b/src/services/filtering/genPokestops.js index 7f2ca7d24..9a075bfd5 100644 --- a/src/services/filtering/genPokestops.js +++ b/src/services/filtering/genPokestops.js @@ -1,5 +1,12 @@ import { t } from 'i18next' +/** + * + * @param {import('@rm/masterfile').Masterfile} pokemon + * @param {*} pokestops + * @param {*} categories + * @returns + */ export default function genPokestops(pokemon, pokestops, categories) { const tempObj = Object.fromEntries(categories.map((x) => [x, {}])) if (!pokestops?.filter) return {} @@ -149,9 +156,8 @@ export default function genPokestops(pokemon, pokestops, categories) { tempObj.quest_reward_9 && pokemon[monId] ) { - const category = [ - id.charAt(0) === 'c' ? 'quest_reward_4' : 'quest_reward_9', - ] + const category = + id.charAt(0) === 'c' ? 'quest_reward_4' : 'quest_reward_9' tempObj[category][id] = { name: `${t(`poke_${monId}`)} ${ id.charAt(0) === 'c' ? t('candy') : t('xl') diff --git a/src/services/functions/checkAdvFilter.js b/src/services/functions/checkAdvFilter.js index 0a09455a1..c9ddadfbd 100644 --- a/src/services/functions/checkAdvFilter.js +++ b/src/services/functions/checkAdvFilter.js @@ -1,7 +1,13 @@ +// @ts-check /* eslint-disable no-plusplus */ /* eslint-disable no-cond-assign */ /* eslint-disable default-case */ -export default function checkIVFilterValid(filter, dnf = true) { + +/** + * @param {string} filter + * @param {boolean} [dnf] + */ +function checkIVFilterValid(filter, dnf = true) { const input = filter.toUpperCase() const tokenizer = /\s*([()|&!,]|([ADSLXG]?|CP|LC|[GU]L)\s*([0-9]+(?:\.[0-9]*)?)(?:\s*-\s*([0-9]+(?:\.[0-9]*)?))?)/g @@ -58,3 +64,5 @@ export default function checkIVFilterValid(filter, dnf = true) { } return true } + +export default checkIVFilterValid diff --git a/src/services/functions/dayCheck.js b/src/services/functions/dayCheck.js index 8fd940b0c..d99c7ff7b 100644 --- a/src/services/functions/dayCheck.js +++ b/src/services/functions/dayCheck.js @@ -1,7 +1,15 @@ -export default function dayCheck(currentStamp, desiredStamp) { +// @ts-check + +/** + * @param {number} currentStamp + * @param {number} desiredStamp + */ +function dayCheck(currentStamp, desiredStamp) { const locale = localStorage.getItem('i18nextLng') || 'en' if (currentStamp - desiredStamp < 86400) { return new Date(desiredStamp * 1000).toLocaleTimeString(locale) } return new Date(desiredStamp * 1000).toLocaleString(locale) } + +export default dayCheck diff --git a/src/services/functions/deepMerge.js b/src/services/functions/deepMerge.js index 2b283c8be..4fea9db1c 100644 --- a/src/services/functions/deepMerge.js +++ b/src/services/functions/deepMerge.js @@ -1,9 +1,16 @@ +// @ts-check + +/** @param {any} item */ function isObject(item) { return ( item && typeof item === 'object' && !Array.isArray(item) && item !== null ) } +/** + * @param {Record} target + * @param {...Record} sources + */ export function deepMerge(target, ...sources) { if (!sources.length) return target const source = sources.shift() diff --git a/src/services/functions/formatInterval.js b/src/services/functions/formatInterval.js index 7bcc0b8f9..b032f2177 100644 --- a/src/services/functions/formatInterval.js +++ b/src/services/functions/formatInterval.js @@ -1,4 +1,7 @@ -export default function formatInterval(intervalMs) { +// @ts-check + +/** @param {number} intervalMs */ +function formatInterval(intervalMs) { const diff = Math.floor(intervalMs / 1000) const d = Math.floor(diff / 86400) const h = Math.floor(diff / 3600) @@ -16,3 +19,5 @@ export default function formatInterval(intervalMs) { return { str: str.join(' '), diff } } + +export default formatInterval diff --git a/src/services/functions/fromSearchCategory.js b/src/services/functions/fromSearchCategory.js index 488832ba1..51ab89180 100644 --- a/src/services/functions/fromSearchCategory.js +++ b/src/services/functions/fromSearchCategory.js @@ -1,3 +1,5 @@ +// @ts-check + /** * * @param {string} searchCategory diff --git a/src/services/functions/fromSnakeCase.js b/src/services/functions/fromSnakeCase.js index 31212a026..fd877f145 100644 --- a/src/services/functions/fromSnakeCase.js +++ b/src/services/functions/fromSnakeCase.js @@ -1,6 +1,10 @@ -/* eslint-disable import/prefer-default-export */ +// @ts-check import { capitalize } from '@mui/material' +/** + * @param {string} str + * @param {string} [separator] + */ export function fromSnakeCase(str, separator = ' ') { return capitalize(str) .replace(/_/g, separator) diff --git a/src/services/functions/getBadge.js b/src/services/functions/getBadge.js index 296bb57c2..d4760e2ca 100644 --- a/src/services/functions/getBadge.js +++ b/src/services/functions/getBadge.js @@ -1,3 +1,5 @@ +// @ts-check + /** * Get rank badge, commonly used for pvp and contests * @param {number} rank diff --git a/src/services/functions/getGruntReward.js b/src/services/functions/getGruntReward.js index d475fd66d..cd74a6a98 100644 --- a/src/services/functions/getGruntReward.js +++ b/src/services/functions/getGruntReward.js @@ -1,6 +1,6 @@ // @ts-check /** - * @typedef {import('@rm/types').Masterfile['invasions']} Invasions + * @typedef {import('@rm/masterfile').Masterfile['invasions']} Invasions * @param {Invasions[keyof Invasions]} grunt * @returns {{ first: number, second: number, third: number }} */ diff --git a/src/services/functions/getProperName.js b/src/services/functions/getProperName.js index 935724b30..4694f082d 100644 --- a/src/services/functions/getProperName.js +++ b/src/services/functions/getProperName.js @@ -1,4 +1,10 @@ -export default function getProperName(word) { +// @ts-check +/** + * @param {string} word + */ +function getProperName(word) { const capital = `${word.charAt(0).toUpperCase()}${word.slice(1)}` return capital.replace(/([a-z0-9])([A-Z])/g, '$1 $2') } + +export default getProperName diff --git a/src/services/functions/isLocalStorageEnabled.js b/src/services/functions/isLocalStorageEnabled.js index 10993ce33..9ed98f674 100644 --- a/src/services/functions/isLocalStorageEnabled.js +++ b/src/services/functions/isLocalStorageEnabled.js @@ -1,3 +1,5 @@ +// @ts-check + export function isLocalStorageEnabled() { const test = 'test' diff --git a/src/services/functions/offset.js b/src/services/functions/offset.js index 5f06481fa..4b72776bd 100644 --- a/src/services/functions/offset.js +++ b/src/services/functions/offset.js @@ -1,3 +1,4 @@ +// @ts-check /* eslint-disable no-bitwise */ /** @@ -33,9 +34,11 @@ export const cyrb53 = (str, seed = 0) => { */ export const getOffset = (coords, amount, id, seed = 0) => { const rand = cyrb53(id, seed) - return [0, 1].map((i) => { - let offset = rand[i] * (0.0002 / 4294967296) - 0.0001 - offset += offset >= 0 ? -amount : amount - return coords[i] + offset - }) + return /** @type {[number, number]} */ ( + [0, 1].map((i) => { + let offset = rand[i] * (0.0002 / 4294967296) - 0.0001 + offset += offset >= 0 ? -amount : amount + return coords[i] + offset + }) + ) } diff --git a/src/services/functions/parseConditions.js b/src/services/functions/parseConditions.js index 35b4e010f..b49f92c02 100644 --- a/src/services/functions/parseConditions.js +++ b/src/services/functions/parseConditions.js @@ -1,4 +1,7 @@ -export default function parseQuestConditions(conditions) { +// @ts-check + +/** @param {string} conditions */ +function parseQuestConditions(conditions) { const [type1, type2] = JSON.parse(conditions) const conditionsToReturn = [] const parseMadRewards = (specifics) => { @@ -60,3 +63,5 @@ export default function parseQuestConditions(conditions) { } return conditionsToReturn } + +export default parseQuestConditions diff --git a/vite.config.js b/vite.config.js index 79db95c1a..ace62341c 100644 --- a/vite.config.js +++ b/vite.config.js @@ -79,6 +79,9 @@ const viteConfig = defineConfig(({ mode }) => { overlay: { initialIsOpen: false, }, + // typescript: { + // tsconfigPath: './jsconfig.json', + // }, eslint: { lintCommand: 'eslint "./src/**/*.{js,jsx}"', }, diff --git a/yarn.lock b/yarn.lock index 8e1831dbb..fed8dbe0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2447,6 +2447,14 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== +"@types/node-fetch@2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" + integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node-fetch@^2.6.1", "@types/node-fetch@^2.6.4": version "2.6.9" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.9.tgz#15f529d247f1ede1824f7e7acdaa192d5f28071e" @@ -4837,6 +4845,15 @@ form-data-encoder@1.7.2: resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-1.7.2.tgz#1f1ae3dccf58ed4690b86d87e4f57c654fbab040" integrity sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + form-data@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" @@ -6928,13 +6945,6 @@ node-emoji@^1.11.0: dependencies: lodash "^4.17.21" -node-fetch@2, node-fetch@^2.6.0, node-fetch@^2.6.7: - version "2.7.0" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-fetch@2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" @@ -6942,6 +6952,13 @@ node-fetch@2.6.7: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.0, node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-geocoder@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/node-geocoder/-/node-geocoder-4.2.0.tgz#340a62636877dc3f8bce3554e9e13bbe85409624" @@ -9249,7 +9266,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== -"typescript@^4.6.4 || ^5.2.2": +"typescript@^4.6.4 || ^5.2.2", typescript@^5.3.3: version "5.3.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==