├── .idea ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── modules.xml └── thai-lotto-api.iml ├── .vscode └── settings.json ├── .prettierrc ├── Dockerfile ├── package.json ├── .gitignore ├── .github └── workflows │ └── deploy.yml ├── src ├── functions │ ├── getList.ts │ └── getLotto.ts ├── models.ts └── index.ts ├── LICENSE ├── .actions └── build-and-push │ └── action.yml ├── tsconfig.json ├── README.md └── bun.lock /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "yaml.schemas": { 3 | "https://json.schemastore.org/github-workflow.json": "file:///Users/rayriffy/Git/thai-lotto-api/.github/workflows/deploy.yml" 4 | } 5 | } -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "arrowParens": "avoid", 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "trailingComma": "es5", 9 | "bracketSpacing": true, 10 | "endOfLine": "lf" 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM oven/bun:alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package.json . 6 | COPY bun.lock . 7 | 8 | RUN bun install 9 | 10 | COPY src src 11 | COPY tsconfig.json . 12 | 13 | ENV NODE_ENV production 14 | CMD ["bun", "src/index.ts"] 15 | 16 | EXPOSE 3000 17 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/thai-lotto-api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thai-lotto-api", 3 | "version": "1.0.16", 4 | "module": "src/index.ts", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "dev": "bun run --hot src/index.ts" 8 | }, 9 | "dependencies": { 10 | "@bogeychan/elysia-logger": "0.1.8", 11 | "@elysiajs/cors": "1.3.3", 12 | "@elysiajs/swagger": "1.3.0", 13 | "cheerio": "1.0.0", 14 | "elysia": "1.3.1", 15 | "elysia-remote-dts": "1.0.3" 16 | }, 17 | "devDependencies": { 18 | "@types/bun": "1.2.13", 19 | "typescript": "5.8.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | **/*.trace 37 | **/*.zip 38 | **/*.tar.gz 39 | **/*.tgz 40 | **/*.log 41 | package-lock.json 42 | **/*.bun -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | name: build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: docker/login-action@v2 16 | with: 17 | registry: ghcr.io 18 | username: rayriffy 19 | password: ${{ secrets.GITHUB_TOKEN }} 20 | 21 | - uses: ./.actions/build-and-push 22 | with: 23 | target_image: ghcr.io/rayriffy/thai-lotto-runtime 24 | deploys_user: ${{ secrets.DEPLOYS_AUTH_USER }} 25 | deploys_pass: ${{ secrets.DEPLOYS_AUTH_PASS }} 26 | -------------------------------------------------------------------------------- /src/functions/getList.ts: -------------------------------------------------------------------------------- 1 | import { load } from 'cheerio' 2 | 3 | export const getList = async (page: number) => { 4 | const $ = load( 5 | await fetch(`https://news.sanook.com/lotto/archive/page/${page}`).then(o => 6 | o.text() 7 | ) 8 | ) 9 | 10 | const res = $( 11 | 'div.box-cell.box-cell--lotto.content > div > div > div > article.archive--lotto' 12 | ) 13 | .map((_, element) => { 14 | const titleElement = $( 15 | 'div.archive--lotto__body > div > a > div > h3.archive--lotto__head-lot', 16 | element 17 | ) 18 | const linkElement = $('div > div > a', element) 19 | 20 | const id = linkElement.attr('href')?.split('/')[5] 21 | 22 | const rawTitleText = titleElement.text() 23 | const parsedTitle = rawTitleText.substring( 24 | rawTitleText.indexOf('ตรวจหวย') + 8 25 | ) 26 | 27 | return { 28 | id: id || '', 29 | url: `/lotto/${id}`, 30 | date: parsedTitle, 31 | } 32 | }) 33 | .toArray() 34 | 35 | return res 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Phumrapee Limpianchop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.actions/build-and-push/action.yml: -------------------------------------------------------------------------------- 1 | name: Initialize 2 | description: "Composite actions to setup Node environments, and pnpm with cache handling" 3 | 4 | inputs: 5 | deploys_user: 6 | description: Deploys.app service account 7 | required: true 8 | deploys_pass: 9 | description: Deploys.app secrets 10 | required: true 11 | target_image: 12 | description: Target image name 13 | required: true 14 | 15 | runs: 16 | using: "composite" 17 | steps: 18 | - name: Build Docker image 19 | shell: bash 20 | run: docker build -t built-image . 21 | 22 | - name: Tag Docker image 23 | shell: bash 24 | run: docker tag built-image ${{ inputs.target_image }} 25 | 26 | - name: Push Docker image 27 | shell: bash 28 | run: docker push ${{ inputs.target_image }} 29 | 30 | - uses: deploys-app/deploys-action@v1 31 | with: 32 | project: rayriffy 33 | location: gke.cluster-rcf2 34 | name: thai-lotto-api 35 | image: ${{ inputs.target_image }} 36 | minReplicas: 1 37 | maxReplicas: 4 38 | env: 39 | DEPLOYS_AUTH_USER: ${{ inputs.deploys_user }} 40 | DEPLOYS_AUTH_PASS: ${{ inputs.deploys_pass }} 41 | -------------------------------------------------------------------------------- /src/models.ts: -------------------------------------------------------------------------------- 1 | import { t, type UnwrapSchema } from 'elysia' 2 | 3 | export const model = { 4 | 'lotto.overview': t.Object( 5 | { 6 | status: t.String({ 7 | default: 'success', 8 | }), 9 | response: t.Array( 10 | t.Object({ 11 | id: t.String(), 12 | url: t.String(), 13 | date: t.String(), 14 | }) 15 | ), 16 | }, 17 | { 18 | description: 'Lottery Overview', 19 | } 20 | ), 21 | 'lotto.detail': t.Object( 22 | { 23 | status: t.String({ 24 | default: 'success', 25 | }), 26 | response: t.Object({ 27 | date: t.String(), 28 | endpoint: t.String(), 29 | prizes: t.Array( 30 | t.Object({ 31 | id: t.String(), 32 | name: t.String(), 33 | reward: t.String(), 34 | amount: t.Number(), 35 | number: t.Array(t.String()), 36 | }) 37 | ), 38 | runningNumbers: t.Array( 39 | t.Object({ 40 | id: t.String(), 41 | name: t.String(), 42 | reward: t.String(), 43 | amount: t.Number(), 44 | number: t.Array(t.String()), 45 | }) 46 | ), 47 | }), 48 | }, 49 | { 50 | description: 'Full Lottery Detail', 51 | } 52 | ), 53 | 'api.error': t.Object( 54 | { 55 | status: t.String({ 56 | default: 'crash', 57 | }), 58 | response: t.String({ 59 | default: 'api cannot fulfill your request at this time', 60 | }), 61 | }, 62 | { 63 | description: 'Default error when API is unable to process the request', 64 | } 65 | ), 66 | } 67 | 68 | export interface Model { 69 | lotto: { 70 | overview: UnwrapSchema 71 | detail: UnwrapSchema 72 | } 73 | api: { 74 | error: UnwrapSchema 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 69 | -------------------------------------------------------------------------------- /src/functions/getLotto.ts: -------------------------------------------------------------------------------- 1 | import { load } from 'cheerio' 2 | import type { CheerioAPI } from 'cheerio' 3 | 4 | const scrapeText = (cheerio: CheerioAPI) => (selector: string) => 5 | cheerio(selector) 6 | .map((_, el) => cheerio(el).text()) 7 | .toArray() 8 | 9 | export const getLotto = async (targetId: string | number) => { 10 | const url = `https://news.sanook.com/lotto/check/${targetId}` 11 | 12 | const $ = load(await fetch(url).then(o => o.text())) 13 | const scraper = scrapeText($) 14 | 15 | const [ 16 | date, 17 | prizeFirst, 18 | prizeFirstNear, 19 | prizeSecond, 20 | prizeThird, 21 | prizeForth, 22 | prizeFifth, 23 | runningNumberFrontThree, 24 | runningNumberBackThree, 25 | runningNumberBackTwo, 26 | ] = await Promise.all([ 27 | $('#contentPrint > header > h2') 28 | .text() 29 | .substring($('#contentPrint > header > h2').text().indexOf(' ') + 1), 30 | scraper( 31 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--bdnone > div.lottocheck__table > div:nth-child(1) > strong.lotto__number' 32 | ), // prizeFirst 33 | scraper( 34 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--bdnone > div.lottocheck__sec--nearby > strong.lotto__number' 35 | ), // prizeFirstNear 36 | scraper( 37 | '#contentPrint > div.lottocheck__resize > div:nth-child(2) > div > span.lotto__number' 38 | ), // prizeSecond 39 | scraper( 40 | '#contentPrint > div.lottocheck__resize > div:nth-child(3) > div > span' 41 | ), // prizeThird 42 | scraper( 43 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--font-mini.lottocheck__sec--bdnoneads > div.lottocheck__box-item > span.lotto__number' 44 | ), // prizeForth 45 | scraper( 46 | '#contentPrint > div.lottocheck__resize > div:nth-child(7) > div > span.lotto__number' 47 | ), // prizeFifth 48 | scraper( 49 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--bdnone > div.lottocheck__table > div:nth-child(2) > strong.lotto__number' 50 | ), // runningNumberFrontThree 51 | scraper( 52 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--bdnone > div.lottocheck__table > div:nth-child(3) > strong.lotto__number' 53 | ), // runningNumberBackThree 54 | scraper( 55 | '#contentPrint > div.lottocheck__resize > div.lottocheck__sec.lottocheck__sec--bdnone > div.lottocheck__table > div:nth-child(4) > strong.lotto__number' 56 | ), // runningNumberBackTwo 57 | ]) 58 | 59 | return { 60 | date: date, 61 | endpoint: url, 62 | prizes: [ 63 | { 64 | id: 'prizeFirst', 65 | name: 'รางวัลที่ 1', 66 | reward: '6000000', 67 | amount: prizeFirst.length, 68 | number: prizeFirst, 69 | }, 70 | { 71 | id: 'prizeFirstNear', 72 | name: 'รางวัลข้างเคียงรางวัลที่ 1', 73 | reward: '100000', 74 | amount: prizeFirstNear.length, 75 | number: prizeFirstNear, 76 | }, 77 | { 78 | id: 'prizeSecond', 79 | name: 'รางวัลที่ 2', 80 | reward: '200000', 81 | amount: prizeSecond.length, 82 | number: prizeSecond, 83 | }, 84 | { 85 | id: 'prizeThird', 86 | name: 'รางวัลที่ 3', 87 | reward: '80000', 88 | amount: prizeThird.length, 89 | number: prizeThird, 90 | }, 91 | { 92 | id: 'prizeForth', 93 | name: 'รางวัลที่ 4', 94 | reward: '40000', 95 | amount: prizeForth.length, 96 | number: prizeForth, 97 | }, 98 | { 99 | id: 'prizeFifth', 100 | name: 'รางวัลที่ 5', 101 | reward: '20000', 102 | amount: prizeFifth.length, 103 | number: prizeFifth, 104 | }, 105 | ], 106 | runningNumbers: [ 107 | { 108 | id: 'runningNumberFrontThree', 109 | name: 'รางวัลเลขหน้า 3 ตัว', 110 | reward: '4000', 111 | amount: runningNumberFrontThree.length, 112 | number: runningNumberFrontThree, 113 | }, 114 | { 115 | id: 'runningNumberBackThree', 116 | name: 'รางวัลเลขท้าย 3 ตัว', 117 | reward: '4000', 118 | amount: runningNumberBackThree.length, 119 | number: runningNumberBackThree, 120 | }, 121 | { 122 | id: 'runningNumberBackTwo', 123 | name: 'รางวัลเลขท้าย 2 ตัว', 124 | reward: '2000', 125 | amount: runningNumberBackTwo.length, 126 | number: runningNumberBackTwo, 127 | }, 128 | ], 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Elysia, t } from 'elysia' 2 | import { cors } from '@elysiajs/cors' 3 | import { swagger } from '@elysiajs/swagger' 4 | import { logger } from '@bogeychan/elysia-logger' 5 | import { dts } from 'elysia-remote-dts' 6 | 7 | import { getList } from './functions/getList' 8 | import { getLotto } from './functions/getLotto' 9 | import { model } from './models' 10 | 11 | const app = new Elysia() 12 | .use(cors()) 13 | .use(dts('./src/index.ts')) 14 | .use( 15 | swagger({ 16 | exclude: ['/', '/ping'], 17 | }) 18 | ) 19 | .use(logger()) 20 | .model(model) 21 | .get('/', ({ set }) => (set.redirect = '/swagger')) 22 | .get('/ping', () => ({ 23 | status: 'success', 24 | response: 'pong', 25 | })) 26 | .get( 27 | '/list/:page', 28 | async ({ params: { page }, set }) => { 29 | try { 30 | const lists = await getList(page) 31 | 32 | return { 33 | status: 'success', 34 | response: lists, 35 | } 36 | } catch (e) { 37 | set.status = 500 38 | return { 39 | status: 'crash', 40 | response: 'api cannot fulfill your request at this time', 41 | } 42 | } 43 | }, 44 | { 45 | beforeHandle({ params, set }) { 46 | params.page = +params.page 47 | 48 | if (!Number.isSafeInteger(params.page)) { 49 | set.status = 400 50 | return { 51 | status: 'crash', 52 | response: 'invalid positive integer', 53 | } 54 | } 55 | }, 56 | params: t.Object({ 57 | page: t.Numeric(), 58 | }), 59 | response: { 60 | 200: 'lotto.overview', 61 | 400: 'api.error', 62 | }, 63 | schema: { 64 | detail: { 65 | summary: 'Get lotto by page', 66 | tags: ['lotto'], 67 | }, 68 | }, 69 | } 70 | ) 71 | .get( 72 | '/lotto/:id', 73 | async ({ params: { id }, set }) => { 74 | try { 75 | if (!Number.isSafeInteger(Number(id))) { 76 | set.status = 400 77 | return { 78 | status: 'crash', 79 | response: 'invalid positive integer', 80 | } 81 | } else { 82 | const lotto = await getLotto(id) 83 | 84 | // const lottoeryDate = dayjs(lotto.date, 'D MMMM YYYY', 'th') 85 | 86 | // if (lottoeryDate.isAfter(dayjs().subtract(2, 'days'))) { 87 | // res.setHeader('Cache-Control', 's-maxage=2592000') 88 | // } else { 89 | // res.setHeader('Cache-Control', 's-maxage=3600') 90 | // } 91 | 92 | // res.setHeader('Access-Control-Allow-Origin', '*') 93 | 94 | return { 95 | status: 'success', 96 | response: lotto, 97 | } 98 | } 99 | } catch (e) { 100 | set.status = 500 101 | return { 102 | status: 'crash', 103 | response: 'api cannot fulfill your request at this time', 104 | } 105 | } 106 | }, 107 | { 108 | beforeHandle({ params, set }) { 109 | if (!Number.isSafeInteger(Number(params.id))) { 110 | set.status = 400 111 | return { 112 | status: 'crash', 113 | response: 'invalid positive integer', 114 | } 115 | } 116 | }, 117 | params: t.Object({ 118 | id: t.String(), 119 | }), 120 | response: { 121 | 200: 'lotto.detail', 122 | 400: 'api.error', 123 | }, 124 | schema: { 125 | detail: { 126 | summary: 'Check lottery status by lottery number', 127 | tags: ['lotto'], 128 | }, 129 | }, 130 | } 131 | ) 132 | .get( 133 | '/latest', 134 | async ({ set }) => { 135 | try { 136 | const latestLottery = await getList(1) 137 | const lotto = await getLotto(latestLottery[0].id) 138 | 139 | // if lotto result is incomplete, then get previous lottery result 140 | if ( 141 | lotto.prizes.some(prize => 142 | prize.number.some(num => num.toLowerCase().includes('x')) 143 | ) 144 | ) { 145 | return { 146 | status: 'success', 147 | response: await getLotto(latestLottery[1].id), 148 | } 149 | } else { 150 | return { 151 | status: 'success', 152 | response: lotto, 153 | } 154 | } 155 | } catch (e) { 156 | set.status = 500 157 | return { 158 | status: 'crash', 159 | response: 'api cannot fulfill your request at this time', 160 | } 161 | } 162 | }, 163 | { 164 | response: { 165 | 200: 'lotto.detail', 166 | 400: 'api.error', 167 | }, 168 | schema: { 169 | detail: { 170 | summary: 'Latest price annoucement', 171 | tags: ['lotto'], 172 | }, 173 | }, 174 | } 175 | ) 176 | .listen(process.env.PORT ?? 3000) 177 | 178 | export type App = typeof app 179 | 180 | console.log( 181 | `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}` 182 | ) 183 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "ES2022", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Thai Lottery API 2 | ================ 3 | 4 | An API for checking latest Thai Lottery 5 | 6 | Requirements 7 | ------------ 8 | 9 | - [Bun](https://bun.sh) 10 | 11 | Development 12 | ----------- 13 | 14 | ```sh 15 | $ bun i 16 | $ bun run dev 17 | ``` 18 | 19 | Building for production 20 | --- 21 | 22 | We packed application to production by using Docker images. 23 | 24 | ```sh 25 | $ docker build -t runtime . 26 | ``` 27 | 28 | Limitations 29 | ----------- 30 | 31 | This API crawl data from sanook.com and the API cannot handle URL in case of 404 yet 32 | 33 | API 34 | --- 35 | 36 | The API is based on HTTPS requests and JSON responses. The stable HTTPS endpoint for the latest version is: `https://lotto.api.rayriffy.com` 37 | 38 | ### Get latest lottery result 39 | 40 | ##### request 41 | `GET /latest` 42 | 43 | ##### response 44 |
45 | JSON 46 | 47 | ```json 48 | { 49 | "status":"success", 50 | "response":{ 51 | "date":"30 ธันวาคม 2561", 52 | "endpoint":"https://news.sanook.com/lotto/check/30122561/", 53 | "prizes":[ 54 | { 55 | "id":"prizeFirst", 56 | "name":"รางวัลที่ 1", 57 | "reward":"6000000", 58 | "amount":1, 59 | "number":[ 60 | "735867" 61 | ] 62 | }, 63 | { 64 | "id":"prizeFirstNear", 65 | "name":"รางวัลข้างเคียงรางวัลที่ 1", 66 | "reward":"100000", 67 | "amount":2, 68 | "number":[ 69 | "735866", 70 | "735868" 71 | ] 72 | }, 73 | { 74 | "id":"prizeSecond", 75 | "name":"รางวัลที่ 2", 76 | "reward":"200000", 77 | "amount":5, 78 | "number":[ 79 | "031880", 80 | "466182", 81 | "548097", 82 | "838262", 83 | "990824" 84 | ] 85 | }, 86 | { 87 | "id":"prizeThrid", 88 | "name":"รางวัลที่ 3", 89 | "reward":"80000", 90 | "amount":5, 91 | "number":[ 92 | "049590", 93 | "063523", 94 | "237012", 95 | "259642", 96 | "348399" 97 | ] 98 | }, 99 | { 100 | "id":"prizeForth", 101 | "name":"รางวัลที่ 4", 102 | "reward":"40000", 103 | "amount":50, 104 | "number":[ 105 | "018432", 106 | "025422", 107 | "049808", 108 | "056211", 109 | "094398", 110 | "121783", 111 | "148104", 112 | "148638", 113 | "150056", 114 | "189221", 115 | "196152", 116 | "219869", 117 | "227554", 118 | "237802", 119 | "260728", 120 | "268460", 121 | "286869", 122 | "288547", 123 | "317267", 124 | "320072", 125 | "346821", 126 | "379926", 127 | "383854", 128 | "388285", 129 | "412794", 130 | "412948", 131 | "449958", 132 | "461152", 133 | "474792", 134 | "489937", 135 | "527656", 136 | "537851", 137 | "556221", 138 | "594958", 139 | "644732", 140 | "646556", 141 | "682687", 142 | "731295", 143 | "771266", 144 | "840258", 145 | "867152", 146 | "897648", 147 | "903266", 148 | "943811", 149 | "953370", 150 | "961883", 151 | "964917", 152 | "978357", 153 | "983361", 154 | "995186" 155 | ] 156 | }, 157 | { 158 | "id":"prizeFifth", 159 | "name":"รางวัลที่ 5", 160 | "reward":"20000", 161 | "amount":100, 162 | "number":[ 163 | "015058", 164 | "028293", 165 | "028606", 166 | "053976", 167 | "057188", 168 | "076979", 169 | "086025", 170 | "088404", 171 | "114402", 172 | "115726", 173 | "123167", 174 | "124132", 175 | "144169", 176 | "162592", 177 | "164805", 178 | "168795", 179 | "169152", 180 | "170811", 181 | "179718", 182 | "182023", 183 | "190866", 184 | "225839", 185 | "227691", 186 | "231646", 187 | "231912", 188 | "241934", 189 | "251830", 190 | "278673", 191 | "279372", 192 | "281526", 193 | "284837", 194 | "293893", 195 | "294604", 196 | "294670", 197 | "304360", 198 | "314093", 199 | "321218", 200 | "335344", 201 | "392746", 202 | "401511", 203 | "426861", 204 | "433739", 205 | "437494", 206 | "444284", 207 | "444854", 208 | "447606", 209 | "449838", 210 | "451979", 211 | "455457", 212 | "483172", 213 | "491712", 214 | "527546", 215 | "555996", 216 | "564587", 217 | "565011", 218 | "572138", 219 | "579551", 220 | "587670", 221 | "599175", 222 | "600249", 223 | "609415", 224 | "616992", 225 | "617281", 226 | "632558", 227 | "636087", 228 | "648256", 229 | "661753", 230 | "669104", 231 | "672017", 232 | "687652", 233 | "697383", 234 | "702306", 235 | "702607", 236 | "708124", 237 | "720464", 238 | "722030", 239 | "744770", 240 | "775333", 241 | "779634", 242 | "785705", 243 | "795585", 244 | "795779", 245 | "807768", 246 | "827729", 247 | "831475", 248 | "833686", 249 | "839802", 250 | "840137", 251 | "845082", 252 | "854427", 253 | "855559", 254 | "861761", 255 | "872372", 256 | "874608", 257 | "880273", 258 | "893374", 259 | "913405", 260 | "954538", 261 | "961018", 262 | "982520" 263 | ] 264 | } 265 | ], 266 | "runningNumbers":[ 267 | { 268 | "id":"runningNumberFrontThree", 269 | "name":"รางวัลเลขหน้า 3 ตัว", 270 | "reward":"4000", 271 | "amount":2, 272 | "number":[ 273 | "701", 274 | "884" 275 | ] 276 | }, 277 | { 278 | "id":"runningNumberBackThree", 279 | "name":"รางวัลเลขท้าย 3 ตัว", 280 | "reward":"4000", 281 | "amount":2, 282 | "number":[ 283 | "701", 284 | "884" 285 | ] 286 | }, 287 | { 288 | "id":"runningNumberBackTwo", 289 | "name":"รางวัลเลขท้าย 2 ตัว", 290 | "reward":"2000", 291 | "amount":1, 292 | "number":[ 293 | "02" 294 | ] 295 | } 296 | ] 297 | } 298 | } 299 | ``` 300 |
301 | 302 | ### List past lottery date 303 | 304 | ##### request 305 | `GET /list/[:page?]` 306 | 307 | ##### response 308 |
309 | JSON 310 | 311 | ```json 312 | { 313 | "status":"success", 314 | "response":[ 315 | { 316 | "id":"30122561", 317 | "url":"/lotto/30122561", 318 | "date":"30 ธันวาคม 2561" 319 | }, 320 | { 321 | "id":"16122561", 322 | "url":"/lotto/16122561", 323 | "date":"16 ธันวาคม 2561" 324 | }, 325 | { 326 | "id":"01122561", 327 | "url":"/lotto/01122561", 328 | "date":"1 ธันวาคม 2561" 329 | }, 330 | { 331 | "id":"16112561", 332 | "url":"/lotto/16112561", 333 | "date":"16 พฤศจิกายน 2561" 334 | }, 335 | { 336 | "id":"01112561", 337 | "url":"/lotto/01112561", 338 | "date":"1 พฤศจิกายน 2561" 339 | }, 340 | { 341 | "id":"16102561", 342 | "url":"/lotto/16102561", 343 | "date":"16 ตุลาคม 2561" 344 | }, 345 | { 346 | "id":"01102561", 347 | "url":"/lotto/01102561", 348 | "date":"1 ตุลาคม 2561" 349 | }, 350 | { 351 | "id":"16092561", 352 | "url":"/lotto/16092561", 353 | "date":"16 กันยายน 2561" 354 | }, 355 | { 356 | "id":"01092561", 357 | "url":"/lotto/01092561", 358 | "date":"1 กันยายน 2561" 359 | }, 360 | { 361 | "id":"16082561", 362 | "url":"/lotto/16082561", 363 | "date":"16 สิงหาคม 2561" 364 | }, 365 | { 366 | "id":"01082561", 367 | "url":"/lotto/01082561", 368 | "date":"1 สิงหาคม 2561" 369 | } 370 | ] 371 | } 372 | ``` 373 |
374 | 375 | ### Get past lottery result 376 | 377 | ##### request 378 | `GET /lotto/[:id]` 379 | 380 | `[:id]` can be obtain from `/list` 381 | 382 | ##### response 383 |
384 | JSON 385 | 386 | ```json 387 | { 388 | "status":"success", 389 | "response":{ 390 | "date":"1 ธันวาคม 2561", 391 | "endpoint":"https://news.sanook.com/lotto/check/01122561", 392 | "prizes":[ 393 | { 394 | "id":"prizeFirst", 395 | "name":"รางวัลที่ 1", 396 | "reward":"6000000", 397 | "amount":1, 398 | "number":[ 399 | "021840" 400 | ] 401 | }, 402 | { 403 | "id":"prizeFirstNear", 404 | "name":"รางวัลข้างเคียงรางวัลที่ 1", 405 | "reward":"100000", 406 | "amount":2, 407 | "number":[ 408 | "021839", 409 | "021841" 410 | ] 411 | }, 412 | { 413 | "id":"prizeSecond", 414 | "name":"รางวัลที่ 2", 415 | "reward":"200000", 416 | "amount":5, 417 | "number":[ 418 | "062948", 419 | "127470", 420 | "288347", 421 | "548436", 422 | "628614" 423 | ] 424 | }, 425 | { 426 | "id":"prizeThrid", 427 | "name":"รางวัลที่ 3", 428 | "reward":"80000", 429 | "amount":5, 430 | "number":[ 431 | "270065", 432 | "464017", 433 | "473504", 434 | "530912", 435 | "575061" 436 | ] 437 | }, 438 | { 439 | "id":"prizeForth", 440 | "name":"รางวัลที่ 4", 441 | "reward":"40000", 442 | "amount":50, 443 | "number":[ 444 | "016086", 445 | "116226", 446 | "124870", 447 | "129956", 448 | "169205", 449 | "182461", 450 | "187910", 451 | "189146", 452 | "205144", 453 | "207414", 454 | "242734", 455 | "256039", 456 | "283334", 457 | "301910", 458 | "336526", 459 | "352184", 460 | "362582", 461 | "382852", 462 | "394749", 463 | "394958", 464 | "397740", 465 | "401668", 466 | "404330", 467 | "407257", 468 | "438263", 469 | "473839", 470 | "576342", 471 | "585200", 472 | "594545", 473 | "603363", 474 | "611618", 475 | "633252", 476 | "636183", 477 | "637833", 478 | "690892", 479 | "709491", 480 | "730042", 481 | "731791", 482 | "755978", 483 | "792456", 484 | "876942", 485 | "883662", 486 | "898711", 487 | "947046", 488 | "971620", 489 | "987205", 490 | "988277", 491 | "992200", 492 | "995247", 493 | "995971" 494 | ] 495 | }, 496 | { 497 | "id":"prizeFifth", 498 | "name":"รางวัลที่ 5", 499 | "reward":"20000", 500 | "amount":100, 501 | "number":[ 502 | "013109", 503 | "024421", 504 | "026991", 505 | "031643", 506 | "050657", 507 | "058926", 508 | "069749", 509 | "071412", 510 | "076515", 511 | "080316", 512 | "092905", 513 | "096928", 514 | "111764", 515 | "137488", 516 | "210276", 517 | "216370", 518 | "218749", 519 | "235889", 520 | "244712", 521 | "250170", 522 | "255504", 523 | "257666", 524 | "262364", 525 | "273440", 526 | "305286", 527 | "310466", 528 | "334375", 529 | "339161", 530 | "345423", 531 | "353899", 532 | "360663", 533 | "363981", 534 | "364818", 535 | "373331", 536 | "381198", 537 | "395963", 538 | "421785", 539 | "428210", 540 | "437231", 541 | "529822", 542 | "546498", 543 | "553767", 544 | "555761", 545 | "559922", 546 | "560040", 547 | "563302", 548 | "588125", 549 | "593122", 550 | "594897", 551 | "601621", 552 | "618166", 553 | "620234", 554 | "623563", 555 | "627915", 556 | "629906", 557 | "634940", 558 | "641039", 559 | "641321", 560 | "667984", 561 | "670679", 562 | "674943", 563 | "682070", 564 | "690240", 565 | "694936", 566 | "709212", 567 | "713159", 568 | "714334", 569 | "720766", 570 | "765748", 571 | "766906", 572 | "770412", 573 | "774771", 574 | "783683", 575 | "785299", 576 | "797030", 577 | "803837", 578 | "804359", 579 | "817707", 580 | "840520", 581 | "852313", 582 | "852630", 583 | "856027", 584 | "856216", 585 | "857880", 586 | "869371", 587 | "873773", 588 | "875098", 589 | "876651", 590 | "917189", 591 | "932242", 592 | "934569", 593 | "935834", 594 | "936538", 595 | "937265", 596 | "944402", 597 | "958246", 598 | "971100", 599 | "987737", 600 | "988424", 601 | "993708" 602 | ] 603 | } 604 | ], 605 | "runningNumbers":[ 606 | { 607 | "id":"runningNumberFrontThree", 608 | "name":"รางวัลเลขหน้า 3 ตัว", 609 | "reward":"4000", 610 | "amount":2, 611 | "number":[ 612 | "561", 613 | "988" 614 | ] 615 | }, 616 | { 617 | "id":"runningNumberBackThree", 618 | "name":"รางวัลเลขท้าย 3 ตัว", 619 | "reward":"4000", 620 | "amount":2, 621 | "number":[ 622 | "561", 623 | "988" 624 | ] 625 | }, 626 | { 627 | "id":"runningNumberBackTwo", 628 | "name":"รางวัลเลขท้าย 2 ตัว", 629 | "reward":"2000", 630 | "amount":1, 631 | "number":[ 632 | "67" 633 | ] 634 | } 635 | ] 636 | } 637 | } 638 | ``` 639 |
640 | 641 | Contributing 642 | ------------ 643 | 644 | We welcome all contributions by sending PR to this repository. 645 | 646 | Need Help ? 647 | ----------- 648 | 649 | If you need help with anything, here're following methods: 650 | 651 | #### Create an Issue 652 | 653 | If you have something you want to discuss in detail, or have hit an issue which you believe others will also have in deployment or development of the system, [opening an issue](https://github.com/rayriffy/thai-lotto-api/issues) is the best way to get help. It creates a permanent resource for others wishing to contribute to conversation. 654 | -------------------------------------------------------------------------------- /bun.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockfileVersion": 1, 3 | "workspaces": { 4 | "": { 5 | "name": "thai-lotto-api", 6 | "dependencies": { 7 | "@bogeychan/elysia-logger": "0.1.8", 8 | "@elysiajs/cors": "1.3.3", 9 | "@elysiajs/swagger": "1.3.0", 10 | "cheerio": "1.0.0", 11 | "elysia": "1.3.1", 12 | "elysia-remote-dts": "1.0.3", 13 | }, 14 | "devDependencies": { 15 | "@types/bun": "1.2.13", 16 | "typescript": "5.8.3", 17 | }, 18 | }, 19 | }, 20 | "packages": { 21 | "@bogeychan/elysia-logger": ["@bogeychan/elysia-logger@0.1.8", "", { "dependencies": { "pino": "^9.6.0" }, "peerDependencies": { "elysia": ">= 1.2.10" } }, "sha512-TbCpMX+m68t0FbvpbBjMrCs4HQ9f1twkvTSGf6ShAkjash7zP9vGLGnEJ0iSG0ymgqLNN8Dgq0SdAEaMC6XLug=="], 22 | 23 | "@elysiajs/cors": ["@elysiajs/cors@1.3.3", "", { "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-mYIU6PyMM6xIJuj7d27Vt0/wuzVKIEnFPjcvlkyd7t/m9xspAG37cwNjFxVOnyvY43oOd2I/oW2DB85utXpA2Q=="], 24 | 25 | "@elysiajs/swagger": ["@elysiajs/swagger@1.3.0", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-0fo3FWkDRPNYpowJvLz3jBHe9bFe6gruZUyf+feKvUEEMG9ZHptO1jolSoPE0ffFw1BgN1/wMsP19p4GRXKdfg=="], 26 | 27 | "@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="], 28 | 29 | "@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="], 30 | 31 | "@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="], 32 | 33 | "@sinclair/typebox": ["@sinclair/typebox@0.34.33", "", {}, "sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g=="], 34 | 35 | "@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="], 36 | 37 | "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], 38 | 39 | "@types/bun": ["@types/bun@1.2.13", "", { "dependencies": { "bun-types": "1.2.13" } }, "sha512-u6vXep/i9VBxoJl3GjZsl/BFIsvML8DfVDO0RYLEwtSZSp981kEO1V5NwRcO1CPJ7AmvpbnDCiMKo3JvbDEjAg=="], 40 | 41 | "@types/node": ["@types/node@22.15.3", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw=="], 42 | 43 | "@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="], 44 | 45 | "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], 46 | 47 | "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], 48 | 49 | "bun-types": ["bun-types@1.2.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-rRjA1T6n7wto4gxhAO/ErZEtOXyEZEmnIHQfl0Dt1QQSB4QV0iP6BZ9/YB5fZaHFQ2dwHFrmPaRQ9GGMX01k9Q=="], 50 | 51 | "cheerio": ["cheerio@1.0.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "encoding-sniffer": "^0.2.0", "htmlparser2": "^9.1.0", "parse5": "^7.1.2", "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" } }, "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww=="], 52 | 53 | "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], 54 | 55 | "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], 56 | 57 | "css-select": ["css-select@5.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg=="], 58 | 59 | "css-what": ["css-what@6.1.0", "", {}, "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="], 60 | 61 | "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], 62 | 63 | "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], 64 | 65 | "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], 66 | 67 | "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], 68 | 69 | "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], 70 | 71 | "elysia": ["elysia@1.3.1", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.1.2", "fast-decode-uri-component": "^1.0.1" }, "optionalDependencies": { "@sinclair/typebox": "^0.34.33", "openapi-types": "^12.1.3" }, "peerDependencies": { "file-type": ">= 20.0.0", "typescript": ">= 5.0.0" } }, "sha512-En41P6cDHcHtQ0nvfsn9ayB+8ahQJqG1nzvPX8FVZjOriFK/RtZPQBtXMfZDq/AsVIk7JFZGFEtAVEmztNJVhQ=="], 72 | 73 | "elysia-remote-dts": ["elysia-remote-dts@1.0.3", "", { "dependencies": { "debug": "4.4.0", "get-tsconfig": "4.10.0" }, "peerDependencies": { "elysia": ">= 1.0.0", "typescript": ">=5" } }, "sha512-mf6zyY15XoBERRnKG3wy2SW+4dTYxJKx0OuiUBUX1grPyksG6xg65VfTXhb3BLc3zMkfWhDNh+bex9b8U1ucXQ=="], 74 | 75 | "encoding-sniffer": ["encoding-sniffer@0.2.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg=="], 76 | 77 | "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 78 | 79 | "exact-mirror": ["exact-mirror@0.1.2", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-wFCPCDLmHbKGUb8TOi/IS7jLsgR8WVDGtDK3CzcB4Guf/weq7G+I+DkXiRSZfbemBFOxOINKpraM6ml78vo8Zw=="], 80 | 81 | "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], 82 | 83 | "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], 84 | 85 | "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], 86 | 87 | "file-type": ["file-type@20.5.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.6", "strtok3": "^10.2.0", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg=="], 88 | 89 | "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], 90 | 91 | "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], 92 | 93 | "htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], 94 | 95 | "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], 96 | 97 | "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 98 | 99 | "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 100 | 101 | "nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], 102 | 103 | "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], 104 | 105 | "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], 106 | 107 | "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], 108 | 109 | "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], 110 | 111 | "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], 112 | 113 | "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], 114 | 115 | "pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="], 116 | 117 | "peek-readable": ["peek-readable@7.0.0", "", {}, "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ=="], 118 | 119 | "pino": ["pino@9.6.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^4.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-i85pKRCt4qMjZ1+L7sy2Ag4t1atFcdbEt76+7iRJn1g2BvsnRMGu9p8pivl9fs63M2kF/A0OacFZhTub+m/qMg=="], 120 | 121 | "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], 122 | 123 | "pino-std-serializers": ["pino-std-serializers@7.0.0", "", {}, "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA=="], 124 | 125 | "process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], 126 | 127 | "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], 128 | 129 | "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], 130 | 131 | "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], 132 | 133 | "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], 134 | 135 | "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 136 | 137 | "sonic-boom": ["sonic-boom@4.2.0", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="], 138 | 139 | "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 140 | 141 | "strtok3": ["strtok3@10.2.2", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^7.0.0" } }, "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg=="], 142 | 143 | "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], 144 | 145 | "token-types": ["token-types@6.0.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA=="], 146 | 147 | "type-fest": ["type-fest@4.40.1", "", {}, "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="], 148 | 149 | "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], 150 | 151 | "uint8array-extras": ["uint8array-extras@1.4.0", "", {}, "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ=="], 152 | 153 | "undici": ["undici@6.21.2", "", {}, "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g=="], 154 | 155 | "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 156 | 157 | "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], 158 | 159 | "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], 160 | 161 | "zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="], 162 | 163 | "zod": ["zod@3.24.3", "", {}, "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg=="], 164 | 165 | "@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="], 166 | 167 | "parse5/entities": ["entities@6.0.0", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="], 168 | 169 | "@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="], 170 | } 171 | } 172 | --------------------------------------------------------------------------------