├── .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 |
4 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
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 |
--------------------------------------------------------------------------------