├── .dockerignore ├── .gitattributes ├── .gitignore ├── Dockerfile ├── README.md ├── _mock_fetched_data_full.json ├── index.ts ├── package.json ├── pnpm-lock.yaml ├── raspberry.rigwild.dev.nginx.conf ├── screenshot-live.png ├── screenshot.png └── server.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | _mock_fetched_data_full.json linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | run*.sh 3 | dist 4 | _cached_request_data.json 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:19-alpine AS base 2 | RUN npm i -g pnpm 3 | 4 | 5 | FROM base AS dependencies 6 | WORKDIR /app 7 | COPY package.json pnpm-lock.yaml ./ 8 | RUN pnpm install 9 | 10 | 11 | FROM base AS build 12 | WORKDIR /app 13 | COPY . . 14 | COPY --from=dependencies /app/node_modules ./node_modules 15 | RUN pnpm build && pnpm prune --prod 16 | 17 | 18 | FROM node:19-alpine AS deploy 19 | WORKDIR /app 20 | COPY --from=build /app/dist ./dist 21 | COPY --from=build /app/node_modules ./node_modules 22 | COPY package.json ./ 23 | CMD ["node", "dist/index.js"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Raspberry instock check bot 2 | 3 | Get an alert on Telegram when there are Raspberry in stock ready to buy. This bot uses [rpilocator](https://rpilocator.com/) to check for stock updates. 4 | 5 | Join the public Telegram channel to get notifications! [@raspberry_alert](https://t.me/raspberry_alert/35) 6 | 7 | We also mirror the content of the [rpilocator](https://rpilocator.com/) website on this public API endpoint: [raspberry.rigwild.dev](https://raspberry.rigwild.dev) 8 | 9 | 🌟 Star the project if we helped you get a Raspberry! 🙏 10 | 11 | ## Features 12 | 13 | - Send an alert when a new Raspberry is in stock 14 | 15 | ![screenshot](./screenshot.png) 16 | 17 | - Live update a message with currently in stock Raspberry 18 | 19 | ![screenshot live update](./screenshot-live.png) 20 | 21 | - API showing current rpilocator stock 22 | 23 | ## Install 24 | 25 | ```sh 26 | pnpm i 27 | ``` 28 | 29 | ## Build 30 | 31 | ```sh 32 | pnpm build 33 | ``` 34 | 35 | ## Run 36 | 37 | ```sh 38 | TELEGRAM_TOKEN= \ 39 | TELEGRAM_ADMIN_CHAT_ID= \ 40 | TELEGRAM_CHAT_ID= \ 41 | TELEGRAM_CURRENTLY_IN_STOCK_MESSAGE_ID= \ 42 | USE_DIRECT_PRODUCT_LINK=1 \ 43 | SEARCHED_RASPBERRY_MODELS=RPI4-MODBP-4GB,RPI4-MODBP-8GB \ 44 | PROXY=http://user:pass@123.123.123.123:51234 \ 45 | CHECK_INTERVAL=30000 \ 46 | pnpm start 47 | ``` 48 | 49 | | Environment variable | Required | Description | 50 | | ---------------------------------------- | :------: | -------------------------------------------------------------------------------------------------- | 51 | | `TELEGRAM_TOKEN` | ✅ | Telegram bot token | 52 | | `TELEGRAM_ADMIN_CHAT_ID` | ✅ | Telegram chat id where to send error messages (can be the same as `TELEGRAM_CHAT_ID`) | 53 | | `TELEGRAM_CHAT_ID` | ✅ | Telegram chat id where to send stock alerts and update the live stock update message | 54 | | `TELEGRAM_CURRENTLY_IN_STOCK_MESSAGE_ID` | | Telegram message id to update with the current stock | 55 | | `USE_DIRECT_PRODUCT_LINK` | | Should the products links be direct product links? (if `0`, will send rpilocator link) | 56 | | `USE_CACHED_REQUEST` | | Load data from file system instead of sending requests (use if you have one other checker running) | 57 | | `SEARCHED_RASPBERRY_MODELS` | | List of Raspberry models to look for, separated by a `,`. If omitted, will look for all models | 58 | | `PROXY` | | Proxy to use to fetch data from rpilocator | 59 | | `CHECK_INTERVAL` | | Check interval in ms, checking too often might get you rate-limited (default is `60000`) | 60 | | `API_RUN` | | Start the API server | 61 | | `API_PORT` | | API server port | 62 | | `API_TRUST_PROXY` | | Enable if server is ran behind a reverse proxy, for the rate limit | 63 | 64 | To get the `TELEGRAM_CURRENTLY_IN_STOCK_MESSAGE_ID`: 65 | 66 | - Right click on the message you want to be updated (message must be in the `TELEGRAM_CHAT_ID` channel) 67 | - Copy message link 68 | - Take the number at the end 69 | 70 | To get a Telegram chat id: 71 | 72 | - Use [@RawDataBot](https://stackoverflow.com/a/46247058) 73 | - If it's a public channel, use the public @: `TELEGRAM_CHAT_ID='@raspberry_alert'` 74 | 75 | ## Run with auto-restart 76 | 77 | Use [PM2](https://pm2.keymetrics.io/) 78 | 79 | ```sh 80 | pnpm build 81 | TELEGRAM_TOKEN= pm2 start dist/index.js 82 | ``` 83 | 84 | See logs 85 | 86 | ```sh 87 | pm2 logs 88 | ``` 89 | 90 | Kill 91 | 92 | ```sh 93 | pm2 delete all 94 | ``` 95 | 96 | ## Run with Docker 97 | 98 | Use [Docker](https://docker.com) 99 | 100 | ```sh 101 | docker build -t raspberry-instock-check . 102 | docker run -d -e TELEGRAM_TOKEN='...' -e TELEGRAM_CHAT_ID='...' -e TELEGRAM_ADMIN_CHAT_ID='...' raspberry-instock-check 103 | ``` 104 | 105 | ## Test 106 | 107 | To simulate some alerts to see if it's working, set the environment variable `NODE_ENV=test`. 108 | 109 | ## List of Raspberry models 110 | 111 | | Model (SKU) | Description | 112 | | ---------------- | -------------------------------------- | 113 | | `CM3-Lite` | RPi CM3 - 1GB RAM, No MMC | 114 | | `CM3+16GB` | RPi CM3+ - 1GB RAM, 16GB MMC | 115 | | `CM3+32GB` | RPi CM3+ - 1GB RAM, 32GB MMC | 116 | | `CM3+8GB` | RPi CM3+ - 1GB RAM, 8GB MMC | 117 | | `CM3+Lite` | RPi CM3+ - 1GB RAM, No MMC | 118 | | `CM4001000` | RPi CM4 - 1GB RAM, No MMC, No Wifi | 119 | | `CM4001008` | RPi CM4 - 1GB RAM, 8GB MMC, No Wifi | 120 | | `CM4001016` | RPi CM4 - 1GB RAM, 16GB MMC, No Wifi | 121 | | `CM4001032` | RPi CM4 - 1GB RAM, 32GB MMC, No Wifi | 122 | | `CM4002000` | RPi CM4 - 2GB RAM, No MMC, No Wifi | 123 | | `CM4002008` | RPi CM4 - 2GB RAM, 8GB MMC, No Wifi | 124 | | `CM4002016` | RPi CM4 - 2GB RAM, 16GB MMC, No Wifi | 125 | | `CM4002032` | RPi CM4 - 2GB RAM, 32GB MMC, No Wifi | 126 | | `CM4004000` | RPi CM4 - 4GB RAM, No MMC, No Wifi | 127 | | `CM4004008` | RPi CM4 - 4GB RAM, 8GB MMC, No Wifi | 128 | | `CM4004016` | RPi CM4 - 4GB RAM, 16GB MMC, No Wifi | 129 | | `CM4004032` | RPi CM4 - 4GB RAM, 32GB MMC, No Wifi | 130 | | `CM4008000` | RPi CM4 - 8GB RAM, No MMC, No Wifi | 131 | | `CM4008008` | RPi CM4 - 8GB RAM, 8GB MMC, No Wifi | 132 | | `CM4008016` | RPi CM4 - 8GB RAM, 16GB MMC, No Wifi | 133 | | `CM4008032` | RPi CM4 - 8GB RAM, 32GB MMC, No Wifi | 134 | | `CM4101000` | RPi CM4 - 1GB RAM, No MMC, With Wifi | 135 | | `CM4101008` | RPi CM4 - 1GB RAM, 8GB MMC, With Wifi | 136 | | `CM4101016` | RPi CM4 - 1GB RAM, 16GB MMC, With Wifi | 137 | | `CM4101032` | RPi CM4 - 1GB RAM, 32GB MMC, With Wifi | 138 | | `CM4102000` | RPi CM4 - 2GB RAM, No MMC, With Wifi | 139 | | `CM4102008` | RPi CM4 - 2GB RAM, 8GB MMC, With Wifi | 140 | | `CM4102016` | RPi CM4 - 2GB RAM, 16GB MMC, With Wifi | 141 | | `CM4102032` | RPi CM4 - 2GB RAM, 32GB MMC, With Wifi | 142 | | `CM4104000` | RPi CM4 - 4GB RAM, No MMC, With Wifi | 143 | | `CM4104008` | RPi CM4 - 4GB RAM, 8GB MMC, With Wifi | 144 | | `CM4104016` | RPi CM4 - 4GB RAM, 16GB MMC, With Wifi | 145 | | `CM4104032` | RPi CM4 - 4GB RAM, 32GB MMC, With Wifi | 146 | | `CM4108000` | RPi CM4 - 8GB RAM, No MMC, With Wifi | 147 | | `CM4108008` | RPi CM4 - 8GB RAM, 8GB MMC, With Wifi | 148 | | `CM4108016` | RPi CM4 - 8GB RAM, 16GB MMC, With Wifi | 149 | | `CM4108032` | RPi CM4 - 8GB RAM, 32GB MMC, With Wifi | 150 | | `RPI3-MODAP` | RPi 3 Model A+ - 512MB RAM | 151 | | `RPI3-MODB` | RPi 3 Model B - 1GB RAM | 152 | | `RPI3-MODBP` | RPi 3 Model B+ - 1GB RAM | 153 | | `RPI4-MODBP-1GB` | RPi 4 Model B - 1GB RAM | 154 | | `RPI4-MODBP-2GB` | RPi 4 Model B - 2GB RAM | 155 | | `RPI4-MODBP-4GB` | RPi 4 Model B - 4GB RAM | 156 | | `RPI4-MODBP-8GB` | RPi 4 Model B - 8GB RAM | 157 | | `SC0020` | Raspberry Pi Zero W | 158 | | `SC0020WH` | Raspberry Pi Zero W (w/ headers) | 159 | | `SC0510` | Raspberry Pi Zero 2 W | 160 | | `SC0510WH` | Raspberry Pi Zero 2 W (w/ headers) | 161 | 162 | ## License 163 | 164 | ``` 165 | Copyright (c) rigwild 166 | 167 | This license is granted to everyone except for the following entities and 168 | any of their subsidiaries: 169 | 170 | - "rpilocator" 171 | - "camerahacks" 172 | - "DPHacks" 173 | 174 | MIT License 175 | 176 | Permission is hereby granted, free of charge, to any person obtaining a copy 177 | of this software and associated documentation files (the "Software"), to deal 178 | in the Software without restriction, including without limitation the rights 179 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 180 | copies of the Software, and to permit persons to whom the Software is 181 | furnished to do so, subject to the following conditions: 182 | 183 | The above copyright notice and this permission notice shall be included in all 184 | copies or substantial portions of the Software. 185 | 186 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 187 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 188 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 189 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 190 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 191 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 192 | SOFTWARE. 193 | ``` 194 | -------------------------------------------------------------------------------- /_mock_fetched_data_full.json: -------------------------------------------------------------------------------- 1 | { 2 | "lastUpdate": "2022-06-29T18:35:15.869Z", 3 | "_data": [ 4 | { 5 | "update_t": { 6 | "sort": 2, 7 | "display": "2 min." 8 | }, 9 | "price": { 10 | "sort": 41.9, 11 | "display": "41.90", 12 | "currency": "CHF" 13 | }, 14 | "vendor": "Pi-Shop (CH)", 15 | "sku": "RPI3-MODAP", 16 | "avail": "Yes", 17 | "link": "https://www.pi-shop.ch/raspberry-pi-3-model-a", 18 | "last_stock": { 19 | "sort": "2022-06-29", 20 | "display": "29-Jun-22" 21 | }, 22 | "description": "RPi 3 Model A+ - 512MB RAM" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | import TelegramBot from 'node-telegram-bot-api' 3 | import { readFileSync, writeFileSync, existsSync } from 'fs' 4 | import HttpsProxyAgentImport from 'https-proxy-agent' 5 | const { HttpsProxyAgent } = HttpsProxyAgentImport 6 | import { startServer } from './server.js' 7 | 8 | const SEARCHED_RASPBERRY_MODELS = process.env.SEARCHED_RASPBERRY_MODELS 9 | ? process.env.SEARCHED_RASPBERRY_MODELS.trim().toLowerCase().split(',') 10 | : ['*'] 11 | const CHECK_INTERVAL = process.env.CHECK_INTERVAL ? +process.env.CHECK_INTERVAL : 60_000 12 | 13 | const TELEGRAM_TOKEN = process.env.TELEGRAM_TOKEN! 14 | const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID! 15 | const TELEGRAM_LIVE_STOCK_UPDATE_MESSAGE_ID = process.env.TELEGRAM_LIVE_STOCK_UPDATE_MESSAGE_ID 16 | ? +process.env.TELEGRAM_LIVE_STOCK_UPDATE_MESSAGE_ID 17 | : undefined 18 | const TELEGRAM_ADMIN_CHAT_ID = process.env.TELEGRAM_ADMIN_CHAT_ID! 19 | const USE_DIRECT_PRODUCT_LINK = process.env.USE_DIRECT_PRODUCT_LINK === '1' 20 | const USE_CACHED_REQUEST = process.env.USE_CACHED_REQUEST === '1' 21 | const API_RUN = process.env.API_RUN === '1' 22 | 23 | const PROXY = process.env.PROXY 24 | 25 | type Raspberry = { 26 | update_t: { sort: number; display: string } 27 | price: { sort: number; display: string; currency: string } 28 | vendor: string 29 | sku: string 30 | avail: string 31 | link: string 32 | last_stock: { sort: string; display: string } 33 | description: string 34 | } 35 | 36 | const USER_AGENTS = [ 37 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', 38 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', 39 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', 40 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0', 41 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0', 42 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', 43 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', 44 | 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/112.0', 45 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15', 46 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/112.0', 47 | 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36', 48 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 OPR/97.0.0.0', 49 | 'Mozilla/5.0 (Windows NT 10.0; rv:112.0) Gecko/20100101 Firefox/112.0', 50 | ] 51 | 52 | if ( 53 | process.env.NODE_ENV !== 'test' && 54 | process.env.NODE_ENV !== 'development' && 55 | !USE_CACHED_REQUEST && 56 | CHECK_INTERVAL < 25_000 57 | ) 58 | throw new Error('CHECK_INTERVAL must be at least 25000 ms') 59 | 60 | const pickRandom = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)] 61 | 62 | let isFirstInit = true 63 | let rpilocatorCookies: string 64 | let currentUserAgent: string 65 | const raspberryAvailableCache = new Map() 66 | 67 | let lastCookiesRefresh = Date.now() 68 | const COOKIES_REFRESH_INTERVAL = 3 * 60 * 1000 // 3 minutes 69 | 70 | /** 71 | * List of errors when fetching data from rpilocator 72 | * When `ERRORS_SKIP_THRESOLD` is reached in a time window of `ERRORS_SKIP_TIME_WINDOW`, 73 | * skip the next `ERRORS_SKIP_CYCLES` fetch cycles, then reset 74 | */ 75 | let fetchErrors: Date[] = [] 76 | let fetchErrorsSkipCyclesLeft = 0 77 | const ERRORS_SKIP_THRESOLD = 5 78 | const ERRORS_SKIP_TIME_WINDOW = 5 * 60_000 // Look at last 5 minutes 79 | const hasReachedErrorsSkipThresold = () => { 80 | const now = Date.now() 81 | // Remove errors that are outside the time window 82 | fetchErrors = fetchErrors.filter(x => x.getTime() > now - ERRORS_SKIP_TIME_WINDOW) 83 | return fetchErrors.length >= ERRORS_SKIP_THRESOLD 84 | } 85 | const ERRORS_SKIP_CYCLES = () => 4 + (Math.floor(Math.random() * 10) % 11) // 4 <= x <= 13 86 | 87 | // Save the sent messages to udpate them when becomes unavailable 88 | type StockMessageContent = { 89 | telegramMessage: TelegramBot.Message 90 | raspberryAvailable: Map 91 | raspberryUnavailable: Map 92 | } 93 | const lastStockMessagesIds = new Map() 94 | const lastStockMessagesContent = new Map() 95 | 96 | const vendors = { 97 | '330ohms (MX)': '330ohms', 98 | 'Adafruit (US)': 'adafruit', 99 | 'Argon 40 (CN)': 'argon40', 100 | 'BerryBase (DE)': 'berrybase', 101 | 'Botland (PL)': 'botland', 102 | 'Chicago Elec. Dist. (US)': 'chicagodist', 103 | 'Cool Components (UK)': 'coolcomp', 104 | 'Digi-Key (US)': 'digikeyus', 105 | 'electro:kit (SE)': 'electrokit', 106 | 'Elektor (NL)': 'elektor', 107 | 'Elektronica Voor Jou (NL)': 'elektronica', 108 | 'Farnell (UK)': 'farnell', 109 | 'Jkollerup.dk (DK)': 'jkollerup', 110 | 'Kamami (PL)': 'kamami', 111 | 'Kiwi Elec. (NL)': 'kiwinl', 112 | 'Kubii (FR)': 'kubii', 113 | 'MC Hobby (BE)': 'mchobby', 114 | 'Melopero (IT)': 'melopero', 115 | 'Newark (US)': 'newark', 116 | 'Pi Australia (AU)': 'piaustralia', 117 | 'Pi-Shop (CH)': 'pishopch', 118 | 'pi3g (DE)': 'pi3g', 119 | 'Pimoroni (UK)': 'pimoroni', 120 | 'Pishop (CA)': 'pishopca', 121 | 'Pishop (US)': 'pishopus', 122 | 'PiShop (ZA)': 'pishopza', 123 | 'Rapid (UK)': 'rapid', 124 | 'RaspberryStore (NL)': 'raspberrystore', 125 | 'Rasppishop (DE)': 'rasppishop', 126 | 'Reichelt (DE)': 'reichelt', 127 | 'Robert Mauser (PT)': 'mauserpt', 128 | 'Robox (MA)': 'robox', 129 | 'SAMM Market (TR)': 'samm', 130 | 'Seeedstudio (CN)': 'seeedstudio', 131 | 'Semaf (AT)': 'semaf', 132 | 'The Pi Hut (UK)': 'thepihut', 133 | 'Thingbits (IN)': 'thingbits', 134 | 'Tiendatec (ES)': 'tiendatec', 135 | 'Welectron (DE)': 'welectron', 136 | 'Yadom (FR)': 'yadom', 137 | } 138 | 139 | let debugRound = 0 140 | 141 | const bot = new TelegramBot(TELEGRAM_TOKEN) 142 | const searchedRaspberryStr = 143 | SEARCHED_RASPBERRY_MODELS?.[0] === '*' ? ' All' : `\n${SEARCHED_RASPBERRY_MODELS.map(x => `\`${x}\``).join('\n')}` 144 | bot.sendMessage( 145 | TELEGRAM_ADMIN_CHAT_ID, 146 | `Bot started! ⚡` + 147 | `\nLooking for models SKU starting with: ${searchedRaspberryStr}` + 148 | (PROXY ? `\nUsing proxy: ${new URL(PROXY).hostname}:${new URL(PROXY).port}` : '') + 149 | `\n🌟 Star on [GitHub](https://github.com/rigwild/raspberry-instock-check)`, 150 | { parse_mode: 'Markdown' } 151 | ) 152 | // .then(res => console.log(res.message_id)) 153 | 154 | const getRpilocatorTokenAndCookies = async () => { 155 | console.log('Getting new rpilocator token and cookies') 156 | 157 | currentUserAgent = pickRandom(USER_AGENTS) 158 | rpilocatorCookies = '' 159 | 160 | const reqHome = await fetch('https://rpilocator.com/', { 161 | headers: { 162 | 'User-Agent': currentUserAgent, 163 | Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 164 | 'Accept-Language': 'en-US,en;q=0.5', 165 | 'Upgrade-Insecure-Requests': '1', 166 | 'Sec-Fetch-Dest': 'document', 167 | 'Sec-Fetch-Mode': 'navigate', 168 | 'Sec-Fetch-Site': 'none', 169 | 'Sec-Fetch-User': '?1', 170 | }, 171 | agent: PROXY ? new HttpsProxyAgent(PROXY) : undefined, 172 | }) 173 | 174 | // prettier-ignore 175 | rpilocatorCookies = reqHome.headers.raw()['set-cookie'].map(x => x.split(';')[0]).join('; ') 176 | console.log('rpilocatorCookies', rpilocatorCookies) 177 | console.log('currentUserAgent', currentUserAgent) 178 | } 179 | 180 | const getRaspberryList = async (): Promise => { 181 | if (process.env.NODE_ENV === 'test' || USE_CACHED_REQUEST) { 182 | // Load from file system cache instead of fetching from rpilocator 183 | let fileName = '_mock_fetched_data_full.json' 184 | if (USE_CACHED_REQUEST) fileName = './_cached_request_data.json' 185 | 186 | let filePath = new URL(fileName, import.meta.url) 187 | if (!existsSync(filePath)) filePath = new URL(`../${fileName}`, filePath) 188 | if (!existsSync(filePath)) 189 | throw new Error('Cached request file not found! Start your other checker instance first!') 190 | 191 | return JSON.parse(readFileSync(filePath, { encoding: 'utf-8' }))._data 192 | } 193 | 194 | // Refresh cookies 195 | if (!rpilocatorCookies || Date.now() - lastCookiesRefresh > COOKIES_REFRESH_INTERVAL) { 196 | await getRpilocatorTokenAndCookies() 197 | lastCookiesRefresh = Date.now() 198 | } 199 | 200 | // Fetch stock data 201 | let reqData: Awaited> 202 | try { 203 | reqData = await fetch(`https://rpilocator.com/data.cfm?method=getProductTable&instock&&_=${Date.now()}`, { 204 | headers: { 205 | 'User-Agent': currentUserAgent, 206 | Accept: 'application/json, text/javascript, */*; q=0.01', 207 | 'Accept-Language': 'en-US,en;q=0.5', 208 | 'X-Requested-With': 'XMLHttpRequest', 209 | 'Alt-Used': 'rpilocator.com', 210 | 'Sec-Fetch-Dest': 'empty', 211 | 'Sec-Fetch-Mode': 'cors', 212 | 'Sec-Fetch-Site': 'same-origin', 213 | cookie: rpilocatorCookies, 214 | referer: 'https://rpilocator.com/', 215 | }, 216 | agent: PROXY ? new HttpsProxyAgent(PROXY) : undefined, 217 | }) 218 | } catch (error) { 219 | // If DNS error, log error but do not alert telegram admin 220 | if (error.message.includes('getaddrinfo EAI_AGAIN')) { 221 | console.error(error) 222 | return null as any 223 | } 224 | throw error 225 | } 226 | 227 | // if (reqData.status === 403) { 228 | // // Try to get a new token and retry in 10s 229 | // await new Promise(resolve => setTimeout(resolve, 3000)) 230 | // await getRpilocatorTokenAndCookies() 231 | // return getRaspberryList() 232 | // } 233 | 234 | if (!reqData.ok) 235 | throw new Error(`Failed to fetch API data! - Status ${reqData.status}\n${(await reqData.text()).slice(0, 4000)}`) 236 | 237 | let raspberryList: Raspberry[] 238 | let raspberryListJson = await reqData.text() 239 | try { 240 | // writeFileSync(new URL(`log-${Date.now()}.html`, import.meta.url), raspberryListJson) 241 | raspberryList = JSON.parse(raspberryListJson).data.sort((a, b) => 242 | getRaspberryKey(a).localeCompare(getRaspberryKey(b)) 243 | ) 244 | } catch (error) { 245 | if (process.env.NODE_ENV === 'development') { 246 | console.log(reqData.status, reqData.statusText) 247 | console.log(rpilocatorCookies) 248 | writeFileSync(new URL(`log-${Date.now()}.html`, import.meta.url), raspberryListJson) 249 | } 250 | throw new Error(`API data was not JSON!\n${raspberryListJson.slice(0, 2000)}`) 251 | } 252 | return raspberryList 253 | } 254 | 255 | const updateRapsberryCache = (raspberryList: Raspberry[]) => { 256 | raspberryList = raspberryList.filter(r => r.avail === 'Yes') 257 | if (SEARCHED_RASPBERRY_MODELS?.[0] !== '*') 258 | raspberryList = raspberryList.filter(r => 259 | SEARCHED_RASPBERRY_MODELS.some(model => r.sku.toLowerCase().startsWith(model)) 260 | ) 261 | 262 | // Mock data for testing 263 | if (process.env.NODE_ENV === 'test') { 264 | const mock1 = { 265 | sku: 'RPI4-MODBP-4GB', 266 | description: 'RPi 4 Model B - 4GB RAM', 267 | vendor: 'electro:kit (SE)', 268 | price: { display: '719.00', currency: 'SEK' }, 269 | link: 'https://www.pi-shop.ch/raspberry-pi-3-model-a', 270 | } 271 | const mock2 = { 272 | sku: 'CM4104000', 273 | description: 'RPi CM4 - 4GB RAM, No MMC, With Wifi', 274 | vendor: 'Welectron (DE)', 275 | price: { display: '64.90', currency: 'EUR' }, 276 | link: 'https://www.pi-shop.ch/raspberry-pi-3-model-a', 277 | } 278 | if (debugRound === 1) { 279 | raspberryList.push(mock1 as any) 280 | // raspberryList.push(mock2 as any) 281 | } 282 | if (debugRound === 2) { 283 | // raspberryList.push(mock1 as any) 284 | // raspberryList.push(mock2 as any) 285 | } 286 | if (debugRound === 3) { 287 | raspberryList.push(mock1 as any) 288 | raspberryList.push(mock2 as any) 289 | } 290 | if (debugRound === 4) { 291 | // raspberryList.push(mock1 as any) 292 | raspberryList.push(mock2 as any) 293 | } 294 | if (debugRound === 5) { 295 | // raspberryList.push(mock1 as any) 296 | // raspberryList.push(mock2 as any) 297 | } 298 | } 299 | const raspberryAvailable = new Map() as typeof raspberryAvailableCache 300 | raspberryList.forEach(raspberry => raspberryAvailable.set(getRaspberryKey(raspberry), raspberry)) 301 | 302 | const raspberryListWithChanges = { 303 | nowAvailableRaspberry: new Map(), 304 | nowUnavailableRaspberry: new Map(), 305 | } 306 | 307 | // Do not alert on first lauch (startup), only fill the cache 308 | if (isFirstInit) { 309 | ;[...raspberryAvailable.entries()].forEach(([raspberryKey, raspberry]) => 310 | raspberryAvailableCache.set(raspberryKey, raspberry) 311 | ) 312 | isFirstInit = false 313 | return raspberryListWithChanges 314 | } 315 | 316 | // Find the raspberrys that are available now but were not before 317 | ;[...raspberryAvailable.entries()].forEach(([raspberryKey, raspberry]) => { 318 | if (!raspberryAvailableCache.has(raspberryKey)) 319 | raspberryListWithChanges.nowAvailableRaspberry.set(raspberryKey, raspberry) 320 | }) 321 | 322 | // Find the raspberrys that are not available now but were before 323 | ;[...raspberryAvailableCache.entries()] 324 | .filter(([raspberryKey, raspberry]) => !raspberryAvailable.has(raspberryKey)) 325 | .forEach(([raspberryKey, raspberry]) => 326 | raspberryListWithChanges.nowUnavailableRaspberry.set(raspberryKey, raspberry) 327 | ) 328 | 329 | // Update the raspberry cache 330 | raspberryAvailableCache.clear() 331 | ;[...raspberryAvailable.entries()].forEach(([raspberryKey, raspberry]) => 332 | raspberryAvailableCache.set(raspberryKey, raspberry) 333 | ) 334 | 335 | return raspberryListWithChanges 336 | } 337 | 338 | const getRaspberryLink = (r: Raspberry) => { 339 | let itemLink = r.link 340 | if (!USE_DIRECT_PRODUCT_LINK) { 341 | itemLink = `https://rpilocator.com/?utm_source=telegram&utm_medium=rapsberry_alert` 342 | if (vendors[r.vendor]) itemLink += `&vendor=${vendors[r.vendor]}` 343 | } 344 | return `[${r.description} | ${r.vendor} | ${r.price.display} ${r.price.currency}](${itemLink})` 345 | } 346 | 347 | const getRaspberryKey = (r: Raspberry) => `${r.sku}-${r.vendor}-${r.price.display}` 348 | 349 | const twoDigits = (serializable: any) => serializable.toString().padStart(2, '0') 350 | 351 | /** @see https://gist.github.com/rigwild/bf712322eac2244096468985ee4a5aae */ 352 | export const toHumanDateTime = (date: Date) => 353 | `${date.getFullYear()}-${twoDigits(date.getMonth() + 1)}-${twoDigits(date.getDate())} - ${twoDigits( 354 | date.getHours() 355 | )}:${twoDigits(date.getMinutes())}` 356 | 357 | /** Check if provided lists are identical, will deep copy and delete `update_t` key as it changes frequently */ 358 | const areIdentical = (raspberryList: Raspberry[], raspberryListDoubleCheck: Raspberry[]): boolean => { 359 | const a = JSON.parse(JSON.stringify(raspberryList)) 360 | const b = JSON.parse(JSON.stringify(raspberryListDoubleCheck)) 361 | a.forEach(r => delete r.update_t) 362 | b.forEach(r => delete r.update_t) 363 | return JSON.stringify(a) === JSON.stringify(b) 364 | } 365 | 366 | const getTelegramMessage = ( 367 | raspberryAvailabilities: ReturnType, 368 | nowAvailableRaspberryListLastStockMessagesKeys?: string[] 369 | ) => { 370 | let message = '🛍️ Raspberry stock changes!' 371 | 372 | if (raspberryAvailabilities.nowAvailableRaspberry.size > 0) { 373 | message += `\n\nNew Raspberry in stock! 🔥🔥\n` 374 | message += [...raspberryAvailabilities.nowAvailableRaspberry.values()] 375 | .map(r => { 376 | const raspberryKey = getRaspberryKey(r) 377 | if (nowAvailableRaspberryListLastStockMessagesKeys) { 378 | nowAvailableRaspberryListLastStockMessagesKeys.push(raspberryKey) 379 | } 380 | return `✅ ${getRaspberryLink(r)}` 381 | }) 382 | .join('\n') 383 | } 384 | 385 | if (raspberryAvailabilities.nowUnavailableRaspberry.size > 0) { 386 | message += `\n\nNow out of stock! 😔\n` 387 | message += [...raspberryAvailabilities.nowUnavailableRaspberry.values()] 388 | .map(r => `❌ ${getRaspberryLink(r)}`) 389 | .join('\n') 390 | } 391 | 392 | // message += `\n\nCurrently in stock:\n` 393 | // // Get links and remove duplicates 394 | // const links = new Set(raspberryAvailabilities.raspberryList.filter(r => r.available).map(r => getRaspberryLink(r))) 395 | // message += [...links].join('\n') 396 | 397 | message += '\n\n🌟 Star on [GitHub](https://github.com/rigwild/raspberry-instock-check)' 398 | message += `\n🌐 Stock data from [rpilocator](https://rpilocator.com/?utm_source=telegram&utm_medium=rapsberry_alert)` 399 | return message 400 | } 401 | 402 | const sendTelegramAlert = async (raspberryListWithChanges: ReturnType) => { 403 | const nowAvailableRaspberryListLastStockMessagesKeys = [] 404 | const message = getTelegramMessage(raspberryListWithChanges, nowAvailableRaspberryListLastStockMessagesKeys) 405 | console.log(raspberryListWithChanges.nowAvailableRaspberry) 406 | console.log(message) 407 | 408 | const sentMsg = await bot.sendMessage(TELEGRAM_CHAT_ID, message, { 409 | parse_mode: 'Markdown', 410 | disable_web_page_preview: true, 411 | }) 412 | 413 | // Record the message to update it later 414 | nowAvailableRaspberryListLastStockMessagesKeys.forEach(raspberryKey => { 415 | const raspberryAvailable = new Map() 416 | raspberryListWithChanges.nowAvailableRaspberry.forEach(raspberry => { 417 | raspberryAvailable.set(raspberryKey, raspberry) 418 | }) 419 | 420 | const messageContent = { 421 | telegramMessage: sentMsg, 422 | raspberryAvailable, 423 | raspberryUnavailable: new Map(), 424 | } 425 | lastStockMessagesIds.set(raspberryKey, sentMsg.message_id) 426 | lastStockMessagesContent.set(sentMsg.message_id, messageContent) 427 | 428 | // Delete key in 48 hours (avoid a dumb memory leak) 429 | setTimeout(() => { 430 | lastStockMessagesIds.delete(raspberryKey) 431 | lastStockMessagesContent.delete(sentMsg.message_id) 432 | }, 48 * 60 * 60 * 1000) 433 | }) 434 | } 435 | 436 | const updateTelegramAlert = async (raspberryListWithChanges: ReturnType) => { 437 | for (const raspberry of raspberryListWithChanges.nowUnavailableRaspberry.values()) { 438 | const raspberryKey = getRaspberryKey(raspberry) 439 | if (lastStockMessagesIds.has(raspberryKey)) { 440 | console.log(`Now unavailable: ${raspberryKey}`) 441 | const message_id = lastStockMessagesIds.get(raspberryKey)! 442 | const lastMessageContent = lastStockMessagesContent.get(message_id)! 443 | lastMessageContent.raspberryAvailable.delete(raspberryKey) 444 | lastMessageContent.raspberryUnavailable.set(raspberryKey, raspberry) 445 | const raspberryAvailabilities = { 446 | nowAvailableRaspberry: lastMessageContent.raspberryAvailable, 447 | nowUnavailableRaspberry: lastMessageContent.raspberryUnavailable, 448 | } 449 | lastMessageContent.telegramMessage.text = getTelegramMessage(raspberryAvailabilities) 450 | await bot.editMessageText(lastMessageContent.telegramMessage.text, { 451 | chat_id: TELEGRAM_CHAT_ID, 452 | message_id: lastMessageContent.telegramMessage.message_id, 453 | parse_mode: 'Markdown', 454 | disable_web_page_preview: true, 455 | }) 456 | } 457 | } 458 | } 459 | 460 | const checkStock = async () => { 461 | if (process.env.NODE_ENV === 'development') console.log(debugRound) 462 | 463 | if (fetchErrorsSkipCyclesLeft > 0) { 464 | console.log(`Too many errors, skipping - errorsSkipCyclesLeft: ${fetchErrorsSkipCyclesLeft}`) 465 | fetchErrorsSkipCyclesLeft-- 466 | return 467 | } else { 468 | // Just to make sure in case we have some wtf race condition 469 | fetchErrorsSkipCyclesLeft = 0 470 | } 471 | 472 | try { 473 | console.log('Checking stock...') 474 | 475 | // Do the request 2 times with a bit of delay and check the result is the same 476 | // Sometimes rpilocator returns invalid data (race condition when updating on their side) 477 | let [raspberryList, raspberryListDoubleCheck] = await Promise.all([ 478 | getRaspberryList(), 479 | new Promise(resolve => setTimeout(() => resolve(getRaspberryList()), 5000)) as Promise< 480 | ReturnType 481 | >, 482 | ]).catch(async e => { 483 | fetchErrors.push(new Date()) 484 | if (hasReachedErrorsSkipThresold()) { 485 | // Too many fetch errors in the time window, skip some fetch cycles 486 | const cyclesToSkip = ERRORS_SKIP_CYCLES() 487 | fetchErrorsSkipCyclesLeft = cyclesToSkip 488 | 489 | await bot.sendMessage(TELEGRAM_ADMIN_CHAT_ID, `⏳ Too many errors, skipping ${cyclesToSkip} check cycles!`) 490 | } 491 | throw e 492 | }) 493 | 494 | // Check both requests were succesful 495 | if ((raspberryList && !raspberryListDoubleCheck) || (!raspberryList && raspberryListDoubleCheck)) { 496 | console.error('One of the double check requests failed') 497 | return 498 | } 499 | 500 | // Both requests failed, log error 501 | if (!raspberryList && !raspberryListDoubleCheck) { 502 | const timestamp = Date.now() 503 | const url = new URL(`invalid-both-requests-failed-${timestamp}.json`, import.meta.url) 504 | 505 | const raspberryListJson = JSON.stringify(raspberryList) 506 | writeFileSync(url, raspberryListJson) 507 | throw new Error(`Failed double check, both requests failed - Content:\n${raspberryListJson.slice(0, 1000)}`) 508 | } 509 | 510 | // Check both requests are indeed identical 511 | if (!areIdentical(raspberryList, raspberryListDoubleCheck)) { 512 | const timestamp = Date.now() 513 | if (process.env.NODE_ENV === 'development') { 514 | const url1 = new URL(`invalid-double-check-${timestamp}-1.json`, import.meta.url) 515 | const url2 = new URL(`invalid-double-check-${timestamp}-2.json`, import.meta.url) 516 | writeFileSync(url1, JSON.stringify(raspberryList, null, 2)) 517 | writeFileSync(url2, JSON.stringify(raspberryListDoubleCheck, null, 2)) 518 | } 519 | console.error('Detected invalid data when double checking') 520 | return 521 | } 522 | 523 | // Blacklist some vendors because they change their stock or price too often 524 | const blacklistedVendors = ['SAMM Market (TR)', 'Jkollerup.dk (DK)'] 525 | raspberryList = raspberryList.filter(r => !blacklistedVendors.includes(r.vendor)) 526 | 527 | const raspberryListWithChanges = updateRapsberryCache(raspberryList) 528 | 529 | // Cache it on file system for other checker instances and API endpoint 530 | if (!USE_CACHED_REQUEST) { 531 | const apiData = { 532 | lastUpdate: new Date(), 533 | _data: raspberryList, 534 | } 535 | writeFileSync(new URL('../_cached_request_data.json', import.meta.url), JSON.stringify(apiData, null, 2)) 536 | } 537 | 538 | // console.log('nowAvailableRaspberry', raspberryListWithChanges.nowAvailableRaspberry) 539 | // console.log(raspberryListWithChanges) 540 | 541 | if (raspberryListWithChanges.nowAvailableRaspberry.size > 0) { 542 | await sendTelegramAlert(raspberryListWithChanges) 543 | if (process.env.NODE_ENV === 'development') 544 | writeFileSync( 545 | `now-available-${Date.now()}.json`, 546 | JSON.stringify([...raspberryAvailableCache.values()], null, 2) 547 | ) 548 | } else { 549 | console.log('Not in stock!') 550 | } 551 | if (raspberryListWithChanges.nowUnavailableRaspberry.size > 0) { 552 | await updateTelegramAlert(raspberryListWithChanges) 553 | } 554 | } catch (error) { 555 | console.error(error) 556 | 557 | let stack = error.stack?.slice(0, 2000) 558 | if (error.message.includes('API data was not JSON!')) stack = 'API data was not JSON' 559 | 560 | await bot.sendMessage(TELEGRAM_ADMIN_CHAT_ID, `❌ Error!\n\`\`\`${stack}\`\`\``, { 561 | parse_mode: 'Markdown', 562 | disable_web_page_preview: true, 563 | }) 564 | } 565 | debugRound++ 566 | } 567 | 568 | const liveStockUpdate = async () => { 569 | if (!TELEGRAM_LIVE_STOCK_UPDATE_MESSAGE_ID) return 570 | 571 | let message = '🔴🤖 Live Raspberry Stock Update\n\n' 572 | 573 | const available = [...new Set([...raspberryAvailableCache.values()])] 574 | .filter(x => x.avail === 'Yes') 575 | .slice(0, 50) // Telegram message is too long if too many 576 | .map(r => `✅ ${getRaspberryLink(r)}`) 577 | message += available.length > 0 ? available.join('\n') : '🤷‍♀️ Nothing available right now' 578 | 579 | message += '\n\n🌟 Star on [GitHub](https://github.com/rigwild/raspberry-instock-check)' 580 | message += '\n🌐 Stock data from [rpilocator](https://rpilocator.com/?utm_source=telegram&utm_medium=rapsberry_alert)' 581 | message += `\n\n🔄 Last update at ${toHumanDateTime(new Date())}` 582 | 583 | await bot 584 | .editMessageText(message, { 585 | chat_id: TELEGRAM_CHAT_ID, 586 | message_id: TELEGRAM_LIVE_STOCK_UPDATE_MESSAGE_ID, 587 | parse_mode: 'Markdown', 588 | }) 589 | .catch(error => { 590 | if ( 591 | error.message.includes( 592 | 'specified new message content and reply markup are exactly the same as a current content and reply markup of the message' 593 | ) 594 | ) { 595 | return 596 | } 597 | console.error(error) 598 | }) 599 | } 600 | 601 | ;(process.env.NODE_ENV === 'test' || USE_CACHED_REQUEST ? Promise.resolve() : getRpilocatorTokenAndCookies()) 602 | .then(checkStock) 603 | .finally(() => { 604 | liveStockUpdate() 605 | setInterval(checkStock, CHECK_INTERVAL + Math.random() * 3000) 606 | setInterval(liveStockUpdate, process.env.NODE_ENV === 'test' ? 2000 : 20_000) 607 | if (API_RUN) startServer() 608 | }) 609 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "raspberry-instock-check", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "node dist/index.js", 7 | "build": "esbuild --format=esm --platform=node --outdir=dist *.ts" 8 | }, 9 | "dependencies": { 10 | "cors": "^2.8.5", 11 | "express": "^4.18.2", 12 | "express-rate-limit": "^6.7.0", 13 | "https-proxy-agent": "^5.0.1", 14 | "morgan": "^1.10.0", 15 | "node-fetch": "^3.3.1", 16 | "node-telegram-bot-api": "^0.61.0" 17 | }, 18 | "devDependencies": { 19 | "@types/cors": "^2.8.13", 20 | "@types/express": "^4.17.17", 21 | "@types/jsdom": "^21.1.1", 22 | "@types/morgan": "^1.9.4", 23 | "@types/node": "^18.16.1", 24 | "@types/node-telegram-bot-api": "^0.61.6", 25 | "esbuild": "^0.17.18" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | dependencies: 4 | cors: 5 | specifier: ^2.8.5 6 | version: 2.8.5 7 | express: 8 | specifier: ^4.18.2 9 | version: 4.18.2 10 | express-rate-limit: 11 | specifier: ^6.7.0 12 | version: 6.7.0(express@4.18.2) 13 | https-proxy-agent: 14 | specifier: ^5.0.1 15 | version: 5.0.1 16 | morgan: 17 | specifier: ^1.10.0 18 | version: 1.10.0 19 | node-fetch: 20 | specifier: ^3.3.1 21 | version: 3.3.1 22 | node-telegram-bot-api: 23 | specifier: ^0.61.0 24 | version: 0.61.0 25 | 26 | devDependencies: 27 | '@types/cors': 28 | specifier: ^2.8.13 29 | version: 2.8.13 30 | '@types/express': 31 | specifier: ^4.17.17 32 | version: 4.17.17 33 | '@types/jsdom': 34 | specifier: ^21.1.1 35 | version: 21.1.1 36 | '@types/morgan': 37 | specifier: ^1.9.4 38 | version: 1.9.4 39 | '@types/node': 40 | specifier: ^18.16.1 41 | version: 18.16.1 42 | '@types/node-telegram-bot-api': 43 | specifier: ^0.61.6 44 | version: 0.61.6 45 | esbuild: 46 | specifier: ^0.17.18 47 | version: 0.17.18 48 | 49 | packages: 50 | 51 | /@esbuild/android-arm64@0.17.18: 52 | resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} 53 | engines: {node: '>=12'} 54 | cpu: [arm64] 55 | os: [android] 56 | requiresBuild: true 57 | dev: true 58 | optional: true 59 | 60 | /@esbuild/android-arm@0.17.18: 61 | resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==} 62 | engines: {node: '>=12'} 63 | cpu: [arm] 64 | os: [android] 65 | requiresBuild: true 66 | dev: true 67 | optional: true 68 | 69 | /@esbuild/android-x64@0.17.18: 70 | resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==} 71 | engines: {node: '>=12'} 72 | cpu: [x64] 73 | os: [android] 74 | requiresBuild: true 75 | dev: true 76 | optional: true 77 | 78 | /@esbuild/darwin-arm64@0.17.18: 79 | resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==} 80 | engines: {node: '>=12'} 81 | cpu: [arm64] 82 | os: [darwin] 83 | requiresBuild: true 84 | dev: true 85 | optional: true 86 | 87 | /@esbuild/darwin-x64@0.17.18: 88 | resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==} 89 | engines: {node: '>=12'} 90 | cpu: [x64] 91 | os: [darwin] 92 | requiresBuild: true 93 | dev: true 94 | optional: true 95 | 96 | /@esbuild/freebsd-arm64@0.17.18: 97 | resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==} 98 | engines: {node: '>=12'} 99 | cpu: [arm64] 100 | os: [freebsd] 101 | requiresBuild: true 102 | dev: true 103 | optional: true 104 | 105 | /@esbuild/freebsd-x64@0.17.18: 106 | resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==} 107 | engines: {node: '>=12'} 108 | cpu: [x64] 109 | os: [freebsd] 110 | requiresBuild: true 111 | dev: true 112 | optional: true 113 | 114 | /@esbuild/linux-arm64@0.17.18: 115 | resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==} 116 | engines: {node: '>=12'} 117 | cpu: [arm64] 118 | os: [linux] 119 | requiresBuild: true 120 | dev: true 121 | optional: true 122 | 123 | /@esbuild/linux-arm@0.17.18: 124 | resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==} 125 | engines: {node: '>=12'} 126 | cpu: [arm] 127 | os: [linux] 128 | requiresBuild: true 129 | dev: true 130 | optional: true 131 | 132 | /@esbuild/linux-ia32@0.17.18: 133 | resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==} 134 | engines: {node: '>=12'} 135 | cpu: [ia32] 136 | os: [linux] 137 | requiresBuild: true 138 | dev: true 139 | optional: true 140 | 141 | /@esbuild/linux-loong64@0.17.18: 142 | resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==} 143 | engines: {node: '>=12'} 144 | cpu: [loong64] 145 | os: [linux] 146 | requiresBuild: true 147 | dev: true 148 | optional: true 149 | 150 | /@esbuild/linux-mips64el@0.17.18: 151 | resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==} 152 | engines: {node: '>=12'} 153 | cpu: [mips64el] 154 | os: [linux] 155 | requiresBuild: true 156 | dev: true 157 | optional: true 158 | 159 | /@esbuild/linux-ppc64@0.17.18: 160 | resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==} 161 | engines: {node: '>=12'} 162 | cpu: [ppc64] 163 | os: [linux] 164 | requiresBuild: true 165 | dev: true 166 | optional: true 167 | 168 | /@esbuild/linux-riscv64@0.17.18: 169 | resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==} 170 | engines: {node: '>=12'} 171 | cpu: [riscv64] 172 | os: [linux] 173 | requiresBuild: true 174 | dev: true 175 | optional: true 176 | 177 | /@esbuild/linux-s390x@0.17.18: 178 | resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==} 179 | engines: {node: '>=12'} 180 | cpu: [s390x] 181 | os: [linux] 182 | requiresBuild: true 183 | dev: true 184 | optional: true 185 | 186 | /@esbuild/linux-x64@0.17.18: 187 | resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==} 188 | engines: {node: '>=12'} 189 | cpu: [x64] 190 | os: [linux] 191 | requiresBuild: true 192 | dev: true 193 | optional: true 194 | 195 | /@esbuild/netbsd-x64@0.17.18: 196 | resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==} 197 | engines: {node: '>=12'} 198 | cpu: [x64] 199 | os: [netbsd] 200 | requiresBuild: true 201 | dev: true 202 | optional: true 203 | 204 | /@esbuild/openbsd-x64@0.17.18: 205 | resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==} 206 | engines: {node: '>=12'} 207 | cpu: [x64] 208 | os: [openbsd] 209 | requiresBuild: true 210 | dev: true 211 | optional: true 212 | 213 | /@esbuild/sunos-x64@0.17.18: 214 | resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==} 215 | engines: {node: '>=12'} 216 | cpu: [x64] 217 | os: [sunos] 218 | requiresBuild: true 219 | dev: true 220 | optional: true 221 | 222 | /@esbuild/win32-arm64@0.17.18: 223 | resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==} 224 | engines: {node: '>=12'} 225 | cpu: [arm64] 226 | os: [win32] 227 | requiresBuild: true 228 | dev: true 229 | optional: true 230 | 231 | /@esbuild/win32-ia32@0.17.18: 232 | resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==} 233 | engines: {node: '>=12'} 234 | cpu: [ia32] 235 | os: [win32] 236 | requiresBuild: true 237 | dev: true 238 | optional: true 239 | 240 | /@esbuild/win32-x64@0.17.18: 241 | resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==} 242 | engines: {node: '>=12'} 243 | cpu: [x64] 244 | os: [win32] 245 | requiresBuild: true 246 | dev: true 247 | optional: true 248 | 249 | /@types/body-parser@1.19.2: 250 | resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} 251 | dependencies: 252 | '@types/connect': 3.4.35 253 | '@types/node': 18.16.1 254 | dev: true 255 | 256 | /@types/caseless@0.12.2: 257 | resolution: {integrity: sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==} 258 | dev: true 259 | 260 | /@types/connect@3.4.35: 261 | resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} 262 | dependencies: 263 | '@types/node': 18.16.1 264 | dev: true 265 | 266 | /@types/cors@2.8.13: 267 | resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} 268 | dependencies: 269 | '@types/node': 18.16.1 270 | dev: true 271 | 272 | /@types/express-serve-static-core@4.17.34: 273 | resolution: {integrity: sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==} 274 | dependencies: 275 | '@types/node': 18.16.1 276 | '@types/qs': 6.9.7 277 | '@types/range-parser': 1.2.4 278 | '@types/send': 0.17.1 279 | dev: true 280 | 281 | /@types/express@4.17.17: 282 | resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} 283 | dependencies: 284 | '@types/body-parser': 1.19.2 285 | '@types/express-serve-static-core': 4.17.34 286 | '@types/qs': 6.9.7 287 | '@types/serve-static': 1.13.10 288 | dev: true 289 | 290 | /@types/jsdom@21.1.1: 291 | resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==} 292 | dependencies: 293 | '@types/node': 18.16.1 294 | '@types/tough-cookie': 4.0.1 295 | parse5: 7.0.0 296 | dev: true 297 | 298 | /@types/mime@1.3.2: 299 | resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} 300 | dev: true 301 | 302 | /@types/morgan@1.9.4: 303 | resolution: {integrity: sha512-cXoc4k+6+YAllH3ZHmx4hf7La1dzUk6keTR4bF4b4Sc0mZxU/zK4wO7l+ZzezXm/jkYj/qC+uYGZrarZdIVvyQ==} 304 | dependencies: 305 | '@types/node': 18.16.1 306 | dev: true 307 | 308 | /@types/node-telegram-bot-api@0.61.6: 309 | resolution: {integrity: sha512-opkSxWn7ZLNMLHOwJHROKfBvr5dkqYoLCDpJ/m8cGMhreuF+tHc5bJ3E9isiIFiFOzD8Vj1K7TnCSWPIf7SmGw==} 310 | dependencies: 311 | '@types/node': 18.16.1 312 | '@types/request': 2.48.8 313 | eventemitter3: 3.1.2 314 | dev: true 315 | 316 | /@types/node@18.16.1: 317 | resolution: {integrity: sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==} 318 | dev: true 319 | 320 | /@types/qs@6.9.7: 321 | resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} 322 | dev: true 323 | 324 | /@types/range-parser@1.2.4: 325 | resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} 326 | dev: true 327 | 328 | /@types/request@2.48.8: 329 | resolution: {integrity: sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==} 330 | dependencies: 331 | '@types/caseless': 0.12.2 332 | '@types/node': 18.16.1 333 | '@types/tough-cookie': 4.0.1 334 | form-data: 2.5.1 335 | dev: true 336 | 337 | /@types/send@0.17.1: 338 | resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} 339 | dependencies: 340 | '@types/mime': 1.3.2 341 | '@types/node': 18.16.1 342 | dev: true 343 | 344 | /@types/serve-static@1.13.10: 345 | resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} 346 | dependencies: 347 | '@types/mime': 1.3.2 348 | '@types/node': 18.16.1 349 | dev: true 350 | 351 | /@types/tough-cookie@4.0.1: 352 | resolution: {integrity: sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==} 353 | dev: true 354 | 355 | /accepts@1.3.8: 356 | resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} 357 | engines: {node: '>= 0.6'} 358 | dependencies: 359 | mime-types: 2.1.34 360 | negotiator: 0.6.3 361 | dev: false 362 | 363 | /agent-base@6.0.2: 364 | resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} 365 | engines: {node: '>= 6.0.0'} 366 | dependencies: 367 | debug: 4.3.3 368 | transitivePeerDependencies: 369 | - supports-color 370 | dev: false 371 | 372 | /ajv@6.12.6: 373 | resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 374 | dependencies: 375 | fast-deep-equal: 3.1.3 376 | fast-json-stable-stringify: 2.1.0 377 | json-schema-traverse: 0.4.1 378 | uri-js: 4.4.1 379 | dev: false 380 | 381 | /array-flatten@1.1.1: 382 | resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} 383 | dev: false 384 | 385 | /array.prototype.findindex@2.1.0: 386 | resolution: {integrity: sha512-25kJHCjXltdtljjwcyKvCTywmbUAeTJVB2ADVe0oP4jcefsd+K9pJJ3IdHPahLICoszcCLoNF+evWpEduzBlng==} 387 | dependencies: 388 | define-properties: 1.1.3 389 | es-abstract: 1.19.1 390 | dev: false 391 | 392 | /asn1@0.2.6: 393 | resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} 394 | dependencies: 395 | safer-buffer: 2.1.2 396 | dev: false 397 | 398 | /assert-plus@1.0.0: 399 | resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} 400 | engines: {node: '>=0.8'} 401 | dev: false 402 | 403 | /asynckit@0.4.0: 404 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 405 | 406 | /aws-sign2@0.7.0: 407 | resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} 408 | dev: false 409 | 410 | /aws4@1.11.0: 411 | resolution: {integrity: sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==} 412 | dev: false 413 | 414 | /basic-auth@2.0.1: 415 | resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} 416 | engines: {node: '>= 0.8'} 417 | dependencies: 418 | safe-buffer: 5.1.2 419 | dev: false 420 | 421 | /bcrypt-pbkdf@1.0.2: 422 | resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} 423 | dependencies: 424 | tweetnacl: 0.14.5 425 | dev: false 426 | 427 | /bl@1.2.3: 428 | resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} 429 | dependencies: 430 | readable-stream: 2.3.7 431 | safe-buffer: 5.2.1 432 | dev: false 433 | 434 | /bluebird@3.7.2: 435 | resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} 436 | dev: false 437 | 438 | /body-parser@1.20.1: 439 | resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} 440 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 441 | dependencies: 442 | bytes: 3.1.2 443 | content-type: 1.0.4 444 | debug: 2.6.9 445 | depd: 2.0.0 446 | destroy: 1.2.0 447 | http-errors: 2.0.0 448 | iconv-lite: 0.4.24 449 | on-finished: 2.4.1 450 | qs: 6.11.0 451 | raw-body: 2.5.1 452 | type-is: 1.6.18 453 | unpipe: 1.0.0 454 | transitivePeerDependencies: 455 | - supports-color 456 | dev: false 457 | 458 | /bytes@3.1.2: 459 | resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} 460 | engines: {node: '>= 0.8'} 461 | dev: false 462 | 463 | /call-bind@1.0.2: 464 | resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} 465 | dependencies: 466 | function-bind: 1.1.1 467 | get-intrinsic: 1.1.1 468 | dev: false 469 | 470 | /caseless@0.12.0: 471 | resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} 472 | dev: false 473 | 474 | /combined-stream@1.0.8: 475 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 476 | engines: {node: '>= 0.8'} 477 | dependencies: 478 | delayed-stream: 1.0.0 479 | 480 | /content-disposition@0.5.4: 481 | resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} 482 | engines: {node: '>= 0.6'} 483 | dependencies: 484 | safe-buffer: 5.2.1 485 | dev: false 486 | 487 | /content-type@1.0.4: 488 | resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} 489 | engines: {node: '>= 0.6'} 490 | dev: false 491 | 492 | /cookie-signature@1.0.6: 493 | resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} 494 | dev: false 495 | 496 | /cookie@0.5.0: 497 | resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} 498 | engines: {node: '>= 0.6'} 499 | dev: false 500 | 501 | /core-util-is@1.0.2: 502 | resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} 503 | dev: false 504 | 505 | /core-util-is@1.0.3: 506 | resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} 507 | dev: false 508 | 509 | /cors@2.8.5: 510 | resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} 511 | engines: {node: '>= 0.10'} 512 | dependencies: 513 | object-assign: 4.1.1 514 | vary: 1.1.2 515 | dev: false 516 | 517 | /dashdash@1.14.1: 518 | resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} 519 | engines: {node: '>=0.10'} 520 | dependencies: 521 | assert-plus: 1.0.0 522 | dev: false 523 | 524 | /data-uri-to-buffer@4.0.0: 525 | resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} 526 | engines: {node: '>= 12'} 527 | dev: false 528 | 529 | /debug@2.6.9: 530 | resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} 531 | peerDependencies: 532 | supports-color: '*' 533 | peerDependenciesMeta: 534 | supports-color: 535 | optional: true 536 | dependencies: 537 | ms: 2.0.0 538 | dev: false 539 | 540 | /debug@3.2.7: 541 | resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} 542 | peerDependencies: 543 | supports-color: '*' 544 | peerDependenciesMeta: 545 | supports-color: 546 | optional: true 547 | dependencies: 548 | ms: 2.1.3 549 | dev: false 550 | 551 | /debug@4.3.3: 552 | resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==} 553 | engines: {node: '>=6.0'} 554 | peerDependencies: 555 | supports-color: '*' 556 | peerDependenciesMeta: 557 | supports-color: 558 | optional: true 559 | dependencies: 560 | ms: 2.1.2 561 | dev: false 562 | 563 | /define-properties@1.1.3: 564 | resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} 565 | engines: {node: '>= 0.4'} 566 | dependencies: 567 | object-keys: 1.1.1 568 | dev: false 569 | 570 | /delayed-stream@1.0.0: 571 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 572 | engines: {node: '>=0.4.0'} 573 | 574 | /depd@2.0.0: 575 | resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} 576 | engines: {node: '>= 0.8'} 577 | dev: false 578 | 579 | /destroy@1.2.0: 580 | resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} 581 | engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} 582 | dev: false 583 | 584 | /ecc-jsbn@0.1.2: 585 | resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} 586 | dependencies: 587 | jsbn: 0.1.1 588 | safer-buffer: 2.1.2 589 | dev: false 590 | 591 | /ee-first@1.1.1: 592 | resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} 593 | dev: false 594 | 595 | /encodeurl@1.0.2: 596 | resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} 597 | engines: {node: '>= 0.8'} 598 | dev: false 599 | 600 | /end-of-stream@1.4.4: 601 | resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} 602 | dependencies: 603 | once: 1.4.0 604 | dev: false 605 | 606 | /entities@4.3.1: 607 | resolution: {integrity: sha512-o4q/dYJlmyjP2zfnaWDUC6A3BQFmVTX+tZPezK7k0GLSU9QYCauscf5Y+qcEPzKL+EixVouYDgLQK5H9GrLpkg==} 608 | engines: {node: '>=0.12'} 609 | dev: true 610 | 611 | /es-abstract@1.19.1: 612 | resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==} 613 | engines: {node: '>= 0.4'} 614 | dependencies: 615 | call-bind: 1.0.2 616 | es-to-primitive: 1.2.1 617 | function-bind: 1.1.1 618 | get-intrinsic: 1.1.1 619 | get-symbol-description: 1.0.0 620 | has: 1.0.3 621 | has-symbols: 1.0.2 622 | internal-slot: 1.0.3 623 | is-callable: 1.2.4 624 | is-negative-zero: 2.0.2 625 | is-regex: 1.1.4 626 | is-shared-array-buffer: 1.0.1 627 | is-string: 1.0.7 628 | is-weakref: 1.0.2 629 | object-inspect: 1.12.0 630 | object-keys: 1.1.1 631 | object.assign: 4.1.2 632 | string.prototype.trimend: 1.0.4 633 | string.prototype.trimstart: 1.0.4 634 | unbox-primitive: 1.0.1 635 | dev: false 636 | 637 | /es-to-primitive@1.2.1: 638 | resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 639 | engines: {node: '>= 0.4'} 640 | dependencies: 641 | is-callable: 1.2.4 642 | is-date-object: 1.0.5 643 | is-symbol: 1.0.4 644 | dev: false 645 | 646 | /esbuild@0.17.18: 647 | resolution: {integrity: sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==} 648 | engines: {node: '>=12'} 649 | hasBin: true 650 | requiresBuild: true 651 | optionalDependencies: 652 | '@esbuild/android-arm': 0.17.18 653 | '@esbuild/android-arm64': 0.17.18 654 | '@esbuild/android-x64': 0.17.18 655 | '@esbuild/darwin-arm64': 0.17.18 656 | '@esbuild/darwin-x64': 0.17.18 657 | '@esbuild/freebsd-arm64': 0.17.18 658 | '@esbuild/freebsd-x64': 0.17.18 659 | '@esbuild/linux-arm': 0.17.18 660 | '@esbuild/linux-arm64': 0.17.18 661 | '@esbuild/linux-ia32': 0.17.18 662 | '@esbuild/linux-loong64': 0.17.18 663 | '@esbuild/linux-mips64el': 0.17.18 664 | '@esbuild/linux-ppc64': 0.17.18 665 | '@esbuild/linux-riscv64': 0.17.18 666 | '@esbuild/linux-s390x': 0.17.18 667 | '@esbuild/linux-x64': 0.17.18 668 | '@esbuild/netbsd-x64': 0.17.18 669 | '@esbuild/openbsd-x64': 0.17.18 670 | '@esbuild/sunos-x64': 0.17.18 671 | '@esbuild/win32-arm64': 0.17.18 672 | '@esbuild/win32-ia32': 0.17.18 673 | '@esbuild/win32-x64': 0.17.18 674 | dev: true 675 | 676 | /escape-html@1.0.3: 677 | resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} 678 | dev: false 679 | 680 | /etag@1.8.1: 681 | resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} 682 | engines: {node: '>= 0.6'} 683 | dev: false 684 | 685 | /eventemitter3@3.1.2: 686 | resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} 687 | 688 | /express-rate-limit@6.7.0(express@4.18.2): 689 | resolution: {integrity: sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==} 690 | engines: {node: '>= 12.9.0'} 691 | peerDependencies: 692 | express: ^4 || ^5 693 | dependencies: 694 | express: 4.18.2 695 | dev: false 696 | 697 | /express@4.18.2: 698 | resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} 699 | engines: {node: '>= 0.10.0'} 700 | dependencies: 701 | accepts: 1.3.8 702 | array-flatten: 1.1.1 703 | body-parser: 1.20.1 704 | content-disposition: 0.5.4 705 | content-type: 1.0.4 706 | cookie: 0.5.0 707 | cookie-signature: 1.0.6 708 | debug: 2.6.9 709 | depd: 2.0.0 710 | encodeurl: 1.0.2 711 | escape-html: 1.0.3 712 | etag: 1.8.1 713 | finalhandler: 1.2.0 714 | fresh: 0.5.2 715 | http-errors: 2.0.0 716 | merge-descriptors: 1.0.1 717 | methods: 1.1.2 718 | on-finished: 2.4.1 719 | parseurl: 1.3.3 720 | path-to-regexp: 0.1.7 721 | proxy-addr: 2.0.7 722 | qs: 6.11.0 723 | range-parser: 1.2.1 724 | safe-buffer: 5.2.1 725 | send: 0.18.0 726 | serve-static: 1.15.0 727 | setprototypeof: 1.2.0 728 | statuses: 2.0.1 729 | type-is: 1.6.18 730 | utils-merge: 1.0.1 731 | vary: 1.1.2 732 | transitivePeerDependencies: 733 | - supports-color 734 | dev: false 735 | 736 | /extend@3.0.2: 737 | resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} 738 | dev: false 739 | 740 | /extsprintf@1.3.0: 741 | resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} 742 | engines: {'0': node >=0.6.0} 743 | dev: false 744 | 745 | /fast-deep-equal@3.1.3: 746 | resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 747 | dev: false 748 | 749 | /fast-json-stable-stringify@2.1.0: 750 | resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 751 | dev: false 752 | 753 | /fetch-blob@3.1.4: 754 | resolution: {integrity: sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==} 755 | engines: {node: ^12.20 || >= 14.13} 756 | dependencies: 757 | node-domexception: 1.0.0 758 | web-streams-polyfill: 3.2.0 759 | dev: false 760 | 761 | /file-type@3.9.0: 762 | resolution: {integrity: sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==} 763 | engines: {node: '>=0.10.0'} 764 | dev: false 765 | 766 | /finalhandler@1.2.0: 767 | resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} 768 | engines: {node: '>= 0.8'} 769 | dependencies: 770 | debug: 2.6.9 771 | encodeurl: 1.0.2 772 | escape-html: 1.0.3 773 | on-finished: 2.4.1 774 | parseurl: 1.3.3 775 | statuses: 2.0.1 776 | unpipe: 1.0.0 777 | transitivePeerDependencies: 778 | - supports-color 779 | dev: false 780 | 781 | /forever-agent@0.6.1: 782 | resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} 783 | dev: false 784 | 785 | /form-data@2.3.3: 786 | resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} 787 | engines: {node: '>= 0.12'} 788 | dependencies: 789 | asynckit: 0.4.0 790 | combined-stream: 1.0.8 791 | mime-types: 2.1.34 792 | dev: false 793 | 794 | /form-data@2.5.1: 795 | resolution: {integrity: sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==} 796 | engines: {node: '>= 0.12'} 797 | dependencies: 798 | asynckit: 0.4.0 799 | combined-stream: 1.0.8 800 | mime-types: 2.1.34 801 | dev: true 802 | 803 | /formdata-polyfill@4.0.10: 804 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 805 | engines: {node: '>=12.20.0'} 806 | dependencies: 807 | fetch-blob: 3.1.4 808 | dev: false 809 | 810 | /forwarded@0.2.0: 811 | resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} 812 | engines: {node: '>= 0.6'} 813 | dev: false 814 | 815 | /fresh@0.5.2: 816 | resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} 817 | engines: {node: '>= 0.6'} 818 | dev: false 819 | 820 | /function-bind@1.1.1: 821 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 822 | dev: false 823 | 824 | /get-intrinsic@1.1.1: 825 | resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} 826 | dependencies: 827 | function-bind: 1.1.1 828 | has: 1.0.3 829 | has-symbols: 1.0.2 830 | dev: false 831 | 832 | /get-symbol-description@1.0.0: 833 | resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 834 | engines: {node: '>= 0.4'} 835 | dependencies: 836 | call-bind: 1.0.2 837 | get-intrinsic: 1.1.1 838 | dev: false 839 | 840 | /getpass@0.1.7: 841 | resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} 842 | dependencies: 843 | assert-plus: 1.0.0 844 | dev: false 845 | 846 | /har-schema@2.0.0: 847 | resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} 848 | engines: {node: '>=4'} 849 | dev: false 850 | 851 | /har-validator@5.1.5: 852 | resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} 853 | engines: {node: '>=6'} 854 | deprecated: this library is no longer supported 855 | dependencies: 856 | ajv: 6.12.6 857 | har-schema: 2.0.0 858 | dev: false 859 | 860 | /has-bigints@1.0.1: 861 | resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} 862 | dev: false 863 | 864 | /has-symbols@1.0.2: 865 | resolution: {integrity: sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==} 866 | engines: {node: '>= 0.4'} 867 | dev: false 868 | 869 | /has-tostringtag@1.0.0: 870 | resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 871 | engines: {node: '>= 0.4'} 872 | dependencies: 873 | has-symbols: 1.0.2 874 | dev: false 875 | 876 | /has@1.0.3: 877 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 878 | engines: {node: '>= 0.4.0'} 879 | dependencies: 880 | function-bind: 1.1.1 881 | dev: false 882 | 883 | /http-errors@2.0.0: 884 | resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} 885 | engines: {node: '>= 0.8'} 886 | dependencies: 887 | depd: 2.0.0 888 | inherits: 2.0.4 889 | setprototypeof: 1.2.0 890 | statuses: 2.0.1 891 | toidentifier: 1.0.1 892 | dev: false 893 | 894 | /http-signature@1.2.0: 895 | resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} 896 | engines: {node: '>=0.8', npm: '>=1.3.7'} 897 | dependencies: 898 | assert-plus: 1.0.0 899 | jsprim: 1.4.2 900 | sshpk: 1.17.0 901 | dev: false 902 | 903 | /https-proxy-agent@5.0.1: 904 | resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} 905 | engines: {node: '>= 6'} 906 | dependencies: 907 | agent-base: 6.0.2 908 | debug: 4.3.3 909 | transitivePeerDependencies: 910 | - supports-color 911 | dev: false 912 | 913 | /iconv-lite@0.4.24: 914 | resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} 915 | engines: {node: '>=0.10.0'} 916 | dependencies: 917 | safer-buffer: 2.1.2 918 | dev: false 919 | 920 | /inherits@2.0.4: 921 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 922 | dev: false 923 | 924 | /internal-slot@1.0.3: 925 | resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} 926 | engines: {node: '>= 0.4'} 927 | dependencies: 928 | get-intrinsic: 1.1.1 929 | has: 1.0.3 930 | side-channel: 1.0.4 931 | dev: false 932 | 933 | /ipaddr.js@1.9.1: 934 | resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} 935 | engines: {node: '>= 0.10'} 936 | dev: false 937 | 938 | /is-bigint@1.0.4: 939 | resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 940 | dependencies: 941 | has-bigints: 1.0.1 942 | dev: false 943 | 944 | /is-boolean-object@1.1.2: 945 | resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 946 | engines: {node: '>= 0.4'} 947 | dependencies: 948 | call-bind: 1.0.2 949 | has-tostringtag: 1.0.0 950 | dev: false 951 | 952 | /is-callable@1.2.4: 953 | resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} 954 | engines: {node: '>= 0.4'} 955 | dev: false 956 | 957 | /is-date-object@1.0.5: 958 | resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 959 | engines: {node: '>= 0.4'} 960 | dependencies: 961 | has-tostringtag: 1.0.0 962 | dev: false 963 | 964 | /is-negative-zero@2.0.2: 965 | resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 966 | engines: {node: '>= 0.4'} 967 | dev: false 968 | 969 | /is-number-object@1.0.6: 970 | resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==} 971 | engines: {node: '>= 0.4'} 972 | dependencies: 973 | has-tostringtag: 1.0.0 974 | dev: false 975 | 976 | /is-regex@1.1.4: 977 | resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 978 | engines: {node: '>= 0.4'} 979 | dependencies: 980 | call-bind: 1.0.2 981 | has-tostringtag: 1.0.0 982 | dev: false 983 | 984 | /is-shared-array-buffer@1.0.1: 985 | resolution: {integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==} 986 | dev: false 987 | 988 | /is-string@1.0.7: 989 | resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 990 | engines: {node: '>= 0.4'} 991 | dependencies: 992 | has-tostringtag: 1.0.0 993 | dev: false 994 | 995 | /is-symbol@1.0.4: 996 | resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 997 | engines: {node: '>= 0.4'} 998 | dependencies: 999 | has-symbols: 1.0.2 1000 | dev: false 1001 | 1002 | /is-typedarray@1.0.0: 1003 | resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} 1004 | dev: false 1005 | 1006 | /is-weakref@1.0.2: 1007 | resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 1008 | dependencies: 1009 | call-bind: 1.0.2 1010 | dev: false 1011 | 1012 | /isarray@1.0.0: 1013 | resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} 1014 | dev: false 1015 | 1016 | /isstream@0.1.2: 1017 | resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} 1018 | dev: false 1019 | 1020 | /jsbn@0.1.1: 1021 | resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} 1022 | dev: false 1023 | 1024 | /json-schema-traverse@0.4.1: 1025 | resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} 1026 | dev: false 1027 | 1028 | /json-schema@0.4.0: 1029 | resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} 1030 | dev: false 1031 | 1032 | /json-stringify-safe@5.0.1: 1033 | resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} 1034 | dev: false 1035 | 1036 | /jsprim@1.4.2: 1037 | resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} 1038 | engines: {node: '>=0.6.0'} 1039 | dependencies: 1040 | assert-plus: 1.0.0 1041 | extsprintf: 1.3.0 1042 | json-schema: 0.4.0 1043 | verror: 1.10.0 1044 | dev: false 1045 | 1046 | /lodash@4.17.21: 1047 | resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} 1048 | dev: false 1049 | 1050 | /media-typer@0.3.0: 1051 | resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} 1052 | engines: {node: '>= 0.6'} 1053 | dev: false 1054 | 1055 | /merge-descriptors@1.0.1: 1056 | resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} 1057 | dev: false 1058 | 1059 | /methods@1.1.2: 1060 | resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} 1061 | engines: {node: '>= 0.6'} 1062 | dev: false 1063 | 1064 | /mime-db@1.51.0: 1065 | resolution: {integrity: sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==} 1066 | engines: {node: '>= 0.6'} 1067 | 1068 | /mime-types@2.1.34: 1069 | resolution: {integrity: sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==} 1070 | engines: {node: '>= 0.6'} 1071 | dependencies: 1072 | mime-db: 1.51.0 1073 | 1074 | /mime@1.6.0: 1075 | resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} 1076 | engines: {node: '>=4'} 1077 | hasBin: true 1078 | dev: false 1079 | 1080 | /morgan@1.10.0: 1081 | resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} 1082 | engines: {node: '>= 0.8.0'} 1083 | dependencies: 1084 | basic-auth: 2.0.1 1085 | debug: 2.6.9 1086 | depd: 2.0.0 1087 | on-finished: 2.3.0 1088 | on-headers: 1.0.2 1089 | transitivePeerDependencies: 1090 | - supports-color 1091 | dev: false 1092 | 1093 | /ms@2.0.0: 1094 | resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} 1095 | dev: false 1096 | 1097 | /ms@2.1.2: 1098 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 1099 | dev: false 1100 | 1101 | /ms@2.1.3: 1102 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1103 | dev: false 1104 | 1105 | /negotiator@0.6.3: 1106 | resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} 1107 | engines: {node: '>= 0.6'} 1108 | dev: false 1109 | 1110 | /node-domexception@1.0.0: 1111 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 1112 | engines: {node: '>=10.5.0'} 1113 | dev: false 1114 | 1115 | /node-fetch@3.3.1: 1116 | resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} 1117 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1118 | dependencies: 1119 | data-uri-to-buffer: 4.0.0 1120 | fetch-blob: 3.1.4 1121 | formdata-polyfill: 4.0.10 1122 | dev: false 1123 | 1124 | /node-telegram-bot-api@0.61.0: 1125 | resolution: {integrity: sha512-BZXd8Bh2C5+uBEQuuI3FD7TFJF3alV+6oFQt8CNLx3ldX/hsd+NYyllTX+Y+5X0tG+xtcRQQjbfLgz/4sRvmBQ==} 1126 | engines: {node: '>=0.12'} 1127 | dependencies: 1128 | array.prototype.findindex: 2.1.0 1129 | bl: 1.2.3 1130 | debug: 3.2.7 1131 | eventemitter3: 3.1.2 1132 | file-type: 3.9.0 1133 | mime: 1.6.0 1134 | pump: 2.0.1 1135 | request: 2.88.2 1136 | request-promise: 4.2.6(request@2.88.2) 1137 | transitivePeerDependencies: 1138 | - supports-color 1139 | dev: false 1140 | 1141 | /oauth-sign@0.9.0: 1142 | resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} 1143 | dev: false 1144 | 1145 | /object-assign@4.1.1: 1146 | resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 1147 | engines: {node: '>=0.10.0'} 1148 | dev: false 1149 | 1150 | /object-inspect@1.12.0: 1151 | resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} 1152 | dev: false 1153 | 1154 | /object-keys@1.1.1: 1155 | resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 1156 | engines: {node: '>= 0.4'} 1157 | dev: false 1158 | 1159 | /object.assign@4.1.2: 1160 | resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} 1161 | engines: {node: '>= 0.4'} 1162 | dependencies: 1163 | call-bind: 1.0.2 1164 | define-properties: 1.1.3 1165 | has-symbols: 1.0.2 1166 | object-keys: 1.1.1 1167 | dev: false 1168 | 1169 | /on-finished@2.3.0: 1170 | resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} 1171 | engines: {node: '>= 0.8'} 1172 | dependencies: 1173 | ee-first: 1.1.1 1174 | dev: false 1175 | 1176 | /on-finished@2.4.1: 1177 | resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} 1178 | engines: {node: '>= 0.8'} 1179 | dependencies: 1180 | ee-first: 1.1.1 1181 | dev: false 1182 | 1183 | /on-headers@1.0.2: 1184 | resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} 1185 | engines: {node: '>= 0.8'} 1186 | dev: false 1187 | 1188 | /once@1.4.0: 1189 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1190 | dependencies: 1191 | wrappy: 1.0.2 1192 | dev: false 1193 | 1194 | /parse5@7.0.0: 1195 | resolution: {integrity: sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==} 1196 | dependencies: 1197 | entities: 4.3.1 1198 | dev: true 1199 | 1200 | /parseurl@1.3.3: 1201 | resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} 1202 | engines: {node: '>= 0.8'} 1203 | dev: false 1204 | 1205 | /path-to-regexp@0.1.7: 1206 | resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} 1207 | dev: false 1208 | 1209 | /performance-now@2.1.0: 1210 | resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} 1211 | dev: false 1212 | 1213 | /process-nextick-args@2.0.1: 1214 | resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} 1215 | dev: false 1216 | 1217 | /proxy-addr@2.0.7: 1218 | resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} 1219 | engines: {node: '>= 0.10'} 1220 | dependencies: 1221 | forwarded: 0.2.0 1222 | ipaddr.js: 1.9.1 1223 | dev: false 1224 | 1225 | /psl@1.8.0: 1226 | resolution: {integrity: sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==} 1227 | dev: false 1228 | 1229 | /pump@2.0.1: 1230 | resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} 1231 | dependencies: 1232 | end-of-stream: 1.4.4 1233 | once: 1.4.0 1234 | dev: false 1235 | 1236 | /punycode@2.1.1: 1237 | resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} 1238 | engines: {node: '>=6'} 1239 | dev: false 1240 | 1241 | /qs@6.11.0: 1242 | resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} 1243 | engines: {node: '>=0.6'} 1244 | dependencies: 1245 | side-channel: 1.0.4 1246 | dev: false 1247 | 1248 | /qs@6.5.3: 1249 | resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} 1250 | engines: {node: '>=0.6'} 1251 | dev: false 1252 | 1253 | /range-parser@1.2.1: 1254 | resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} 1255 | engines: {node: '>= 0.6'} 1256 | dev: false 1257 | 1258 | /raw-body@2.5.1: 1259 | resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} 1260 | engines: {node: '>= 0.8'} 1261 | dependencies: 1262 | bytes: 3.1.2 1263 | http-errors: 2.0.0 1264 | iconv-lite: 0.4.24 1265 | unpipe: 1.0.0 1266 | dev: false 1267 | 1268 | /readable-stream@2.3.7: 1269 | resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} 1270 | dependencies: 1271 | core-util-is: 1.0.3 1272 | inherits: 2.0.4 1273 | isarray: 1.0.0 1274 | process-nextick-args: 2.0.1 1275 | safe-buffer: 5.1.2 1276 | string_decoder: 1.1.1 1277 | util-deprecate: 1.0.2 1278 | dev: false 1279 | 1280 | /request-promise-core@1.1.4(request@2.88.2): 1281 | resolution: {integrity: sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==} 1282 | engines: {node: '>=0.10.0'} 1283 | peerDependencies: 1284 | request: ^2.34 1285 | dependencies: 1286 | lodash: 4.17.21 1287 | request: 2.88.2 1288 | dev: false 1289 | 1290 | /request-promise@4.2.6(request@2.88.2): 1291 | resolution: {integrity: sha512-HCHI3DJJUakkOr8fNoCc73E5nU5bqITjOYFMDrKHYOXWXrgD/SBaC7LjwuPymUprRyuF06UK7hd/lMHkmUXglQ==} 1292 | engines: {node: '>=0.10.0'} 1293 | deprecated: request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142 1294 | peerDependencies: 1295 | request: ^2.34 1296 | dependencies: 1297 | bluebird: 3.7.2 1298 | request: 2.88.2 1299 | request-promise-core: 1.1.4(request@2.88.2) 1300 | stealthy-require: 1.1.1 1301 | tough-cookie: 2.5.0 1302 | dev: false 1303 | 1304 | /request@2.88.2: 1305 | resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} 1306 | engines: {node: '>= 6'} 1307 | deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 1308 | dependencies: 1309 | aws-sign2: 0.7.0 1310 | aws4: 1.11.0 1311 | caseless: 0.12.0 1312 | combined-stream: 1.0.8 1313 | extend: 3.0.2 1314 | forever-agent: 0.6.1 1315 | form-data: 2.3.3 1316 | har-validator: 5.1.5 1317 | http-signature: 1.2.0 1318 | is-typedarray: 1.0.0 1319 | isstream: 0.1.2 1320 | json-stringify-safe: 5.0.1 1321 | mime-types: 2.1.34 1322 | oauth-sign: 0.9.0 1323 | performance-now: 2.1.0 1324 | qs: 6.5.3 1325 | safe-buffer: 5.2.1 1326 | tough-cookie: 2.5.0 1327 | tunnel-agent: 0.6.0 1328 | uuid: 3.4.0 1329 | dev: false 1330 | 1331 | /safe-buffer@5.1.2: 1332 | resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} 1333 | dev: false 1334 | 1335 | /safe-buffer@5.2.1: 1336 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1337 | dev: false 1338 | 1339 | /safer-buffer@2.1.2: 1340 | resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1341 | dev: false 1342 | 1343 | /send@0.18.0: 1344 | resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} 1345 | engines: {node: '>= 0.8.0'} 1346 | dependencies: 1347 | debug: 2.6.9 1348 | depd: 2.0.0 1349 | destroy: 1.2.0 1350 | encodeurl: 1.0.2 1351 | escape-html: 1.0.3 1352 | etag: 1.8.1 1353 | fresh: 0.5.2 1354 | http-errors: 2.0.0 1355 | mime: 1.6.0 1356 | ms: 2.1.3 1357 | on-finished: 2.4.1 1358 | range-parser: 1.2.1 1359 | statuses: 2.0.1 1360 | transitivePeerDependencies: 1361 | - supports-color 1362 | dev: false 1363 | 1364 | /serve-static@1.15.0: 1365 | resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} 1366 | engines: {node: '>= 0.8.0'} 1367 | dependencies: 1368 | encodeurl: 1.0.2 1369 | escape-html: 1.0.3 1370 | parseurl: 1.3.3 1371 | send: 0.18.0 1372 | transitivePeerDependencies: 1373 | - supports-color 1374 | dev: false 1375 | 1376 | /setprototypeof@1.2.0: 1377 | resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} 1378 | dev: false 1379 | 1380 | /side-channel@1.0.4: 1381 | resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1382 | dependencies: 1383 | call-bind: 1.0.2 1384 | get-intrinsic: 1.1.1 1385 | object-inspect: 1.12.0 1386 | dev: false 1387 | 1388 | /sshpk@1.17.0: 1389 | resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} 1390 | engines: {node: '>=0.10.0'} 1391 | hasBin: true 1392 | dependencies: 1393 | asn1: 0.2.6 1394 | assert-plus: 1.0.0 1395 | bcrypt-pbkdf: 1.0.2 1396 | dashdash: 1.14.1 1397 | ecc-jsbn: 0.1.2 1398 | getpass: 0.1.7 1399 | jsbn: 0.1.1 1400 | safer-buffer: 2.1.2 1401 | tweetnacl: 0.14.5 1402 | dev: false 1403 | 1404 | /statuses@2.0.1: 1405 | resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} 1406 | engines: {node: '>= 0.8'} 1407 | dev: false 1408 | 1409 | /stealthy-require@1.1.1: 1410 | resolution: {integrity: sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==} 1411 | engines: {node: '>=0.10.0'} 1412 | dev: false 1413 | 1414 | /string.prototype.trimend@1.0.4: 1415 | resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} 1416 | dependencies: 1417 | call-bind: 1.0.2 1418 | define-properties: 1.1.3 1419 | dev: false 1420 | 1421 | /string.prototype.trimstart@1.0.4: 1422 | resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} 1423 | dependencies: 1424 | call-bind: 1.0.2 1425 | define-properties: 1.1.3 1426 | dev: false 1427 | 1428 | /string_decoder@1.1.1: 1429 | resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} 1430 | dependencies: 1431 | safe-buffer: 5.1.2 1432 | dev: false 1433 | 1434 | /toidentifier@1.0.1: 1435 | resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} 1436 | engines: {node: '>=0.6'} 1437 | dev: false 1438 | 1439 | /tough-cookie@2.5.0: 1440 | resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} 1441 | engines: {node: '>=0.8'} 1442 | dependencies: 1443 | psl: 1.8.0 1444 | punycode: 2.1.1 1445 | dev: false 1446 | 1447 | /tunnel-agent@0.6.0: 1448 | resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} 1449 | dependencies: 1450 | safe-buffer: 5.2.1 1451 | dev: false 1452 | 1453 | /tweetnacl@0.14.5: 1454 | resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} 1455 | dev: false 1456 | 1457 | /type-is@1.6.18: 1458 | resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} 1459 | engines: {node: '>= 0.6'} 1460 | dependencies: 1461 | media-typer: 0.3.0 1462 | mime-types: 2.1.34 1463 | dev: false 1464 | 1465 | /unbox-primitive@1.0.1: 1466 | resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} 1467 | dependencies: 1468 | function-bind: 1.1.1 1469 | has-bigints: 1.0.1 1470 | has-symbols: 1.0.2 1471 | which-boxed-primitive: 1.0.2 1472 | dev: false 1473 | 1474 | /unpipe@1.0.0: 1475 | resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} 1476 | engines: {node: '>= 0.8'} 1477 | dev: false 1478 | 1479 | /uri-js@4.4.1: 1480 | resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1481 | dependencies: 1482 | punycode: 2.1.1 1483 | dev: false 1484 | 1485 | /util-deprecate@1.0.2: 1486 | resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1487 | dev: false 1488 | 1489 | /utils-merge@1.0.1: 1490 | resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} 1491 | engines: {node: '>= 0.4.0'} 1492 | dev: false 1493 | 1494 | /uuid@3.4.0: 1495 | resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} 1496 | deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. 1497 | hasBin: true 1498 | dev: false 1499 | 1500 | /vary@1.1.2: 1501 | resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} 1502 | engines: {node: '>= 0.8'} 1503 | dev: false 1504 | 1505 | /verror@1.10.0: 1506 | resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} 1507 | engines: {'0': node >=0.6.0} 1508 | dependencies: 1509 | assert-plus: 1.0.0 1510 | core-util-is: 1.0.2 1511 | extsprintf: 1.3.0 1512 | dev: false 1513 | 1514 | /web-streams-polyfill@3.2.0: 1515 | resolution: {integrity: sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==} 1516 | engines: {node: '>= 8'} 1517 | dev: false 1518 | 1519 | /which-boxed-primitive@1.0.2: 1520 | resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 1521 | dependencies: 1522 | is-bigint: 1.0.4 1523 | is-boolean-object: 1.1.2 1524 | is-number-object: 1.0.6 1525 | is-string: 1.0.7 1526 | is-symbol: 1.0.4 1527 | dev: false 1528 | 1529 | /wrappy@1.0.2: 1530 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1531 | dev: false 1532 | -------------------------------------------------------------------------------- /raspberry.rigwild.dev.nginx.conf: -------------------------------------------------------------------------------- 1 | upstream raspberry.rigwild.dev { 2 | server 127.0.0.1:56109; 3 | keepalive 8; 4 | } 5 | 6 | server { 7 | server_name raspberry.rigwild.dev; 8 | access_log /var/log/nginx/raspberry.rigwild.dev_access.log; 9 | error_log /var/log/nginx/raspberry.rigwild.dev_error.log warn; 10 | 11 | location / { 12 | proxy_set_header X-Real-IP $remote_addr; 13 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 14 | proxy_set_header Host $http_host; 15 | proxy_set_header X-NginX-Proxy true; 16 | 17 | proxy_pass http://raspberry.rigwild.dev/; 18 | proxy_redirect off; 19 | } 20 | } 21 | 22 | # ln -s /etc/nginx/sites-available/raspberry.rigwild.dev.nginx.conf /etc/nginx/sites-enabled/raspberry.rigwild.dev.nginx.conf 23 | -------------------------------------------------------------------------------- /screenshot-live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rigwild/raspberry-instock-check/43152b778f6d40bffae07b3f9c61edfebb865e2e/screenshot-live.png -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rigwild/raspberry-instock-check/43152b778f6d40bffae07b3f9c61edfebb865e2e/screenshot.png -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs/promises' 2 | import express from 'express' 3 | import rateLimit from 'express-rate-limit' 4 | import morgan from 'morgan' 5 | import cors from 'cors' 6 | 7 | const port = process.env.API_PORT || 3000 8 | const trustProxy = process.env.API_TRUST_PROXY === '1' 9 | let cache = {} 10 | 11 | const refreshCache = async () => { 12 | const data = await readFile(new URL('../_cached_request_data.json', import.meta.url), { encoding: 'utf-8' }) 13 | cache = JSON.parse(data) 14 | } 15 | 16 | const limiter = rateLimit({ 17 | windowMs: 60_000, 18 | max: 60, 19 | standardHeaders: true 20 | }) 21 | 22 | export const startServer = () => { 23 | const app = express() 24 | if (trustProxy) app.enable('trust proxy') 25 | app.use(limiter) 26 | app.use(morgan('common')) 27 | app.use(cors()) 28 | 29 | app.get('/', (req, res) => res.json(cache)) 30 | 31 | refreshCache().finally(() => { 32 | setInterval(refreshCache, 1000) 33 | app.listen(port, () => console.log(`Server is listening on http://localhost:${port}`)) 34 | }) 35 | } 36 | --------------------------------------------------------------------------------