diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index 197928b25..171fbd8ee 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -1466,6 +1466,20 @@ "__name": "SCANNER_SCAN_NEXT_USER_COOLDOWN_SECONDS", "__format": "number" }, + "cooldownBypass": { + "discordRoles": { + "__name": "SCANNER_SCAN_NEXT_COOLDOWN_BYPASS_DISCORD_ROLES", + "__format": "json" + }, + "local": { + "__name": "SCANNER_SCAN_NEXT_COOLDOWN_BYPASS_LOCAL", + "__format": "json" + }, + "telegramGroups": { + "__name": "SCANNER_SCAN_NEXT_COOLDOWN_BYPASS_TELEGRAM_GROUPS", + "__format": "json" + } + }, "scanNextAreaRestriction": { "__name": "SCANNER_SCAN_NEXT_SCAN_NEXT_AREA_RESTRICTION", "__format": "json" @@ -1524,6 +1538,20 @@ "__name": "SCANNER_SCAN_ZONE_USER_COOLDOWN_SECONDS", "__format": "number" }, + "cooldownBypass": { + "discordRoles": { + "__name": "SCANNER_SCAN_ZONE_COOLDOWN_BYPASS_DISCORD_ROLES", + "__format": "json" + }, + "local": { + "__name": "SCANNER_SCAN_ZONE_COOLDOWN_BYPASS_LOCAL", + "__format": "json" + }, + "telegramGroups": { + "__name": "SCANNER_SCAN_ZONE_COOLDOWN_BYPASS_TELEGRAM_GROUPS", + "__format": "json" + } + }, "advancedScanZoneOptions": { "__name": "SCANNER_SCAN_ZONE_ADVANCED_SCAN_ZONE_OPTIONS", "__format": "boolean" @@ -2327,4 +2355,4 @@ } } } -} \ No newline at end of file +} diff --git a/config/default.json b/config/default.json index c1f2b8d8d..8de21302d 100644 --- a/config/default.json +++ b/config/default.json @@ -632,6 +632,11 @@ "scanNextDevice": "Device01", "scanNextSleeptime": 5, "userCooldownSeconds": 0, + "cooldownBypass": { + "discordRoles": [], + "local": [], + "telegramGroups": [] + }, "scanNextAreaRestriction": [], "discordRoles": [], "local": [], @@ -648,6 +653,11 @@ "gmf": false, "scanZoneMaxSize": 10, "userCooldownSeconds": 0, + "cooldownBypass": { + "discordRoles": [], + "local": [], + "telegramGroups": [] + }, "advancedScanZoneOptions": false, "scanZoneRadius": { "pokemon": 70, diff --git a/packages/types/lib/config.d.ts b/packages/types/lib/config.d.ts index 3c17ecf67..4c098138e 100644 --- a/packages/types/lib/config.d.ts +++ b/packages/types/lib/config.d.ts @@ -136,11 +136,21 @@ export type Config = DeepMerge< discordRoles: string[] telegramGroups: string[] local: string[] + cooldownBypass: { + discordRoles: string[] + telegramGroups: string[] + local: string[] + } } scanZone: { discordRoles: string[] telegramGroups: string[] local: string[] + cooldownBypass: { + discordRoles: string[] + telegramGroups: string[] + local: string[] + } } } icons: Icons diff --git a/packages/types/lib/server.d.ts b/packages/types/lib/server.d.ts index e4211fdd9..3b377dfeb 100644 --- a/packages/types/lib/server.d.ts +++ b/packages/types/lib/server.d.ts @@ -150,6 +150,7 @@ type BasePerms = { [K in keyof Config['authentication']['perms']]: boolean } export interface Permissions extends BasePerms { blockedGuildNames: string[] scanner: string[] + scannerCooldownBypass: string[] areaRestrictions: string[] webhooks: string[] trial: boolean diff --git a/server/src/graphql/resolvers.js b/server/src/graphql/resolvers.js index a0b4705bf..89558913d 100644 --- a/server/src/graphql/resolvers.js +++ b/server/src/graphql/resolvers.js @@ -403,30 +403,38 @@ const resolvers = { }, scannerConfig: (_, { mode }, { perms }) => { const scanner = config.getSafe('scanner') + const modeConfig = scanner[mode] - if (perms.scanner?.includes(mode) && scanner[mode].enabled) { - return mode === 'scanZone' - ? { - scannerType: scanner.backendConfig.platform, - showScanCount: scanner.scanZone.showScanCount, - showScanQueue: scanner.scanZone.showScanQueue, - advancedOptions: scanner.scanZone.advancedScanZoneOptions, - pokemonRadius: scanner.scanZone.scanZoneRadius.pokemon, - gymRadius: scanner.scanZone.scanZoneRadius.gym, - spacing: scanner.scanZone.scanZoneSpacing, - maxSize: scanner.scanZone.scanZoneMaxSize, - cooldown: scanner.scanZone.userCooldownSeconds, - refreshQueue: scanner.backendConfig.queueRefreshInterval, - enabled: scanner[mode].enabled, - } - : { - scannerType: scanner.backendConfig.platform, - showScanCount: scanner.scanNext.showScanCount, - showScanQueue: scanner.scanNext.showScanQueue, - cooldown: scanner.scanNext.userCooldownSeconds, - refreshQueue: scanner.backendConfig.queueRefreshInterval, - enabled: scanner[mode].enabled, - } + if (perms.scanner?.includes(mode) && modeConfig?.enabled) { + const bypassCooldown = perms.scannerCooldownBypass?.includes(mode) + const cooldownSeconds = bypassCooldown + ? 0 + : modeConfig.userCooldownSeconds + + if (mode === 'scanZone') { + return { + scannerType: scanner.backendConfig.platform, + showScanCount: scanner.scanZone.showScanCount, + showScanQueue: scanner.scanZone.showScanQueue, + advancedOptions: scanner.scanZone.advancedScanZoneOptions, + pokemonRadius: scanner.scanZone.scanZoneRadius.pokemon, + gymRadius: scanner.scanZone.scanZoneRadius.gym, + spacing: scanner.scanZone.scanZoneSpacing, + maxSize: scanner.scanZone.scanZoneMaxSize, + cooldown: cooldownSeconds, + refreshQueue: scanner.backendConfig.queueRefreshInterval, + enabled: modeConfig.enabled, + } + } + + return { + scannerType: scanner.backendConfig.platform, + showScanCount: scanner.scanNext.showScanCount, + showScanQueue: scanner.scanNext.showScanQueue, + cooldown: cooldownSeconds, + refreshQueue: scanner.backendConfig.queueRefreshInterval, + enabled: modeConfig.enabled, + } } return null }, @@ -639,18 +647,24 @@ const resolvers = { if (category === 'getQueue') { return scannerApi(category, method, data, req?.user) } + const bypassCooldown = perms?.scannerCooldownBypass?.includes(category) + const cooldownExpired = + !req.session.cooldown || req.session.cooldown < Date.now() + if ( perms?.scanner?.includes(category) && - (!req.session.cooldown || req.session.cooldown < Date.now()) + (bypassCooldown || cooldownExpired) ) { const validCoords = getValidCoords(category, data?.scanCoords, perms) - const cooldown = - config.getSafe(`scanner.${category}.userCooldownSeconds`) * - validCoords.filter(Boolean).length * - 1000 + - Date.now() - req.session.cooldown = cooldown + if (!bypassCooldown) { + const cooldown = + config.getSafe(`scanner.${category}.userCooldownSeconds`) * + validCoords.filter(Boolean).length * + 1000 + + Date.now() + req.session.cooldown = cooldown + } return scannerApi( category, method, diff --git a/server/src/routes/rootRouter.js b/server/src/routes/rootRouter.js index a2feb1428..a7f76b84c 100644 --- a/server/src/routes/rootRouter.js +++ b/server/src/routes/rootRouter.js @@ -149,6 +149,7 @@ rootRouter.get('/api/settings', async (req, res, next) => { !scanner[key].discordRoles.length && !scanner[key].telegramGroups.length, ), + scannerCooldownBypass: [], } authentication.alwaysEnabledPerms.forEach((perm) => { if (authentication.perms[perm]) { diff --git a/server/src/services/DiscordClient.js b/server/src/services/DiscordClient.js index 036e986ad..4591cda1a 100644 --- a/server/src/services/DiscordClient.js +++ b/server/src/services/DiscordClient.js @@ -8,7 +8,7 @@ const config = require('@rm/config') const { logUserAuth } = require('./logUserAuth') const { areaPerms } = require('../utils/areaPerms') const { webhookPerms } = require('../utils/webhookPerms') -const { scannerPerms } = require('../utils/scannerPerms') +const { scannerPerms, scannerCooldownBypass } = require('../utils/scannerPerms') const { mergePerms } = require('../utils/mergePerms') const { AuthClient } = require('./AuthClient') const { state } = require('./state') @@ -128,6 +128,7 @@ class DiscordClient extends AuthClient { areaRestrictions: new Set(), webhooks: new Set(), scanner: new Set(), + scannerCooldownBypass: new Set(), blockedGuildNames: new Set(), } const scanner = config.getSafe('scanner') @@ -141,9 +142,12 @@ class DiscordClient extends AuthClient { Object.keys(this.perms).forEach((key) => (perms[key] = true)) perms.admin = true config.getSafe('webhooks').forEach((x) => permSets.webhooks.add(x.name)) - Object.keys(scanner).forEach( - (x) => scanner[x]?.enabled && permSets.scanner.add(x), - ) + Object.keys(scanner).forEach((x) => { + if (scanner[x]?.enabled) { + permSets.scanner.add(x) + permSets.scannerCooldownBypass.add(x) + } + }) this.log.debug( `User ${user.username} (${user.id}) in allowed users list, skipping guild and role check.`, ) @@ -197,6 +201,9 @@ class DiscordClient extends AuthClient { scannerPerms(userRoles, 'discordRoles', trialActive).forEach( (x) => permSets.scanner.add(x), ) + scannerCooldownBypass(userRoles, 'discordRoles').forEach((x) => + permSets.scannerCooldownBypass.add(x), + ) } }), ) diff --git a/server/src/services/LocalClient.js b/server/src/services/LocalClient.js index d7e4e44c4..c8f5615b2 100644 --- a/server/src/services/LocalClient.js +++ b/server/src/services/LocalClient.js @@ -7,7 +7,7 @@ const config = require('@rm/config') const { areaPerms } = require('../utils/areaPerms') const { webhookPerms } = require('../utils/webhookPerms') -const { scannerPerms } = require('../utils/scannerPerms') +const { scannerPerms, scannerCooldownBypass } = require('../utils/scannerPerms') const { mergePerms } = require('../utils/mergePerms') const { AuthClient } = require('./AuthClient') const { state } = require('./state') @@ -47,6 +47,7 @@ class LocalClient extends AuthClient { areaRestrictions: areaPerms(localPerms), webhooks: [], scanner: [], + scannerCooldownBypass: [], }), rmStrategy: this.rmStrategy, } @@ -120,6 +121,9 @@ class LocalClient extends AuthClient { scannerPerms([user.status], 'local', trialActive).forEach((x) => user.perms.scanner.push(x), ) + scannerCooldownBypass([user.status], 'local').forEach((x) => + user.perms.scannerCooldownBypass.push(x), + ) this.log.info( user.username, `(${user.id})`, diff --git a/server/src/services/TelegramClient.js b/server/src/services/TelegramClient.js index ea951f48e..55d9eb8cb 100644 --- a/server/src/services/TelegramClient.js +++ b/server/src/services/TelegramClient.js @@ -8,7 +8,7 @@ const config = require('@rm/config') const { state } = require('./state') const { areaPerms } = require('../utils/areaPerms') const { webhookPerms } = require('../utils/webhookPerms') -const { scannerPerms } = require('../utils/scannerPerms') +const { scannerPerms, scannerCooldownBypass } = require('../utils/scannerPerms') const { mergePerms } = require('../utils/mergePerms') const { AuthClient } = require('./AuthClient') @@ -102,6 +102,7 @@ class TelegramClient extends AuthClient { areaRestrictions: areaPerms(groups), webhooks: webhookPerms(groups, 'telegramGroups', trialActive), scanner: scannerPerms(groups, 'telegramGroups', trialActive), + scannerCooldownBypass: scannerCooldownBypass(groups, 'telegramGroups'), }, } if (newUserObj.perms.trial) { diff --git a/server/src/utils/mergePerms.js b/server/src/utils/mergePerms.js index c578593be..3e13abf31 100644 --- a/server/src/utils/mergePerms.js +++ b/server/src/utils/mergePerms.js @@ -6,14 +6,24 @@ * @param {import("@rm/types").Permissions} incomingPerms */ function mergePerms(existingPerms, incomingPerms) { + const keys = new Set([ + ...Object.keys(existingPerms), + ...Object.keys(incomingPerms), + ]) + return /** @type {import("@rm/types").Permissions} */ ( Object.fromEntries( - Object.keys(existingPerms).map((key) => [ - key, - Array.isArray(existingPerms[key]) - ? [...new Set([...existingPerms[key], ...incomingPerms[key]])] - : existingPerms[key] || incomingPerms[key], - ]), + [...keys].map((key) => { + const existingValue = existingPerms[key] + const incomingValue = incomingPerms[key] + + return [ + key, + Array.isArray(existingValue) || Array.isArray(incomingValue) + ? [...new Set([...(existingValue || []), ...(incomingValue || [])])] + : existingValue || incomingValue, + ] + }), ) ) } diff --git a/server/src/utils/scannerPerms.js b/server/src/utils/scannerPerms.js index bcfbdbaa0..c6582eb85 100644 --- a/server/src/utils/scannerPerms.js +++ b/server/src/utils/scannerPerms.js @@ -28,4 +28,30 @@ function scannerPerms(roles, provider, trialActive = false) { return [...new Set(perms)] } -module.exports = { scannerPerms } +/** + * Determine which scanner modes should bypass the cooldown for a given role set. + * + * @param {string[]} roles + * @param {'discordRoles' | 'telegramGroups' | 'local'} provider + * @returns {string[]} + */ +function scannerCooldownBypass(roles, provider) { + const scanner = config.getSafe('scanner') + + const bypass = [] + roles.forEach((role) => { + Object.keys(scanner).forEach((mode) => { + const bypassRoles = scanner[mode]?.cooldownBypass?.[provider] + if ( + scanner[mode]?.enabled && + Array.isArray(bypassRoles) && + bypassRoles.includes(role) + ) { + bypass.push(mode) + } + }) + }) + return [...new Set(bypass)] +} + +module.exports = { scannerPerms, scannerCooldownBypass }