diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index ff563da3..33f33714 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -29,7 +29,7 @@ jobs: with: name: database path: scripts/database - - run: npm run guides:update + - run: npm run guides:update_legacy - uses: tibdex/github-app-token@v1 if: ${{ !env.ACT }} id: create-deploy-token diff --git a/.github/workflows/_update-api.yml b/.github/workflows/_update-api.yml deleted file mode 100644 index 39f7f5a6..00000000 --- a/.github/workflows/_update-api.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: _update-api -on: - workflow_dispatch: - schedule: - - cron: '0 9 * * *' -jobs: - update: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - if: ${{ !env.ACT }} - with: - node-version: 16 - cache: 'npm' - - run: npm install - - run: npm run api:update - - uses: tibdex/github-app-token@v1 - if: ${{ !env.ACT }} - id: create-app-token - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: JamesIves/github-pages-deploy-action@v4.4.1 - if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} - with: - repository-name: iptv-org/api - branch: gh-pages - folder: .api - token: ${{ steps.create-app-token.outputs.token }} - git-config-name: iptv-bot[bot] - git-config-email: 84861620+iptv-bot[bot]@users.noreply.github.com - commit-message: '[Bot] Deploy to GitHub Pages' - clean: false diff --git a/.github/workflows/_update-status.yml b/.github/workflows/_update-status.yml deleted file mode 100644 index c7f67177..00000000 --- a/.github/workflows/_update-status.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: _update-status -on: - workflow_dispatch: - schedule: - - cron: '0 9 * * *' -jobs: - update: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - run: echo "BRANCH_NAME=$(date +'bot/update-status-%s')" >> $GITHUB_OUTPUT - id: create-branch-name - - run: git config user.name 'iptv-bot[bot]' - - run: git config user.email '84861620+iptv-bot[bot]@users.noreply.github.com' - - run: git checkout -b ${{ steps.create-branch-name.outputs.BRANCH_NAME }} - - uses: actions/setup-node@v3 - if: ${{ !env.ACT }} - with: - node-version: 16 - cache: 'npm' - - run: npm install - - run: npm run status:update - - name: Commit Changes - if: ${{ !env.ACT }} - run: | - git add STATUS.md - git commit -m "[Bot] Update STATUS.md" - git status - git push -u origin ${{ steps.create-branch-name.outputs.BRANCH_NAME }} - - uses: tibdex/github-app-token@v1 - if: ${{ !env.ACT }} - id: create-app-token - with: - app_id: ${{ secrets.APP_ID }} - private_key: ${{ secrets.APP_PRIVATE_KEY }} - - uses: repo-sync/pull-request@v2 - if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} - id: pull-request - with: - github_token: ${{ steps.create-app-token.outputs.token }} - source_branch: ${{ steps.create-branch-name.outputs.BRANCH_NAME }} - destination_branch: 'master' - pr_title: '[Bot] Update STATUS.md' - pr_body: | - This pull request is created via [update-status][1] workflow. - - [1]: https://github.com/iptv-org/epg/actions/runs/${{ github.run_id }} - - uses: juliangruber/merge-pull-request-action@v1 - if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} - with: - github-token: ${{ secrets.PAT }} - number: ${{ steps.pull-request.outputs.pr_number }} - method: squash diff --git a/.github/workflows/_update-readme.yml b/.github/workflows/_update.yml similarity index 51% rename from .github/workflows/_update-readme.yml rename to .github/workflows/_update.yml index 41893691..955bdbce 100644 --- a/.github/workflows/_update-readme.yml +++ b/.github/workflows/_update.yml @@ -1,4 +1,4 @@ -name: _update-readme +name: _update on: workflow_dispatch: schedule: @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: echo "BRANCH_NAME=$(date +'bot/update-readme-%s')" >> $GITHUB_OUTPUT + - run: echo "BRANCH_NAME=$(date +'bot/auto-update-%s')" >> $GITHUB_OUTPUT id: create-branch-name - run: git config user.name 'iptv-bot[bot]' - run: git config user.email '84861620+iptv-bot[bot]@users.noreply.github.com' @@ -18,22 +18,54 @@ jobs: with: node-version: 16 cache: 'npm' - - run: npm install - - run: npm run api:load - - run: npm run readme:update - - name: Commit Changes - if: ${{ !env.ACT }} - run: | - git add README.md - git commit -m "[Bot] Update README.md" - git status - git push -u origin ${{ steps.create-branch-name.outputs.BRANCH_NAME }} - uses: tibdex/github-app-token@v1 if: ${{ !env.ACT }} id: create-app-token with: app_id: ${{ secrets.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} + - run: npm install + - run: npm run api:load + - if: ${{ !env.ACT }} + run: GITHUB_TOKEN=${{ steps.create-app-token.outputs.token }} npm run programs:load + - run: npm run guides:update + - uses: JamesIves/github-pages-deploy-action@v4.4.1 + if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} + with: + branch: gh-pages + folder: .gh-pages + token: ${{ steps.create-deploy-token.outputs.token }} + git-config-name: iptv-bot[bot] + git-config-email: 84861620+iptv-bot[bot]@users.noreply.github.com + commit-message: '[Bot] Update guides' + clean: false + force: false + - run: npm run api:update + - uses: JamesIves/github-pages-deploy-action@v4.4.1 + if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} + with: + repository-name: iptv-org/api + branch: gh-pages + folder: .api + token: ${{ steps.create-app-token.outputs.token }} + git-config-name: iptv-bot[bot] + git-config-email: 84861620+iptv-bot[bot]@users.noreply.github.com + commit-message: '[Bot] Deploy to iptv-org/api' + clean: false + - run: npm run readme:update + - if: ${{ !env.ACT }} + run: | + git add README.md + git commit -m "[Bot] Update README.md" + git status + git push -u origin ${{ steps.create-branch-name.outputs.BRANCH_NAME }} + - run: npm run status:update + - if: ${{ !env.ACT }} + run: | + git add STATUS.md + git commit -m "[Bot] Update STATUS.md" + git status + git push -u origin ${{ steps.create-branch-name.outputs.BRANCH_NAME }} - uses: repo-sync/pull-request@v2 if: ${{ !env.ACT && github.ref == 'refs/heads/master' }} id: pull-request @@ -43,7 +75,7 @@ jobs: destination_branch: 'master' pr_title: '[Bot] Update README.md' pr_body: | - This pull request is created via [update-readme][1] workflow. + This pull request is created via [update][1] workflow. [1]: https://github.com/iptv-org/epg/actions/runs/${{ github.run_id }} - uses: juliangruber/merge-pull-request-action@v1 diff --git a/.readme/.gitignore b/.readme/.gitignore index 33dfaaae..7453eaa7 100644 --- a/.readme/.gitignore +++ b/.readme/.gitignore @@ -1,3 +1,2 @@ -_ca-provinces.md _countries.md -_us-states.md \ No newline at end of file +_sites.md \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0cb235a7..f51a637c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "license": "MIT", "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", + "@octokit/core": "^4.1.0", "axios": "^0.21.1", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.10", @@ -22,6 +23,7 @@ "iconv-lite": "^0.4.24", "inquirer": "^8.2.0", "jest": "^27.3.1", + "langs": "^2.0.0", "libxmljs2": "^0.30.1", "lodash": "^4.17.21", "markdown-include": "^0.4.3", @@ -35,6 +37,7 @@ "srcset": "^4.0.0", "tabletojson": "^2.0.7", "transliteration": "^2.2.0", + "unzipit": "^1.4.0", "wildcard-match": "^5.1.2" }, "devDependencies": { @@ -967,6 +970,102 @@ "node": ">=10" } }, + "node_modules/@octokit/auth-token": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz", + "integrity": "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==", + "dependencies": { + "@octokit/types": "^8.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz", + "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==", + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz", + "integrity": "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==", + "dependencies": { + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz", + "integrity": "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^8.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", + "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==" + }, + "node_modules/@octokit/request": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz", + "integrity": "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==", + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz", + "integrity": "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==", + "dependencies": { + "@octokit/types": "^8.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/types": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", + "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "dependencies": { + "@octokit/openapi-types": "^14.0.0" + } + }, "node_modules/@seald-io/binary-search-tree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", @@ -1495,6 +1594,11 @@ } ] }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -2144,6 +2248,11 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "node_modules/detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -3374,6 +3483,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -4233,6 +4350,11 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/langs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/langs/-/langs-2.0.0.tgz", + "integrity": "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -5840,6 +5962,11 @@ "is-typedarray": "^1.0.0" } }, + "node_modules/universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -5848,6 +5975,17 @@ "node": ">= 10.0.0" } }, + "node_modules/unzipit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.0.tgz", + "integrity": "sha512-hjoB8j1igXJgmxqaAvqkIW+faKTpG9cPK6QvkBhNCyd8OPWqODXTBVqTEmZbz62K5J/dX4Xa8lTa0NRikQwSjQ==", + "dependencies": { + "uzip-module": "^1.0.2" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5862,6 +6000,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "node_modules/uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -6846,6 +6989,84 @@ } } }, + "@octokit/auth-token": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz", + "integrity": "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==", + "requires": { + "@octokit/types": "^8.0.0" + } + }, + "@octokit/core": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz", + "integrity": "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==", + "requires": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/endpoint": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz", + "integrity": "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==", + "requires": { + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/graphql": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz", + "integrity": "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==", + "requires": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^8.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/openapi-types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz", + "integrity": "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==" + }, + "@octokit/request": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz", + "integrity": "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==", + "requires": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^8.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "@octokit/request-error": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz", + "integrity": "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==", + "requires": { + "@octokit/types": "^8.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "@octokit/types": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz", + "integrity": "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==", + "requires": { + "@octokit/openapi-types": "^14.0.0" + } + }, "@seald-io/binary-search-tree": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz", @@ -7270,6 +7491,11 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -7760,6 +7986,11 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, "detect-libc": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", @@ -8634,6 +8865,11 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, "is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -9293,6 +9529,11 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "langs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/langs/-/langs-2.0.0.tgz", + "integrity": "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==" + }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10496,11 +10737,24 @@ "is-typedarray": "^1.0.0" } }, + "universal-user-agent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, + "unzipit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/unzipit/-/unzipit-1.4.0.tgz", + "integrity": "sha512-hjoB8j1igXJgmxqaAvqkIW+faKTpG9cPK6QvkBhNCyd8OPWqODXTBVqTEmZbz62K5J/dX4Xa8lTa0NRikQwSjQ==", + "requires": { + "uzip-module": "^1.0.2" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10515,6 +10769,11 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "uzip-module": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz", + "integrity": "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index d29da832..43a990bb 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,12 @@ "channels:editor": "node scripts/commands/channels/editor.js", "queue:create": "node scripts/commands/queue/create.js", "cluster:load": "node scripts/commands/cluster/load.js", + "programs:load": "node scripts/commands/programs/load.js", "programs:save": "node scripts/commands/programs/save.js", - "guides:update": "node scripts/commands/guides/update.js", + "guides:update": "NODE_OPTIONS=--max-old-space-size=4096 node scripts/commands/guides/update.js", + "guides:update_legacy": "node scripts/commands/guides/update_legacy.js", + "api:load": "./scripts/commands/api/load.sh", "api:update": "node scripts/commands/api/update.js", - "api:load": "mkdir -p scripts/data && curl -L -o scripts/data/channels.json https://iptv-org.github.io/api/channels.json && curl -L -o scripts/data/countries.json https://iptv-org.github.io/api/countries.json", "readme:update": "node scripts/commands/readme/update.js", "status:update": "node scripts/commands/status/update.js", "lint": "npx eslint ./scripts/**/*.js", @@ -34,6 +36,7 @@ }, "dependencies": { "@alex_neo/jest-expect-message": "^1.0.5", + "@octokit/core": "^4.1.0", "axios": "^0.21.1", "chalk": "^4.1.2", "cheerio": "^1.0.0-rc.10", @@ -48,6 +51,7 @@ "iconv-lite": "^0.4.24", "inquirer": "^8.2.0", "jest": "^27.3.1", + "langs": "^2.0.0", "libxmljs2": "^0.30.1", "lodash": "^4.17.21", "markdown-include": "^0.4.3", @@ -61,6 +65,7 @@ "srcset": "^4.0.0", "tabletojson": "^2.0.7", "transliteration": "^2.2.0", + "unzipit": "^1.4.0", "wildcard-match": "^5.1.2" }, "devDependencies": { diff --git a/scripts/.gitignore b/scripts/.gitignore index 5deebb54..220083a6 100644 --- a/scripts/.gitignore +++ b/scripts/.gitignore @@ -1,3 +1,3 @@ -database/ -logs/ -data/ \ No newline at end of file +/database/ +/logs/ +/data/ \ No newline at end of file diff --git a/scripts/commands/api/load.sh b/scripts/commands/api/load.sh new file mode 100755 index 00000000..00b0a62a --- /dev/null +++ b/scripts/commands/api/load.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +mkdir -p scripts/data +curl -L -o scripts/data/channels.json https://iptv-org.github.io/api/channels.json +curl -L -o scripts/data/countries.json https://iptv-org.github.io/api/countries.json +curl -L -o scripts/data/regions.json https://iptv-org.github.io/api/regions.json +curl -L -o scripts/data/subdivisions.json https://iptv-org.github.io/api/subdivisions.json \ No newline at end of file diff --git a/scripts/commands/api/update.js b/scripts/commands/api/update.js index f11afa47..db5212bd 100644 --- a/scripts/commands/api/update.js +++ b/scripts/commands/api/update.js @@ -2,35 +2,22 @@ const { file, parser, logger } = require('../../core') const { program } = require('commander') const _ = require('lodash') -const CHANNELS_PATH = process.env.CHANNELS_PATH || 'sites/**/*.channels.xml' +const LOGS_DIR = process.env.LOGS_DIR || 'scripts/logs' const OUTPUT_DIR = process.env.OUTPUT_DIR || '.api' async function main() { let guides = [] - const files = await file.list(CHANNELS_PATH).catch(console.error) - for (const filepath of files) { - try { - const { site, channels } = await parser.parseChannels(filepath) - const dir = file.dirname(filepath) - const config = require(file.resolve(`${dir}/${site}.config.js`)) - if (config.ignore) continue + const logPath = `${LOGS_DIR}/guides/update.log` + const results = await parser.parseLogs(logPath) - const filename = file.basename(filepath) - const [__, suffix] = filename.match(/\_(.*)\.channels\.xml$/) || [null, null] - - for (const channel of channels) { - guides.push({ - channel: channel.id, - site: channel.site, - lang: channel.lang, - url: `https://iptv-org.github.io/epg/guides/${suffix}/${site}.epg.xml.gz` - }) - } - } catch (err) { - console.error(err) - continue - } + for (const result of results) { + guides.push({ + channel: result.channel, + site: result.site, + lang: result.lang, + url: `https://iptv-org.github.io/epg/guides/${result.filename}.xml.gz` + }) } guides = _.sortBy(guides, 'channel') diff --git a/scripts/commands/guides/update.js b/scripts/commands/guides/update.js index 19173b19..6ef36a1a 100644 --- a/scripts/commands/guides/update.js +++ b/scripts/commands/guides/update.js @@ -1,84 +1,139 @@ -const { db, logger, file, zip } = require('../../core') +const { db, api, logger, file, zip } = require('../../core') const { generateXMLTV, Program, Channel } = require('epg-grabber') const _ = require('lodash') +const langs = require('langs') const PUBLIC_DIR = process.env.PUBLIC_DIR || '.gh-pages' +const LOGS_DIR = process.env.LOGS_DIR || 'scripts/logs' const CURR_DATE = process.env.CURR_DATE || new Date() +const logPath = `${LOGS_DIR}/guides/update.log` + async function main() { - logger.info(`Generating guides/...`) + logger.info(`starting...`) - logger.info('Loading "database/programs.db"...') + logger.info('loading API data...') + await api.countries.load() + await api.channels.load() + await api.regions.load() + await api.subdivisions.load() + + let countries = await api.countries.all() + let api_channels = await api.channels.all() + + let channels_dic = {} + api_channels.forEach(channel => { + channels_dic[channel.id] = channel + }) + + let api_regions = await api.regions.all() + let api_subdivisions = await api.subdivisions.all() + + logger.info('loading database/programs.db...') await db.programs.load() + let db_programs = await db.programs.find({}) + db_programs = db_programs + .map(p => { + if (p.titles.length) { + p.lang = p.titles[0].lang + return p + } + return null + }) + .filter(Boolean) + logger.info(`found ${db_programs.length} programs`) - let total = 0 - const grouped = groupByGroup(await loadQueue()) - for (const key in grouped) { - let channels = {} - let programs = [] - for (const item of grouped[key]) { - if (item.error) continue + logger.info(`creating ${logPath}...`) + await file.create(logPath) - const itemPrograms = await loadProgramsForItem(item) - programs = programs.concat(itemPrograms) + for (let country of countries) { + let countryBroadcastCode = `c/${country.code}` + let countryRegions = api_regions + .filter(r => r.countries.includes(country.code)) + .map(r => `r/${r.code}`) + let countrySubdivisions = api_subdivisions + .filter(s => s.country === country.code) + .map(s => `s/${s.code}`) + let broadcastCodes = [countryBroadcastCode, ...countryRegions, ...countrySubdivisions] - if (channels[item.channel.id]) continue - channels[item.channel.id] = new Channel(item.channel) + let countryChannels = api_channels.filter( + c => _.intersection(c.broadcast_area, broadcastCodes).length + ) + countryChannels = countryChannels.map(c => c.id) + + let countryPrograms = db_programs.filter(p => countryChannels.includes(p.channel)) + let langGroups = _.groupBy(countryPrograms, 'lang') + + for (let langCode of country.languages) { + const lang = convertLangCode(langCode, '3', '1') + if (!lang) continue + + let langPrograms = langGroups[lang] + if (!langPrograms || !langPrograms.length) continue + + let programs = [] + let channelGroups = _.groupBy(langPrograms, 'channel') + for (let groupedPrograms of Object.values(channelGroups)) { + let channelPrograms = getChannelPrograms(groupedPrograms) + if (!channelPrograms.length) continue + + programs = programs.concat(channelPrograms) + } + programs = _.sortBy(programs, ['channel', 'start']) + programs = programs.map(p => new Program(p, new Channel(channels_dic[p.channel]))) + let channels = programs.map(p => { + let c = channels_dic[p.channel] + c.site = p.site + c.lang = lang + + return new Channel(c) + }) + channels = _.sortBy(channels, 'id') + channels = _.uniqBy(channels, 'id') + + const filename = `${country.code.toLowerCase()}_${lang}` + const xmlFilepath = `${PUBLIC_DIR}/guides/${filename}.xml` + const gzFilepath = `${PUBLIC_DIR}/guides/${filename}.xml.gz` + const jsonFilepath = `${PUBLIC_DIR}/guides/${filename}.json` + logger.info(`creating ${xmlFilepath}...`) + const xmltv = generateXMLTV({ + channels, + programs, + date: CURR_DATE + }) + await file.create(xmlFilepath, xmltv) + logger.info(`creating ${gzFilepath}...`) + const compressed = await zip.compress(xmltv) + await file.create(gzFilepath, compressed) + logger.info(`creating ${jsonFilepath}...`) + await file.create(jsonFilepath, JSON.stringify({ channels, programs })) + + for (let channel of channels) { + let result = { + country: country.code, + lang, + site: channel.site, + channel: channel.id, + filename + } + + await file.append(logPath, JSON.stringify(result) + '\r\n') + } } - programs = _.sortBy(programs, ['channel', 'start']) - programs = programs.map(p => new Program(p, channels[p.channel])) - programs = _.uniqBy(programs, p => p.channel + p.start) - total += programs.length - channels = Object.values(channels) - channels = _.sortBy(channels, 'id') - - const xmlFilepath = `${PUBLIC_DIR}/guides/${key}.epg.xml` - const gzFilepath = `${PUBLIC_DIR}/guides/${key}.epg.xml.gz` - const jsonFilepath = `${PUBLIC_DIR}/guides/${key}.epg.json` - logger.info(`Creating "${xmlFilepath}"...`) - const xmltv = generateXMLTV({ channels, programs, date: CURR_DATE }) - await file.create(xmlFilepath, xmltv) - logger.info(`Creating "${gzFilepath}"...`) - const compressed = await zip.compress(xmltv) - await file.create(gzFilepath, compressed) - logger.info(`Creating "${jsonFilepath}"...`) - await file.create(jsonFilepath, JSON.stringify({ channels, programs })) - } - - if (!total) { - logger.error('\nError: No programs found') - process.exit(1) - } else { - logger.info(`Done`) } } main() -function groupByGroup(items = []) { - const groups = {} +function convertLangCode(code, from, to) { + let found = langs.where(from, code) - items.forEach(item => { - item.groups.forEach(key => { - if (!groups[key]) { - groups[key] = [] - } - - groups[key].push(item) - }) - }) - - return groups + return found ? found[to] : null } -async function loadQueue() { - logger.info('Loading queue...') +function getChannelPrograms(programs) { + let groups = _.groupBy(programs, 'site') + groups = Object.values(groups) - await db.queue.load() - - return await db.queue.find({}).sort({ xmltv_id: 1 }) -} - -async function loadProgramsForItem(item) { - return await db.programs.find({ _qid: item._id }).sort({ channel: 1, start: 1 }) + return groups[0] } diff --git a/scripts/commands/guides/update_legacy.js b/scripts/commands/guides/update_legacy.js new file mode 100644 index 00000000..19173b19 --- /dev/null +++ b/scripts/commands/guides/update_legacy.js @@ -0,0 +1,84 @@ +const { db, logger, file, zip } = require('../../core') +const { generateXMLTV, Program, Channel } = require('epg-grabber') +const _ = require('lodash') + +const PUBLIC_DIR = process.env.PUBLIC_DIR || '.gh-pages' +const CURR_DATE = process.env.CURR_DATE || new Date() + +async function main() { + logger.info(`Generating guides/...`) + + logger.info('Loading "database/programs.db"...') + await db.programs.load() + + let total = 0 + const grouped = groupByGroup(await loadQueue()) + for (const key in grouped) { + let channels = {} + let programs = [] + for (const item of grouped[key]) { + if (item.error) continue + + const itemPrograms = await loadProgramsForItem(item) + programs = programs.concat(itemPrograms) + + if (channels[item.channel.id]) continue + channels[item.channel.id] = new Channel(item.channel) + } + programs = _.sortBy(programs, ['channel', 'start']) + programs = programs.map(p => new Program(p, channels[p.channel])) + programs = _.uniqBy(programs, p => p.channel + p.start) + total += programs.length + channels = Object.values(channels) + channels = _.sortBy(channels, 'id') + + const xmlFilepath = `${PUBLIC_DIR}/guides/${key}.epg.xml` + const gzFilepath = `${PUBLIC_DIR}/guides/${key}.epg.xml.gz` + const jsonFilepath = `${PUBLIC_DIR}/guides/${key}.epg.json` + logger.info(`Creating "${xmlFilepath}"...`) + const xmltv = generateXMLTV({ channels, programs, date: CURR_DATE }) + await file.create(xmlFilepath, xmltv) + logger.info(`Creating "${gzFilepath}"...`) + const compressed = await zip.compress(xmltv) + await file.create(gzFilepath, compressed) + logger.info(`Creating "${jsonFilepath}"...`) + await file.create(jsonFilepath, JSON.stringify({ channels, programs })) + } + + if (!total) { + logger.error('\nError: No programs found') + process.exit(1) + } else { + logger.info(`Done`) + } +} + +main() + +function groupByGroup(items = []) { + const groups = {} + + items.forEach(item => { + item.groups.forEach(key => { + if (!groups[key]) { + groups[key] = [] + } + + groups[key].push(item) + }) + }) + + return groups +} + +async function loadQueue() { + logger.info('Loading queue...') + + await db.queue.load() + + return await db.queue.find({}).sort({ xmltv_id: 1 }) +} + +async function loadProgramsForItem(item) { + return await db.programs.find({ _qid: item._id }).sort({ channel: 1, start: 1 }) +} diff --git a/scripts/commands/programs/load.js b/scripts/commands/programs/load.js new file mode 100644 index 00000000..e3dc521f --- /dev/null +++ b/scripts/commands/programs/load.js @@ -0,0 +1,110 @@ +const { Octokit } = require('@octokit/core') +const dayjs = require('dayjs') +const isToday = require('dayjs/plugin/isToday') +const utc = require('dayjs/plugin/utc') +const unzipit = require('unzipit') +const { file, logger } = require('../../core') + +dayjs.extend(isToday) +dayjs.extend(utc) + +const DB_DIR = process.env.DB_DIR || './scripts/database' +const dbPath = `${DB_DIR}/programs.db` + +const octokit = new Octokit({ + auth: process.env.GITHUB_TOKEN +}) + +async function main() { + try { + let workflows = await getWorkflows() + logger.info(`found ${workflows.length} workflows\r\n`) + + await file.create(dbPath) + const total = workflows.length + for (let [i, workflow] of workflows.entries()) { + logger.info(`[${i + 1}/${total}] ${workflow.name}`) + const run = await getWorkflowRun(workflow) + if (!run) continue + + let artifact = await getRunArtifacts(run) + + const buffer = await downloadArtifact(artifact) + await file.append(dbPath, buffer) + } + } catch (err) { + console.log(err.message) + } +} + +main() + +async function downloadArtifact(artifact) { + let results = await octokit.request( + 'GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}', + { + owner: 'iptv-org', + repo: 'epg', + artifact_id: artifact.id, + archive_format: 'zip' + } + ) + + const { entries } = await unzipit.unzip(results.data) + + const arrayBuffer = await entries['programs.db'].arrayBuffer() + + return toString(arrayBuffer) +} + +async function getRunArtifacts(run) { + let results = await octokit.request('GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts', { + owner: 'iptv-org', + repo: 'epg', + run_id: run.id + }) + + return results.data.artifacts.find(a => a.name === 'database') +} + +async function getWorkflowRun(workflow) { + let today = dayjs.utc().format('YYYY-MM-DD') + let results = await octokit.request( + 'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs', + { + owner: 'iptv-org', + repo: 'epg', + workflow_id: workflow.id, + status: 'success', + created: `>=${today}` + } + ) + + return results.data.workflow_runs.find( + r => r.event === 'schedule' || r.event === 'workflow_dispatch' + ) +} + +async function getWorkflows() { + let workflows = [] + for (let page of [1, 2, 3]) { + try { + let results = await octokit.request('GET /repos/{owner}/{repo}/actions/workflows', { + owner: 'iptv-org', + repo: 'epg', + per_page: 100, + page + }) + + workflows = workflows.concat(results.data.workflows) + } catch (err) { + console.log(err.message) + } + } + + return workflows.filter(w => !/^_/.test(w.name) && w.name !== 'pages-build-deployment') +} + +function toString(arrayBuffer) { + return new TextDecoder().decode(arrayBuffer) +} diff --git a/scripts/commands/queue/create.js b/scripts/commands/queue/create.js index c09baf53..91019872 100644 --- a/scripts/commands/queue/create.js +++ b/scripts/commands/queue/create.js @@ -14,7 +14,6 @@ const options = program .opts() const CHANNELS_PATH = process.env.CHANNELS_PATH || 'sites/**/*.channels.xml' -const LOGS_DIR = process.env.LOGS_DIR || 'scripts/logs' async function main() { logger.info('Starting...') diff --git a/scripts/commands/readme/update.js b/scripts/commands/readme/update.js index 4ce5da4f..dc1fbd3e 100644 --- a/scripts/commands/readme/update.js +++ b/scripts/commands/readme/update.js @@ -2,7 +2,7 @@ const { file, markdown, parser, logger, api, table } = require('../../core') const { program } = require('commander') const _ = require('lodash') -const CHANNELS_PATH = process.env.CHANNELS_PATH || 'sites/**/*.channels.xml' +const LOGS_DIR = process.env.LOGS_DIR || 'scripts/logs' const options = program .option('-c, --config ', 'Set path to config file', '.readme/readme.json') @@ -10,52 +10,34 @@ const options = program .opts() async function main() { - const items = [] - await api.countries.load().catch(console.error) - const files = await file.list(CHANNELS_PATH).catch(console.error) - for (const filepath of files) { - try { - const { site, channels } = await parser.parseChannels(filepath) - const dir = file.dirname(filepath) - const config = require(file.resolve(`${dir}/${site}.config.js`)) - if (config.ignore) continue - - const filename = file.basename(filepath) - const [__, suffix] = filename.match(/\_(.*)\.channels\.xml$/) || [null, null] - const [code] = suffix.split('-') - - items.push({ - code, - site, - count: channels.length, - group: `${suffix}/${site}` - }) - } catch (err) { - console.error(err) - continue + const logPath = `${LOGS_DIR}/guides/update.log` + const results = await parser.parseLogs(logPath) + const files = results.reduce((acc, curr) => { + if (acc[curr.filename]) { + acc[curr.filename].channels++ + } else { + acc[curr.filename] = { + country: curr.country, + channels: 1, + filename: curr.filename + } } - } - await generateCountriesTable(items) - await updateReadme() -} - -main() - -async function generateCountriesTable(items = []) { - logger.info('generating countries table...') + return acc + }, {}) let data = [] - for (const item of items) { - const country = api.countries.find({ code: item.code.toUpperCase() }) + for (const filename in files) { + const item = files[filename] + const country = api.countries.find({ code: item.country }) if (!country) continue data.push([ country.name, `${country.flag} ${country.name}`, - item.count, - `https://iptv-org.github.io/epg/guides/${item.group}.epg.xml` + item.channels, + `https://iptv-org.github.io/epg/guides/${filename}.xml` ]) } @@ -73,8 +55,12 @@ async function generateCountriesTable(items = []) { ]) await file.create('./.readme/_countries.md', output) + + await updateReadme() } +main() + async function updateReadme() { logger.info('updating readme.md...') diff --git a/scripts/core/api.js b/scripts/core/api.js index 575dad99..e18e4da3 100644 --- a/scripts/core/api.js +++ b/scripts/core/api.js @@ -25,6 +25,7 @@ class API { const api = {} api.channels = new API(`${DATA_DIR}/channels.json`) +api.regions = new API(`${DATA_DIR}/regions.json`) api.countries = new API(`${DATA_DIR}/countries.json`) api.subdivisions = new API(`${DATA_DIR}/subdivisions.json`) diff --git a/scripts/core/logger.js b/scripts/core/logger.js index 2be5eda2..09423e1a 100644 --- a/scripts/core/logger.js +++ b/scripts/core/logger.js @@ -10,4 +10,10 @@ logger.config({ displayBadge: false }) +logger.memoryUsage = function () { + const used = process.memoryUsage().heapUsed / 1024 / 1024 + + logger.info(`memory: ${Math.round(used * 100) / 100} MB`) +} + module.exports = logger diff --git a/tests/__data__/expected/_readme.md b/tests/__data__/expected/_readme.md index 5afa948d..65e315aa 100644 --- a/tests/__data__/expected/_readme.md +++ b/tests/__data__/expected/_readme.md @@ -12,8 +12,9 @@ To load a program guide, all you need to do is copy the link to one or more of t Country                         ChannelsEPG - ๐Ÿ‡จ๐Ÿ‡ฆ Canada2https://iptv-org.github.io/epg/guides/ca-en/example.com.epg.xml - 1https://iptv-org.github.io/epg/guides/ca-ru/example.com.epg.xml + ๐Ÿ‡จ๐Ÿ‡ฆ Canada5https://iptv-org.github.io/epg/guides/ca_fr.xml + 2https://iptv-org.github.io/epg/guides/ca_en.xml + ๐Ÿ‡ฉ๐Ÿ‡ฐ Denmark1https://iptv-org.github.io/epg/guides/dk_da.xml diff --git a/tests/__data__/expected/api/guides.json b/tests/__data__/expected/api/guides.json index fdf08bc4..d82f7348 100644 --- a/tests/__data__/expected/api/guides.json +++ b/tests/__data__/expected/api/guides.json @@ -1 +1 @@ -[{"channel":"CNNInternationalEurope.us","site":"example.com","lang":"en","url":"https://iptv-org.github.io/epg/guides/ca-en/example.com.epg.xml.gz"},{"channel":"CNNInternationalEurope.us","site":"example.com","lang":"ru","url":"https://iptv-org.github.io/epg/guides/ca-ru/example.com.epg.xml.gz"},{"channel":"CNNInternationalEurope2.us","site":"example.com","lang":"en","url":"https://iptv-org.github.io/epg/guides/ca-en/example.com.epg.xml.gz"}] \ No newline at end of file +[{"channel":"6eren.dk","site":"allente.se","lang":"da","url":"https://iptv-org.github.io/epg/guides/dk_da.xml.gz"},{"channel":"ABCSpark.ca","site":"tvhebdo.com","lang":"fr","url":"https://iptv-org.github.io/epg/guides/ca_fr.xml.gz"},{"channel":"BBCEarthCanada.ca","site":"tvhebdo.com","lang":"fr","url":"https://iptv-org.github.io/epg/guides/ca_fr.xml.gz"},{"channel":"CFMTDT.ca","site":"tvhebdo.com","lang":"fr","url":"https://iptv-org.github.io/epg/guides/ca_fr.xml.gz"},{"channel":"CFMTDT.ca","site":"tvhebdo.com","lang":"en","url":"https://iptv-org.github.io/epg/guides/ca_en.xml.gz"},{"channel":"CanalVie.ca","site":"tvhebdo.com","lang":"fr","url":"https://iptv-org.github.io/epg/guides/ca_fr.xml.gz"},{"channel":"CanalVie.ca","site":"tvhebdo.com","lang":"en","url":"https://iptv-org.github.io/epg/guides/ca_en.xml.gz"},{"channel":"beINSportsCanada.ca","site":"tvhebdo.com","lang":"fr","url":"https://iptv-org.github.io/epg/guides/ca_fr.xml.gz"}] \ No newline at end of file diff --git a/tests/__data__/expected/guides/dk_da.json b/tests/__data__/expected/guides/dk_da.json new file mode 100644 index 00000000..37dcb8ba --- /dev/null +++ b/tests/__data__/expected/guides/dk_da.json @@ -0,0 +1 @@ +{"channels":[{"id":"6eren.dk","name":"6'eren","site":"allente.se","lang":"da","logo":"https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png","url":"https://allente.se"}],"programs":[{"site":"allente.se","channel":"6eren.dk","titles":[{"value":"Diners, Drive-Ins and Dives","lang":"da"}],"sub_titles":[],"descriptions":[{"value":"Underholdning","lang":"da"}],"icon":{"src":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/487/2022-10-24/se.cs.6eren.event.B_0254194276971024040000.jpg?size=2560x1440"},"episodeNumbers":[{"system":"xmltv_ns","value":"23.5.0/1"},{"system":"onscreen","value":"S24E06"}],"date":null,"start":1666584000000,"stop":1666585500000,"urls":[],"ratings":[],"categories":[{"value":"series","lang":"da"}],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[]}]} \ No newline at end of file diff --git a/tests/__data__/expected/guides/dk_da.xml b/tests/__data__/expected/guides/dk_da.xml new file mode 100644 index 00000000..40e099ac --- /dev/null +++ b/tests/__data__/expected/guides/dk_da.xml @@ -0,0 +1,4 @@ + +6'erenhttps://allente.se +Diners, Drive-Ins and DivesUnderholdningseries23.5.0/1S24E06 + \ No newline at end of file diff --git a/tests/__data__/expected/guides/dk_da.xml.gz b/tests/__data__/expected/guides/dk_da.xml.gz new file mode 100644 index 00000000..fe964bfa Binary files /dev/null and b/tests/__data__/expected/guides/dk_da.xml.gz differ diff --git a/tests/__data__/expected/logs/guides/update.log b/tests/__data__/expected/logs/guides/update.log index 67cb1ca0..9e39d6cc 100644 --- a/tests/__data__/expected/logs/guides/update.log +++ b/tests/__data__/expected/logs/guides/update.log @@ -1,6 +1 @@ -{"group":"us/directv.com","count":0,"status":1} -{"group":"fr/chaines-tv.orange.fr","count":1,"status":0} -{"group":"bh/chaines-tv.orange.fr","count":1,"status":0} -{"group":"ge/magticom.ge","count":0,"status":1} -{"group":"ru/yandex.ru","count":0,"status":1} -{"group":"zw/dstv.com","count":1,"status":0} +{"country":"DK","lang":"da","site":"allente.se","channel":"6eren.dk","filename":"dk_da"} diff --git a/tests/__data__/input/data/channels.json b/tests/__data__/input/data/channels.json index 6bd67500..dd19f628 100644 --- a/tests/__data__/input/data/channels.json +++ b/tests/__data__/input/data/channels.json @@ -51,5 +51,6 @@ "categories": [], "is_nsfw": false, "logo": "https://rndcdn.dstv.com/dstvcms/2020/08/31/M-Net_Movies_2_Logo_4-3_lightbackground_xlrg.png" - } + }, + {"id":"6eren.dk","name":"6'eren","alt_names":[],"network":null,"owners":["Warner Bros. Discovery EMEA"],"country":"DK","subdivision":null,"city":null,"broadcast_area":["c/DK"],"languages":["dan"],"categories":[],"is_nsfw":false,"launched":"2009-01-01","closed":null,"replaced_by":null,"website":"http://www.6-eren.dk/","logo":"https://upload.wikimedia.org/wikipedia/commons/6/64/6%27eren_2015.png"} ] \ No newline at end of file diff --git a/tests/__data__/input/data/countries.json b/tests/__data__/input/data/countries.json index 6a24850c..1b474dde 100644 --- a/tests/__data__/input/data/countries.json +++ b/tests/__data__/input/data/countries.json @@ -1,1502 +1 @@ -[ - { - "name": "Afghanistan", - "code": "AF", - "lang": "pus", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ซ" - }, - { - "name": "Albania", - "code": "AL", - "lang": "sqi", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฑ" - }, - { - "name": "Algeria", - "code": "DZ", - "lang": "ara", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ฟ" - }, - { - "name": "American Samoa", - "code": "AS", - "lang": "eng", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ธ" - }, - { - "name": "Andorra", - "code": "AD", - "lang": "cat", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฉ" - }, - { - "name": "Angola", - "code": "AO", - "lang": "por", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ด" - }, - { - "name": "Anguilla", - "code": "AI", - "lang": "eng", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฎ" - }, - { - "name": "Antarctica", - "code": "AQ", - "lang": "eng", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ถ" - }, - { - "name": "Antigua and Barbuda", - "code": "AG", - "lang": "eng", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฌ" - }, - { - "name": "Argentina", - "code": "AR", - "lang": "spa", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ท" - }, - { - "name": "Armenia", - "code": "AM", - "lang": "hye", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฒ" - }, - { - "name": "Aruba", - "code": "AW", - "lang": "nld", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ผ" - }, - { - "name": "Australia", - "code": "AU", - "lang": "eng", - "flag": "๐Ÿ‡ฆ๐Ÿ‡บ" - }, - { - "name": "Austria", - "code": "AT", - "lang": "deu", - "flag": "๐Ÿ‡ฆ๐Ÿ‡น" - }, - { - "name": "Azerbaijan", - "code": "AZ", - "lang": "aze", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฟ" - }, - { - "name": "Bahamas", - "code": "BS", - "lang": "eng", - "flag": "๐Ÿ‡ง๐Ÿ‡ธ" - }, - { - "name": "Bahrain", - "code": "BH", - "lang": "ara", - "flag": "๐Ÿ‡ง๐Ÿ‡ญ" - }, - { - "name": "Bangladesh", - "code": "BD", - "lang": "ben", - "flag": "๐Ÿ‡ง๐Ÿ‡ฉ" - }, - { - "name": "Barbados", - "code": "BB", - "lang": "eng", - "flag": "๐Ÿ‡ง๐Ÿ‡ง" - }, - { - "name": "Belarus", - "code": "BY", - "lang": "bel", - "flag": "๐Ÿ‡ง๐Ÿ‡พ" - }, - { - "name": "Belgium", - "code": "BE", - "lang": "nld", - "flag": "๐Ÿ‡ง๐Ÿ‡ช" - }, - { - "name": "Belize", - "code": "BZ", - "lang": "eng", - "flag": "๐Ÿ‡ง๐Ÿ‡ฟ" - }, - { - "name": "Benin", - "code": "BJ", - "lang": "fra", - "flag": "๐Ÿ‡ง๐Ÿ‡ฏ" - }, - { - "name": "Bermuda", - "code": "BM", - "lang": "eng", - "flag": "๐Ÿ‡ง๐Ÿ‡ฒ" - }, - { - "name": "Bhutan", - "code": "BT", - "lang": "dzo", - "flag": "๐Ÿ‡ง๐Ÿ‡น" - }, - { - "name": "Bolivia", - "code": "BO", - "lang": "spa", - "flag": "๐Ÿ‡ง๐Ÿ‡ด" - }, - { - "name": "Bonaire", - "code": "BQ", - "lang": "nld", - "flag": "๐Ÿ‡ง๐Ÿ‡ถ" - }, - { - "name": "Bosnia and Herzegovina", - "code": "BA", - "lang": "bos", - "flag": "๐Ÿ‡ง๐Ÿ‡ฆ" - }, - { - "name": "Botswana", - "code": "BW", - "lang": "eng", - "flag": "๐Ÿ‡ง๐Ÿ‡ผ" - }, - { - "name": "Bouvet Island", - "code": "BV", - "lang": "nor", - "flag": "๐Ÿ‡ง๐Ÿ‡ป" - }, - { - "name": "Brazil", - "code": "BR", - "lang": "por", - "flag": "๐Ÿ‡ง๐Ÿ‡ท" - }, - { - "name": "British Indian Ocean Territory", - "code": "IO", - "lang": "eng", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ด" - }, - { - "name": "British Virgin Islands", - "code": "VG", - "lang": "eng", - "flag": "๐Ÿ‡ป๐Ÿ‡ฌ" - }, - { - "name": "Brunei", - "code": "BN", - "lang": "msa", - "flag": "๐Ÿ‡ง๐Ÿ‡ณ" - }, - { - "name": "Bulgaria", - "code": "BG", - "lang": "bul", - "flag": "๐Ÿ‡ง๐Ÿ‡ฌ" - }, - { - "name": "Burkina Faso", - "code": "BF", - "lang": "fra", - "flag": "๐Ÿ‡ง๐Ÿ‡ซ" - }, - { - "name": "Burundi", - "code": "BI", - "lang": "fra", - "flag": "๐Ÿ‡ง๐Ÿ‡ฎ" - }, - { - "name": "Cambodia", - "code": "KH", - "lang": "khm", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ญ" - }, - { - "name": "Cameroon", - "code": "CM", - "lang": "eng", - "flag": "๐Ÿ‡จ๐Ÿ‡ฒ" - }, - { - "name": "Canada", - "code": "CA", - "lang": "eng", - "flag": "๐Ÿ‡จ๐Ÿ‡ฆ" - }, - { - "name": "Cape Verde", - "code": "CV", - "lang": "por", - "flag": "๐Ÿ‡จ๐Ÿ‡ป" - }, - { - "name": "Cayman Islands", - "code": "KY", - "lang": "eng", - "flag": "๐Ÿ‡ฐ๐Ÿ‡พ" - }, - { - "name": "Central African Republic", - "code": "CF", - "lang": "fra", - "flag": "๐Ÿ‡จ๐Ÿ‡ซ" - }, - { - "name": "Chad", - "code": "TD", - "lang": "fra", - "flag": "๐Ÿ‡น๐Ÿ‡ฉ" - }, - { - "name": "Chile", - "code": "CL", - "lang": "spa", - "flag": "๐Ÿ‡จ๐Ÿ‡ฑ" - }, - { - "name": "China", - "code": "CN", - "lang": "zho", - "flag": "๐Ÿ‡จ๐Ÿ‡ณ" - }, - { - "name": "Christmas Island", - "code": "CX", - "lang": "eng", - "flag": "๐Ÿ‡จ๐Ÿ‡ฝ" - }, - { - "name": "Cocos (Keeling) Islands", - "code": "CC", - "lang": "eng", - "flag": "๐Ÿ‡จ๐Ÿ‡จ" - }, - { - "name": "Colombia", - "code": "CO", - "lang": "spa", - "flag": "๐Ÿ‡จ๐Ÿ‡ด" - }, - { - "name": "Comoros", - "code": "KM", - "lang": "ara", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ฒ" - }, - { - "name": "Cook Islands", - "code": "CK", - "lang": "eng", - "flag": "๐Ÿ‡จ๐Ÿ‡ฐ" - }, - { - "name": "Costa Rica", - "code": "CR", - "lang": "spa", - "flag": "๐Ÿ‡จ๐Ÿ‡ท" - }, - { - "name": "Croatia", - "code": "HR", - "lang": "hrv", - "flag": "๐Ÿ‡ญ๐Ÿ‡ท" - }, - { - "name": "Cuba", - "code": "CU", - "lang": "spa", - "flag": "๐Ÿ‡จ๐Ÿ‡บ" - }, - { - "name": "Curacao", - "code": "CW", - "lang": "nld", - "flag": "๐Ÿ‡จ๐Ÿ‡ผ" - }, - { - "name": "Cyprus", - "code": "CY", - "lang": "ell", - "flag": "๐Ÿ‡จ๐Ÿ‡พ" - }, - { - "name": "Czech Republic", - "code": "CZ", - "lang": "ces", - "flag": "๐Ÿ‡จ๐Ÿ‡ฟ" - }, - { - "name": "Democratic Republic of the Congo", - "code": "CD", - "lang": "fra", - "flag": "๐Ÿ‡จ๐Ÿ‡ฉ" - }, - { - "name": "Denmark", - "code": "DK", - "lang": "dan", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ฐ" - }, - { - "name": "Djibouti", - "code": "DJ", - "lang": "fra", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ฏ" - }, - { - "name": "Dominica", - "code": "DM", - "lang": "eng", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ฒ" - }, - { - "name": "Dominican Republic", - "code": "DO", - "lang": "spa", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ด" - }, - { - "name": "East Timor", - "code": "TL", - "lang": "por", - "flag": "๐Ÿ‡น๐Ÿ‡ฑ" - }, - { - "name": "Ecuador", - "code": "EC", - "lang": "spa", - "flag": "๐Ÿ‡ช๐Ÿ‡จ" - }, - { - "name": "Egypt", - "code": "EG", - "lang": "ara", - "flag": "๐Ÿ‡ช๐Ÿ‡ฌ" - }, - { - "name": "El Salvador", - "code": "SV", - "lang": "spa", - "flag": "๐Ÿ‡ธ๐Ÿ‡ป" - }, - { - "name": "Equatorial Guinea", - "code": "GQ", - "lang": "spa", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ถ" - }, - { - "name": "Eritrea", - "code": "ER", - "lang": "tir", - "flag": "๐Ÿ‡ช๐Ÿ‡ท" - }, - { - "name": "Estonia", - "code": "EE", - "lang": "est", - "flag": "๐Ÿ‡ช๐Ÿ‡ช" - }, - { - "name": "Ethiopia", - "code": "ET", - "lang": "amh", - "flag": "๐Ÿ‡ช๐Ÿ‡น" - }, - { - "name": "Falkland Islands", - "code": "FK", - "lang": "eng", - "flag": "๐Ÿ‡ซ๐Ÿ‡ฐ" - }, - { - "name": "Faroe Islands", - "code": "FO", - "lang": "fao", - "flag": "๐Ÿ‡ซ๐Ÿ‡ด" - }, - { - "name": "Fiji", - "code": "FJ", - "lang": "eng", - "flag": "๐Ÿ‡ซ๐Ÿ‡ฏ" - }, - { - "name": "Finland", - "code": "FI", - "lang": "fin", - "flag": "๐Ÿ‡ซ๐Ÿ‡ฎ" - }, - { - "name": "France", - "code": "FR", - "lang": "fra", - "flag": "๐Ÿ‡ซ๐Ÿ‡ท" - }, - { - "name": "French Guiana", - "code": "GF", - "lang": "fra", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ซ" - }, - { - "name": "French Polynesia", - "code": "PF", - "lang": "fra", - "flag": "๐Ÿ‡ต๐Ÿ‡ซ" - }, - { - "name": "French Southern Territories", - "code": "TF", - "lang": "fra", - "flag": "๐Ÿ‡น๐Ÿ‡ซ" - }, - { - "name": "Gabon", - "code": "GA", - "lang": "fra", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฆ" - }, - { - "name": "Gambia", - "code": "GM", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฒ" - }, - { - "name": "Georgia", - "code": "GE", - "lang": "kat", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ช" - }, - { - "name": "Germany", - "code": "DE", - "lang": "deu", - "flag": "๐Ÿ‡ฉ๐Ÿ‡ช" - }, - { - "name": "Ghana", - "code": "GH", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ญ" - }, - { - "name": "Gibraltar", - "code": "GI", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฎ" - }, - { - "name": "Greece", - "code": "GR", - "lang": "ell", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ท" - }, - { - "name": "Greenland", - "code": "GL", - "lang": "kal", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฑ" - }, - { - "name": "Grenada", - "code": "GD", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฉ" - }, - { - "name": "Guadeloupe", - "code": "GP", - "lang": "fra", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ต" - }, - { - "name": "Guam", - "code": "GU", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡บ" - }, - { - "name": "Guatemala", - "code": "GT", - "lang": "spa", - "flag": "๐Ÿ‡ฌ๐Ÿ‡น" - }, - { - "name": "Guernsey", - "code": "GG", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ฌ" - }, - { - "name": "Guinea", - "code": "GN", - "lang": "fra", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ณ" - }, - { - "name": "Guinea-Bissau", - "code": "GW", - "lang": "por", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ผ" - }, - { - "name": "Guyana", - "code": "GY", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡พ" - }, - { - "name": "Haiti", - "code": "HT", - "lang": "fra", - "flag": "๐Ÿ‡ญ๐Ÿ‡น" - }, - { - "name": "Heard Island and McDonald Islands", - "code": "HM", - "lang": "eng", - "flag": "๐Ÿ‡ญ๐Ÿ‡ฒ" - }, - { - "name": "Honduras", - "code": "HN", - "lang": "spa", - "flag": "๐Ÿ‡ญ๐Ÿ‡ณ" - }, - { - "name": "Hong Kong", - "code": "HK", - "lang": "zho", - "flag": "๐Ÿ‡ญ๐Ÿ‡ฐ" - }, - { - "name": "Hungary", - "code": "HU", - "lang": "hun", - "flag": "๐Ÿ‡ญ๐Ÿ‡บ" - }, - { - "name": "Iceland", - "code": "IS", - "lang": "isl", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ธ" - }, - { - "name": "India", - "code": "IN", - "lang": "hin", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ณ" - }, - { - "name": "Indonesia", - "code": "ID", - "lang": "ind", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ฉ" - }, - { - "name": "Iran", - "code": "IR", - "lang": "fas", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ท" - }, - { - "name": "Iraq", - "code": "IQ", - "lang": "ara", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ถ" - }, - { - "name": "Ireland", - "code": "IE", - "lang": "gle", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ช" - }, - { - "name": "Isle of Man", - "code": "IM", - "lang": "eng", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ฒ" - }, - { - "name": "Israel", - "code": "IL", - "lang": "heb", - "flag": "๐Ÿ‡ฎ๐Ÿ‡ฑ" - }, - { - "name": "Italy", - "code": "IT", - "lang": "ita", - "flag": "๐Ÿ‡ฎ๐Ÿ‡น" - }, - { - "name": "Ivory Coast", - "code": "CI", - "lang": "fra", - "flag": "๐Ÿ‡จ๐Ÿ‡ฎ" - }, - { - "name": "Jamaica", - "code": "JM", - "lang": "eng", - "flag": "๐Ÿ‡ฏ๐Ÿ‡ฒ" - }, - { - "name": "Japan", - "code": "JP", - "lang": "jpn", - "flag": "๐Ÿ‡ฏ๐Ÿ‡ต" - }, - { - "name": "Jersey", - "code": "JE", - "lang": "eng", - "flag": "๐Ÿ‡ฏ๐Ÿ‡ช" - }, - { - "name": "Jordan", - "code": "JO", - "lang": "ara", - "flag": "๐Ÿ‡ฏ๐Ÿ‡ด" - }, - { - "name": "Kazakhstan", - "code": "KZ", - "lang": "kaz", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ฟ" - }, - { - "name": "Kenya", - "code": "KE", - "lang": "eng", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ช" - }, - { - "name": "Kiribati", - "code": "KI", - "lang": "eng", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ฎ" - }, - { - "name": "Kosovo", - "code": "XK", - "lang": "sqi", - "flag": "๐Ÿ‡ฝ๐Ÿ‡ฐ" - }, - { - "name": "Kuwait", - "code": "KW", - "lang": "ara", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ผ" - }, - { - "name": "Kyrgyzstan", - "code": "KG", - "lang": "kir", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ฌ" - }, - { - "name": "Laos", - "code": "LA", - "lang": "lao", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ฆ" - }, - { - "name": "Latvia", - "code": "LV", - "lang": "lav", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ป" - }, - { - "name": "Lebanon", - "code": "LB", - "lang": "ara", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ง" - }, - { - "name": "Lesotho", - "code": "LS", - "lang": "eng", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ธ" - }, - { - "name": "Liberia", - "code": "LR", - "lang": "eng", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ท" - }, - { - "name": "Libya", - "code": "LY", - "lang": "ara", - "flag": "๐Ÿ‡ฑ๐Ÿ‡พ" - }, - { - "name": "Liechtenstein", - "code": "LI", - "lang": "deu", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ฎ" - }, - { - "name": "Lithuania", - "code": "LT", - "lang": "lit", - "flag": "๐Ÿ‡ฑ๐Ÿ‡น" - }, - { - "name": "Luxembourg", - "code": "LU", - "lang": "fra", - "flag": "๐Ÿ‡ฑ๐Ÿ‡บ" - }, - { - "name": "Macao", - "code": "MO", - "lang": "zho", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ด" - }, - { - "name": "Madagascar", - "code": "MG", - "lang": "fra", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฌ" - }, - { - "name": "Malawi", - "code": "MW", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ผ" - }, - { - "name": "Malaysia", - "code": "MY", - "lang": "msa", - "flag": "๐Ÿ‡ฒ๐Ÿ‡พ" - }, - { - "name": "Maldives", - "code": "MV", - "lang": "div", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ป" - }, - { - "name": "Mali", - "code": "ML", - "lang": "fra", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฑ" - }, - { - "name": "Malta", - "code": "MT", - "lang": "mlt", - "flag": "๐Ÿ‡ฒ๐Ÿ‡น" - }, - { - "name": "Marshall Islands", - "code": "MH", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ญ" - }, - { - "name": "Martinique", - "code": "MQ", - "lang": "fra", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ถ" - }, - { - "name": "Mauritania", - "code": "MR", - "lang": "ara", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ท" - }, - { - "name": "Mauritius", - "code": "MU", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡บ" - }, - { - "name": "Mayotte", - "code": "YT", - "lang": "fra", - "flag": "๐Ÿ‡พ๐Ÿ‡น" - }, - { - "name": "Mexico", - "code": "MX", - "lang": "spa", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฝ" - }, - { - "name": "Micronesia", - "code": "FM", - "lang": "eng", - "flag": "๐Ÿ‡ซ๐Ÿ‡ฒ" - }, - { - "name": "Moldova", - "code": "MD", - "lang": "ron", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฉ" - }, - { - "name": "Monaco", - "code": "MC", - "lang": "fra", - "flag": "๐Ÿ‡ฒ๐Ÿ‡จ" - }, - { - "name": "Mongolia", - "code": "MN", - "lang": "mon", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ณ" - }, - { - "name": "Montenegro", - "code": "ME", - "lang": "srp", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ช" - }, - { - "name": "Montserrat", - "code": "MS", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ธ" - }, - { - "name": "Morocco", - "code": "MA", - "lang": "ara", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฆ" - }, - { - "name": "Mozambique", - "code": "MZ", - "lang": "por", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฟ" - }, - { - "name": "Myanmar (Burma)", - "code": "MM", - "lang": "mya", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฒ" - }, - { - "name": "Namibia", - "code": "NA", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡ฆ" - }, - { - "name": "Nauru", - "code": "NR", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡ท" - }, - { - "name": "Nepal", - "code": "NP", - "lang": "nep", - "flag": "๐Ÿ‡ณ๐Ÿ‡ต" - }, - { - "name": "Netherlands", - "code": "NL", - "lang": "nld", - "flag": "๐Ÿ‡ณ๐Ÿ‡ฑ" - }, - { - "name": "New Caledonia", - "code": "NC", - "lang": "fra", - "flag": "๐Ÿ‡ณ๐Ÿ‡จ" - }, - { - "name": "New Zealand", - "code": "NZ", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡ฟ" - }, - { - "name": "Nicaragua", - "code": "NI", - "lang": "spa", - "flag": "๐Ÿ‡ณ๐Ÿ‡ฎ" - }, - { - "name": "Niger", - "code": "NE", - "lang": "fra", - "flag": "๐Ÿ‡ณ๐Ÿ‡ช" - }, - { - "name": "Nigeria", - "code": "NG", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡ฌ" - }, - { - "name": "Niue", - "code": "NU", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡บ" - }, - { - "name": "Norfolk Island", - "code": "NF", - "lang": "eng", - "flag": "๐Ÿ‡ณ๐Ÿ‡ซ" - }, - { - "name": "North Korea", - "code": "KP", - "lang": "kor", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ต" - }, - { - "name": "North Macedonia", - "code": "MK", - "lang": "mkd", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ฐ" - }, - { - "name": "Northern Mariana Islands", - "code": "MP", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ต" - }, - { - "name": "Norway", - "code": "NO", - "lang": "nor", - "flag": "๐Ÿ‡ณ๐Ÿ‡ด" - }, - { - "name": "Oman", - "code": "OM", - "lang": "ara", - "flag": "๐Ÿ‡ด๐Ÿ‡ฒ" - }, - { - "name": "Pakistan", - "code": "PK", - "lang": "eng", - "flag": "๐Ÿ‡ต๐Ÿ‡ฐ" - }, - { - "name": "Palau", - "code": "PW", - "lang": "eng", - "flag": "๐Ÿ‡ต๐Ÿ‡ผ" - }, - { - "name": "Palestine", - "code": "PS", - "lang": "ara", - "flag": "๐Ÿ‡ต๐Ÿ‡ธ" - }, - { - "name": "Panama", - "code": "PA", - "lang": "spa", - "flag": "๐Ÿ‡ต๐Ÿ‡ฆ" - }, - { - "name": "Papua New Guinea", - "code": "PG", - "lang": "eng", - "flag": "๐Ÿ‡ต๐Ÿ‡ฌ" - }, - { - "name": "Paraguay", - "code": "PY", - "lang": "spa", - "flag": "๐Ÿ‡ต๐Ÿ‡พ" - }, - { - "name": "Peru", - "code": "PE", - "lang": "spa", - "flag": "๐Ÿ‡ต๐Ÿ‡ช" - }, - { - "name": "Philippines", - "code": "PH", - "lang": "eng", - "flag": "๐Ÿ‡ต๐Ÿ‡ญ" - }, - { - "name": "Pitcairn Islands", - "code": "PN", - "lang": "eng", - "flag": "๐Ÿ‡ต๐Ÿ‡ณ" - }, - { - "name": "Poland", - "code": "PL", - "lang": "pol", - "flag": "๐Ÿ‡ต๐Ÿ‡ฑ" - }, - { - "name": "Portugal", - "code": "PT", - "lang": "por", - "flag": "๐Ÿ‡ต๐Ÿ‡น" - }, - { - "name": "Puerto Rico", - "code": "PR", - "lang": "spa", - "flag": "๐Ÿ‡ต๐Ÿ‡ท" - }, - { - "name": "Qatar", - "code": "QA", - "lang": "ara", - "flag": "๐Ÿ‡ถ๐Ÿ‡ฆ" - }, - { - "name": "Republic of the Congo", - "code": "CG", - "lang": "fra", - "flag": "๐Ÿ‡จ๐Ÿ‡ฌ" - }, - { - "name": "Romania", - "code": "RO", - "lang": "ron", - "flag": "๐Ÿ‡ท๐Ÿ‡ด" - }, - { - "name": "Russia", - "code": "RU", - "lang": "rus", - "flag": "๐Ÿ‡ท๐Ÿ‡บ" - }, - { - "name": "Rwanda", - "code": "RW", - "lang": "kin", - "flag": "๐Ÿ‡ท๐Ÿ‡ผ" - }, - { - "name": "Rรฉunion", - "code": "RE", - "lang": "fra", - "flag": "๐Ÿ‡ท๐Ÿ‡ช" - }, - { - "name": "Saint Barthรฉlemy", - "code": "BL", - "lang": "fra", - "flag": "๐Ÿ‡ง๐Ÿ‡ฑ" - }, - { - "name": "Saint Helena", - "code": "SH", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ญ" - }, - { - "name": "Saint Kitts and Nevis", - "code": "KN", - "lang": "eng", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ณ" - }, - { - "name": "Saint Lucia", - "code": "LC", - "lang": "eng", - "flag": "๐Ÿ‡ฑ๐Ÿ‡จ" - }, - { - "name": "Saint Martin", - "code": "MF", - "lang": "eng", - "flag": "๐Ÿ‡ฒ๐Ÿ‡ซ" - }, - { - "name": "Saint Pierre and Miquelon", - "code": "PM", - "lang": "fra", - "flag": "๐Ÿ‡ต๐Ÿ‡ฒ" - }, - { - "name": "Saint Vincent and the Grenadines", - "code": "VC", - "lang": "eng", - "flag": "๐Ÿ‡ป๐Ÿ‡จ" - }, - { - "name": "Samoa", - "code": "WS", - "lang": "smo", - "flag": "๐Ÿ‡ผ๐Ÿ‡ธ" - }, - { - "name": "San Marino", - "code": "SM", - "lang": "ita", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฒ" - }, - { - "name": "Saudi Arabia", - "code": "SA", - "lang": "ara", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฆ" - }, - { - "name": "Senegal", - "code": "SN", - "lang": "fra", - "flag": "๐Ÿ‡ธ๐Ÿ‡ณ" - }, - { - "name": "Serbia", - "code": "RS", - "lang": "srp", - "flag": "๐Ÿ‡ท๐Ÿ‡ธ" - }, - { - "name": "Seychelles", - "code": "SC", - "lang": "fra", - "flag": "๐Ÿ‡ธ๐Ÿ‡จ" - }, - { - "name": "Sierra Leone", - "code": "SL", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฑ" - }, - { - "name": "Singapore", - "code": "SG", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฌ" - }, - { - "name": "Sint Maarten", - "code": "SX", - "lang": "nld", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฝ" - }, - { - "name": "Slovakia", - "code": "SK", - "lang": "slk", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฐ" - }, - { - "name": "Slovenia", - "code": "SI", - "lang": "slv", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฎ" - }, - { - "name": "Solomon Islands", - "code": "SB", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ง" - }, - { - "name": "Somalia", - "code": "SO", - "lang": "som", - "flag": "๐Ÿ‡ธ๐Ÿ‡ด" - }, - { - "name": "South Africa", - "code": "ZA", - "lang": "afr", - "flag": "๐Ÿ‡ฟ๐Ÿ‡ฆ" - }, - { - "name": "South Georgia and the South Sandwich Islands", - "code": "GS", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ธ" - }, - { - "name": "South Korea", - "code": "KR", - "lang": "kor", - "flag": "๐Ÿ‡ฐ๐Ÿ‡ท" - }, - { - "name": "South Sudan", - "code": "SS", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ธ" - }, - { - "name": "Spain", - "code": "ES", - "lang": "spa", - "flag": "๐Ÿ‡ช๐Ÿ‡ธ" - }, - { - "name": "Sri Lanka", - "code": "LK", - "lang": "sin", - "flag": "๐Ÿ‡ฑ๐Ÿ‡ฐ" - }, - { - "name": "Sudan", - "code": "SD", - "lang": "ara", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฉ" - }, - { - "name": "Suriname", - "code": "SR", - "lang": "nld", - "flag": "๐Ÿ‡ธ๐Ÿ‡ท" - }, - { - "name": "Svalbard and Jan Mayen", - "code": "SJ", - "lang": "nor", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฏ" - }, - { - "name": "Swaziland", - "code": "SZ", - "lang": "eng", - "flag": "๐Ÿ‡ธ๐Ÿ‡ฟ" - }, - { - "name": "Sweden", - "code": "SE", - "lang": "swe", - "flag": "๐Ÿ‡ธ๐Ÿ‡ช" - }, - { - "name": "Switzerland", - "code": "CH", - "lang": "deu", - "flag": "๐Ÿ‡จ๐Ÿ‡ญ" - }, - { - "name": "Syria", - "code": "SY", - "lang": "ara", - "flag": "๐Ÿ‡ธ๐Ÿ‡พ" - }, - { - "name": "Sรฃo Tomรฉ and Prรญncipe", - "code": "ST", - "lang": "por", - "flag": "๐Ÿ‡ธ๐Ÿ‡น" - }, - { - "name": "Taiwan", - "code": "TW", - "lang": "zho", - "flag": "๐Ÿ‡น๐Ÿ‡ผ" - }, - { - "name": "Tajikistan", - "code": "TJ", - "lang": "tgk", - "flag": "๐Ÿ‡น๐Ÿ‡ฏ" - }, - { - "name": "Tanzania", - "code": "TZ", - "lang": "swa", - "flag": "๐Ÿ‡น๐Ÿ‡ฟ" - }, - { - "name": "Thailand", - "code": "TH", - "lang": "tha", - "flag": "๐Ÿ‡น๐Ÿ‡ญ" - }, - { - "name": "Togo", - "code": "TG", - "lang": "fra", - "flag": "๐Ÿ‡น๐Ÿ‡ฌ" - }, - { - "name": "Tokelau", - "code": "TK", - "lang": "eng", - "flag": "๐Ÿ‡น๐Ÿ‡ฐ" - }, - { - "name": "Tonga", - "code": "TO", - "lang": "eng", - "flag": "๐Ÿ‡น๐Ÿ‡ด" - }, - { - "name": "Trinidad and Tobago", - "code": "TT", - "lang": "eng", - "flag": "๐Ÿ‡น๐Ÿ‡น" - }, - { - "name": "Tunisia", - "code": "TN", - "lang": "ara", - "flag": "๐Ÿ‡น๐Ÿ‡ณ" - }, - { - "name": "Turkey", - "code": "TR", - "lang": "tur", - "flag": "๐Ÿ‡น๐Ÿ‡ท" - }, - { - "name": "Turkmenistan", - "code": "TM", - "lang": "tuk", - "flag": "๐Ÿ‡น๐Ÿ‡ฒ" - }, - { - "name": "Turks and Caicos Islands", - "code": "TC", - "lang": "eng", - "flag": "๐Ÿ‡น๐Ÿ‡จ" - }, - { - "name": "Tuvalu", - "code": "TV", - "lang": "eng", - "flag": "๐Ÿ‡น๐Ÿ‡ป" - }, - { - "name": "U.S. Minor Outlying Islands", - "code": "UM", - "lang": "eng", - "flag": "๐Ÿ‡บ๐Ÿ‡ฒ" - }, - { - "name": "U.S. Virgin Islands", - "code": "VI", - "lang": "eng", - "flag": "๐Ÿ‡ป๐Ÿ‡ฎ" - }, - { - "name": "Uganda", - "code": "UG", - "lang": "eng", - "flag": "๐Ÿ‡บ๐Ÿ‡ฌ" - }, - { - "name": "Ukraine", - "code": "UA", - "lang": "ukr", - "flag": "๐Ÿ‡บ๐Ÿ‡ฆ" - }, - { - "name": "United Arab Emirates", - "code": "AE", - "lang": "ara", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ช" - }, - { - "name": "United Kingdom", - "code": "UK", - "lang": "eng", - "flag": "๐Ÿ‡ฌ๐Ÿ‡ง" - }, - { - "name": "United States", - "code": "US", - "lang": "eng", - "flag": "๐Ÿ‡บ๐Ÿ‡ธ" - }, - { - "name": "Uruguay", - "code": "UY", - "lang": "spa", - "flag": "๐Ÿ‡บ๐Ÿ‡พ" - }, - { - "name": "Uzbekistan", - "code": "UZ", - "lang": "uzb", - "flag": "๐Ÿ‡บ๐Ÿ‡ฟ" - }, - { - "name": "Vanuatu", - "code": "VU", - "lang": "bis", - "flag": "๐Ÿ‡ป๐Ÿ‡บ" - }, - { - "name": "Vatican City", - "code": "VA", - "lang": "ita", - "flag": "๐Ÿ‡ป๐Ÿ‡ฆ" - }, - { - "name": "Venezuela", - "code": "VE", - "lang": "spa", - "flag": "๐Ÿ‡ป๐Ÿ‡ช" - }, - { - "name": "Vietnam", - "code": "VN", - "lang": "vie", - "flag": "๐Ÿ‡ป๐Ÿ‡ณ" - }, - { - "name": "Wallis and Futuna", - "code": "WF", - "lang": "fra", - "flag": "๐Ÿ‡ผ๐Ÿ‡ซ" - }, - { - "name": "Western Sahara", - "code": "EH", - "lang": "spa", - "flag": "๐Ÿ‡ช๐Ÿ‡ญ" - }, - { - "name": "Yemen", - "code": "YE", - "lang": "ara", - "flag": "๐Ÿ‡พ๐Ÿ‡ช" - }, - { - "name": "Zambia", - "code": "ZM", - "lang": "eng", - "flag": "๐Ÿ‡ฟ๐Ÿ‡ฒ" - }, - { - "name": "Zimbabwe", - "code": "ZW", - "lang": "eng", - "flag": "๐Ÿ‡ฟ๐Ÿ‡ผ" - }, - { - "name": "ร…land", - "code": "AX", - "lang": "swe", - "flag": "๐Ÿ‡ฆ๐Ÿ‡ฝ" - } -] \ No newline at end of file +[{"name":"Afghanistan","code":"AF","languages":["prs","pus","tuk"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ซ"},{"name":"Albania","code":"AL","languages":["sqi"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฑ"},{"name":"Algeria","code":"DZ","languages":["ara"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ฟ"},{"name":"American Samoa","code":"AS","languages":["eng","smo"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ธ"},{"name":"Andorra","code":"AD","languages":["cat"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฉ"},{"name":"Angola","code":"AO","languages":["por"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ด"},{"name":"Anguilla","code":"AI","languages":["eng"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฎ"},{"name":"Antarctica","code":"AQ","languages":["eng"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ถ"},{"name":"Antigua and Barbuda","code":"AG","languages":["eng"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฌ"},{"name":"Argentina","code":"AR","languages":["grn","spa"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ท"},{"name":"Armenia","code":"AM","languages":["hye"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฒ"},{"name":"Aruba","code":"AW","languages":["nld","pap"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ผ"},{"name":"Australia","code":"AU","languages":["eng"],"flag":"๐Ÿ‡ฆ๐Ÿ‡บ"},{"name":"Austria","code":"AT","languages":["bar"],"flag":"๐Ÿ‡ฆ๐Ÿ‡น"},{"name":"Azerbaijan","code":"AZ","languages":["aze","rus"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฟ"},{"name":"Bahamas","code":"BS","languages":["eng"],"flag":"๐Ÿ‡ง๐Ÿ‡ธ"},{"name":"Bahrain","code":"BH","languages":["ara"],"flag":"๐Ÿ‡ง๐Ÿ‡ญ"},{"name":"Bangladesh","code":"BD","languages":["ben"],"flag":"๐Ÿ‡ง๐Ÿ‡ฉ"},{"name":"Barbados","code":"BB","languages":["eng"],"flag":"๐Ÿ‡ง๐Ÿ‡ง"},{"name":"Belarus","code":"BY","languages":["bel","rus"],"flag":"๐Ÿ‡ง๐Ÿ‡พ"},{"name":"Belgium","code":"BE","languages":["deu","fra","nld"],"flag":"๐Ÿ‡ง๐Ÿ‡ช"},{"name":"Belize","code":"BZ","languages":["bjz","eng","spa"],"flag":"๐Ÿ‡ง๐Ÿ‡ฟ"},{"name":"Benin","code":"BJ","languages":["fra"],"flag":"๐Ÿ‡ง๐Ÿ‡ฏ"},{"name":"Bermuda","code":"BM","languages":["eng"],"flag":"๐Ÿ‡ง๐Ÿ‡ฒ"},{"name":"Bhutan","code":"BT","languages":["dzo"],"flag":"๐Ÿ‡ง๐Ÿ‡น"},{"name":"Bolivia","code":"BO","languages":["aym","grn","que","spa"],"flag":"๐Ÿ‡ง๐Ÿ‡ด"},{"name":"Bonaire","code":"BQ","languages":["eng","nld","pap"],"flag":"๐Ÿ‡ง๐Ÿ‡ถ"},{"name":"Bosnia and Herzegovina","code":"BA","languages":["bos","hrv","srp"],"flag":"๐Ÿ‡ง๐Ÿ‡ฆ"},{"name":"Botswana","code":"BW","languages":["eng","tsn"],"flag":"๐Ÿ‡ง๐Ÿ‡ผ"},{"name":"Bouvet Island","code":"BV","languages":["nor"],"flag":"๐Ÿ‡ง๐Ÿ‡ป"},{"name":"Brazil","code":"BR","languages":["por"],"flag":"๐Ÿ‡ง๐Ÿ‡ท"},{"name":"British Indian Ocean Territory","code":"IO","languages":["eng"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ด"},{"name":"British Virgin Islands","code":"VG","languages":["eng"],"flag":"๐Ÿ‡ป๐Ÿ‡ฌ"},{"name":"Brunei","code":"BN","languages":["msa"],"flag":"๐Ÿ‡ง๐Ÿ‡ณ"},{"name":"Bulgaria","code":"BG","languages":["bul"],"flag":"๐Ÿ‡ง๐Ÿ‡ฌ"},{"name":"Burkina Faso","code":"BF","languages":["fra"],"flag":"๐Ÿ‡ง๐Ÿ‡ซ"},{"name":"Burundi","code":"BI","languages":["fra","run"],"flag":"๐Ÿ‡ง๐Ÿ‡ฎ"},{"name":"Cambodia","code":"KH","languages":["khm"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ญ"},{"name":"Cameroon","code":"CM","languages":["eng","fra"],"flag":"๐Ÿ‡จ๐Ÿ‡ฒ"},{"name":"Canada","code":"CA","languages":["eng","fra"],"flag":"๐Ÿ‡จ๐Ÿ‡ฆ"},{"name":"Cape Verde","code":"CV","languages":["por"],"flag":"๐Ÿ‡จ๐Ÿ‡ป"},{"name":"Cayman Islands","code":"KY","languages":["eng"],"flag":"๐Ÿ‡ฐ๐Ÿ‡พ"},{"name":"Central African Republic","code":"CF","languages":["fra","sag"],"flag":"๐Ÿ‡จ๐Ÿ‡ซ"},{"name":"Chad","code":"TD","languages":["ara","fra"],"flag":"๐Ÿ‡น๐Ÿ‡ฉ"},{"name":"Chile","code":"CL","languages":["spa"],"flag":"๐Ÿ‡จ๐Ÿ‡ฑ"},{"name":"China","code":"CN","languages":["zho"],"flag":"๐Ÿ‡จ๐Ÿ‡ณ"},{"name":"Christmas Island","code":"CX","languages":["eng"],"flag":"๐Ÿ‡จ๐Ÿ‡ฝ"},{"name":"Cocos (Keeling) Islands","code":"CC","languages":["eng"],"flag":"๐Ÿ‡จ๐Ÿ‡จ"},{"name":"Colombia","code":"CO","languages":["spa"],"flag":"๐Ÿ‡จ๐Ÿ‡ด"},{"name":"Comoros","code":"KM","languages":["ara","fra","zdj"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ฒ"},{"name":"Cook Islands","code":"CK","languages":["eng","rar"],"flag":"๐Ÿ‡จ๐Ÿ‡ฐ"},{"name":"Costa Rica","code":"CR","languages":["spa"],"flag":"๐Ÿ‡จ๐Ÿ‡ท"},{"name":"Croatia","code":"HR","languages":["hrv"],"flag":"๐Ÿ‡ญ๐Ÿ‡ท"},{"name":"Cuba","code":"CU","languages":["spa"],"flag":"๐Ÿ‡จ๐Ÿ‡บ"},{"name":"Curacao","code":"CW","languages":["eng","nld","pap"],"flag":"๐Ÿ‡จ๐Ÿ‡ผ"},{"name":"Cyprus","code":"CY","languages":["ell","tur"],"flag":"๐Ÿ‡จ๐Ÿ‡พ"},{"name":"Czech Republic","code":"CZ","languages":["ces","slk"],"flag":"๐Ÿ‡จ๐Ÿ‡ฟ"},{"name":"Democratic Republic of the Congo","code":"CD","languages":["fra","kon","lin","lua","swa"],"flag":"๐Ÿ‡จ๐Ÿ‡ฉ"},{"name":"Denmark","code":"DK","languages":["dan"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ฐ"},{"name":"Djibouti","code":"DJ","languages":["ara","fra"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ฏ"},{"name":"Dominica","code":"DM","languages":["eng"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ฒ"},{"name":"Dominican Republic","code":"DO","languages":["spa"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ด"},{"name":"East Timor","code":"TL","languages":["por","tet"],"flag":"๐Ÿ‡น๐Ÿ‡ฑ"},{"name":"Ecuador","code":"EC","languages":["spa"],"flag":"๐Ÿ‡ช๐Ÿ‡จ"},{"name":"Egypt","code":"EG","languages":["ara"],"flag":"๐Ÿ‡ช๐Ÿ‡ฌ"},{"name":"El Salvador","code":"SV","languages":["spa"],"flag":"๐Ÿ‡ธ๐Ÿ‡ป"},{"name":"Equatorial Guinea","code":"GQ","languages":["fra","por","spa"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ถ"},{"name":"Eritrea","code":"ER","languages":["ara","eng","tir"],"flag":"๐Ÿ‡ช๐Ÿ‡ท"},{"name":"Estonia","code":"EE","languages":["est"],"flag":"๐Ÿ‡ช๐Ÿ‡ช"},{"name":"Ethiopia","code":"ET","languages":["amh"],"flag":"๐Ÿ‡ช๐Ÿ‡น"},{"name":"Falkland Islands","code":"FK","languages":["eng"],"flag":"๐Ÿ‡ซ๐Ÿ‡ฐ"},{"name":"Faroe Islands","code":"FO","languages":["dan","fao"],"flag":"๐Ÿ‡ซ๐Ÿ‡ด"},{"name":"Fiji","code":"FJ","languages":["eng","fij","hif"],"flag":"๐Ÿ‡ซ๐Ÿ‡ฏ"},{"name":"Finland","code":"FI","languages":["fin","swe"],"flag":"๐Ÿ‡ซ๐Ÿ‡ฎ"},{"name":"France","code":"FR","languages":["fra"],"flag":"๐Ÿ‡ซ๐Ÿ‡ท"},{"name":"French Guiana","code":"GF","languages":["fra"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ซ"},{"name":"French Polynesia","code":"PF","languages":["fra"],"flag":"๐Ÿ‡ต๐Ÿ‡ซ"},{"name":"French Southern Territories","code":"TF","languages":["fra"],"flag":"๐Ÿ‡น๐Ÿ‡ซ"},{"name":"Gabon","code":"GA","languages":["fra"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฆ"},{"name":"Gambia","code":"GM","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฒ"},{"name":"Georgia","code":"GE","languages":["kat"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ช"},{"name":"Germany","code":"DE","languages":["deu"],"flag":"๐Ÿ‡ฉ๐Ÿ‡ช"},{"name":"Ghana","code":"GH","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ญ"},{"name":"Gibraltar","code":"GI","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฎ"},{"name":"Greece","code":"GR","languages":["ell"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ท"},{"name":"Greenland","code":"GL","languages":["kal"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฑ"},{"name":"Grenada","code":"GD","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฉ"},{"name":"Guadeloupe","code":"GP","languages":["fra"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ต"},{"name":"Guam","code":"GU","languages":["cha","eng","spa"],"flag":"๐Ÿ‡ฌ๐Ÿ‡บ"},{"name":"Guatemala","code":"GT","languages":["spa"],"flag":"๐Ÿ‡ฌ๐Ÿ‡น"},{"name":"Guernsey","code":"GG","languages":["eng","fra","nfr"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ฌ"},{"name":"Guinea","code":"GN","languages":["fra"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ณ"},{"name":"Guinea-Bissau","code":"GW","languages":["por","pov"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ผ"},{"name":"Guyana","code":"GY","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡พ"},{"name":"Haiti","code":"HT","languages":["fra","hat"],"flag":"๐Ÿ‡ญ๐Ÿ‡น"},{"name":"Heard Island and McDonald Islands","code":"HM","languages":["eng"],"flag":"๐Ÿ‡ญ๐Ÿ‡ฒ"},{"name":"Honduras","code":"HN","languages":["spa"],"flag":"๐Ÿ‡ญ๐Ÿ‡ณ"},{"name":"Hong Kong","code":"HK","languages":["eng","zho"],"flag":"๐Ÿ‡ญ๐Ÿ‡ฐ"},{"name":"Hungary","code":"HU","languages":["hun"],"flag":"๐Ÿ‡ญ๐Ÿ‡บ"},{"name":"Iceland","code":"IS","languages":["isl"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ธ"},{"name":"India","code":"IN","languages":["eng","hin","tam"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ณ"},{"name":"Indonesia","code":"ID","languages":["ind"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ฉ"},{"name":"Iran","code":"IR","languages":["fas"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ท"},{"name":"Iraq","code":"IQ","languages":["ara","arc","ckb"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ถ"},{"name":"Ireland","code":"IE","languages":["eng","gle"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ช"},{"name":"Isle of Man","code":"IM","languages":["eng","glv"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ฒ"},{"name":"Israel","code":"IL","languages":["ara","heb"],"flag":"๐Ÿ‡ฎ๐Ÿ‡ฑ"},{"name":"Italy","code":"IT","languages":["ita"],"flag":"๐Ÿ‡ฎ๐Ÿ‡น"},{"name":"Ivory Coast","code":"CI","languages":["fra"],"flag":"๐Ÿ‡จ๐Ÿ‡ฎ"},{"name":"Jamaica","code":"JM","languages":["eng","jam"],"flag":"๐Ÿ‡ฏ๐Ÿ‡ฒ"},{"name":"Japan","code":"JP","languages":["jpn"],"flag":"๐Ÿ‡ฏ๐Ÿ‡ต"},{"name":"Jersey","code":"JE","languages":["eng","fra","nrf"],"flag":"๐Ÿ‡ฏ๐Ÿ‡ช"},{"name":"Jordan","code":"JO","languages":["ara"],"flag":"๐Ÿ‡ฏ๐Ÿ‡ด"},{"name":"Kazakhstan","code":"KZ","languages":["kaz","rus"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ฟ"},{"name":"Kenya","code":"KE","languages":["eng","swa"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ช"},{"name":"Kiribati","code":"KI","languages":["eng","gil"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ฎ"},{"name":"Kosovo","code":"XK","languages":["sqi","srp"],"flag":"๐Ÿ‡ฝ๐Ÿ‡ฐ"},{"name":"Kuwait","code":"KW","languages":["ara"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ผ"},{"name":"Kyrgyzstan","code":"KG","languages":["kir","rus"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ฌ"},{"name":"Laos","code":"LA","languages":["lao"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ฆ"},{"name":"Latvia","code":"LV","languages":["lav"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ป"},{"name":"Lebanon","code":"LB","languages":["ara","fra"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ง"},{"name":"Lesotho","code":"LS","languages":["eng","sot"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ธ"},{"name":"Liberia","code":"LR","languages":["eng"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ท"},{"name":"Libya","code":"LY","languages":["ara"],"flag":"๐Ÿ‡ฑ๐Ÿ‡พ"},{"name":"Liechtenstein","code":"LI","languages":["deu"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ฎ"},{"name":"Lithuania","code":"LT","languages":["lit"],"flag":"๐Ÿ‡ฑ๐Ÿ‡น"},{"name":"Luxembourg","code":"LU","languages":["deu","fra","ltz"],"flag":"๐Ÿ‡ฑ๐Ÿ‡บ"},{"name":"Macao","code":"MO","languages":["por","zho"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ด"},{"name":"Madagascar","code":"MG","languages":["fra","mlg"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฌ"},{"name":"Malawi","code":"MW","languages":["eng","nya"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ผ"},{"name":"Malaysia","code":"MY","languages":["eng","msa"],"flag":"๐Ÿ‡ฒ๐Ÿ‡พ"},{"name":"Maldives","code":"MV","languages":["div"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ป"},{"name":"Mali","code":"ML","languages":["fra"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฑ"},{"name":"Malta","code":"MT","languages":["eng","mlt"],"flag":"๐Ÿ‡ฒ๐Ÿ‡น"},{"name":"Marshall Islands","code":"MH","languages":["eng","mah"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ญ"},{"name":"Martinique","code":"MQ","languages":["fra"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ถ"},{"name":"Mauritania","code":"MR","languages":["ara"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ท"},{"name":"Mauritius","code":"MU","languages":["eng","fra","mfe"],"flag":"๐Ÿ‡ฒ๐Ÿ‡บ"},{"name":"Mayotte","code":"YT","languages":["fra"],"flag":"๐Ÿ‡พ๐Ÿ‡น"},{"name":"Mexico","code":"MX","languages":["spa"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฝ"},{"name":"Micronesia","code":"FM","languages":["eng"],"flag":"๐Ÿ‡ซ๐Ÿ‡ฒ"},{"name":"Moldova","code":"MD","languages":["ron"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฉ"},{"name":"Monaco","code":"MC","languages":["fra"],"flag":"๐Ÿ‡ฒ๐Ÿ‡จ"},{"name":"Mongolia","code":"MN","languages":["mon"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ณ"},{"name":"Montenegro","code":"ME","languages":["cnr"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ช"},{"name":"Montserrat","code":"MS","languages":["eng"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ธ"},{"name":"Morocco","code":"MA","languages":["ara","zgh"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฆ"},{"name":"Mozambique","code":"MZ","languages":["por"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฟ"},{"name":"Myanmar (Burma)","code":"MM","languages":["mya"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฒ"},{"name":"Namibia","code":"NA","languages":["afr","deu","eng","her","hgm","kwn","loz","ndo","tsn"],"flag":"๐Ÿ‡ณ๐Ÿ‡ฆ"},{"name":"Nauru","code":"NR","languages":["eng","nau"],"flag":"๐Ÿ‡ณ๐Ÿ‡ท"},{"name":"Nepal","code":"NP","languages":["nep"],"flag":"๐Ÿ‡ณ๐Ÿ‡ต"},{"name":"Netherlands","code":"NL","languages":["nld"],"flag":"๐Ÿ‡ณ๐Ÿ‡ฑ"},{"name":"New Caledonia","code":"NC","languages":["fra"],"flag":"๐Ÿ‡ณ๐Ÿ‡จ"},{"name":"New Zealand","code":"NZ","languages":["eng","mri","nzs"],"flag":"๐Ÿ‡ณ๐Ÿ‡ฟ"},{"name":"Nicaragua","code":"NI","languages":["spa"],"flag":"๐Ÿ‡ณ๐Ÿ‡ฎ"},{"name":"Niger","code":"NE","languages":["fra"],"flag":"๐Ÿ‡ณ๐Ÿ‡ช"},{"name":"Nigeria","code":"NG","languages":["eng"],"flag":"๐Ÿ‡ณ๐Ÿ‡ฌ"},{"name":"Niue","code":"NU","languages":["eng","niu"],"flag":"๐Ÿ‡ณ๐Ÿ‡บ"},{"name":"Norfolk Island","code":"NF","languages":["eng","pih"],"flag":"๐Ÿ‡ณ๐Ÿ‡ซ"},{"name":"North Korea","code":"KP","languages":["kor"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ต"},{"name":"North Macedonia","code":"MK","languages":["mkd"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ฐ"},{"name":"Northern Mariana Islands","code":"MP","languages":["cal","cha","eng"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ต"},{"name":"Norway","code":"NO","languages":["nno","nob","sme","smj","sma"],"flag":"๐Ÿ‡ณ๐Ÿ‡ด"},{"name":"Oman","code":"OM","languages":["ara"],"flag":"๐Ÿ‡ด๐Ÿ‡ฒ"},{"name":"Pakistan","code":"PK","languages":["eng","urd"],"flag":"๐Ÿ‡ต๐Ÿ‡ฐ"},{"name":"Palau","code":"PW","languages":["eng","pau"],"flag":"๐Ÿ‡ต๐Ÿ‡ผ"},{"name":"Palestine","code":"PS","languages":["ara"],"flag":"๐Ÿ‡ต๐Ÿ‡ธ"},{"name":"Panama","code":"PA","languages":["spa"],"flag":"๐Ÿ‡ต๐Ÿ‡ฆ"},{"name":"Papua New Guinea","code":"PG","languages":["eng","hmo","tpi"],"flag":"๐Ÿ‡ต๐Ÿ‡ฌ"},{"name":"Paraguay","code":"PY","languages":["grn","spa"],"flag":"๐Ÿ‡ต๐Ÿ‡พ"},{"name":"Peru","code":"PE","languages":["aym","que","spa"],"flag":"๐Ÿ‡ต๐Ÿ‡ช"},{"name":"Philippines","code":"PH","languages":["eng","fil"],"flag":"๐Ÿ‡ต๐Ÿ‡ญ"},{"name":"Pitcairn Islands","code":"PN","languages":["eng"],"flag":"๐Ÿ‡ต๐Ÿ‡ณ"},{"name":"Poland","code":"PL","languages":["pol"],"flag":"๐Ÿ‡ต๐Ÿ‡ฑ"},{"name":"Portugal","code":"PT","languages":["por"],"flag":"๐Ÿ‡ต๐Ÿ‡น"},{"name":"Puerto Rico","code":"PR","languages":["eng","spa"],"flag":"๐Ÿ‡ต๐Ÿ‡ท"},{"name":"Qatar","code":"QA","languages":["ara"],"flag":"๐Ÿ‡ถ๐Ÿ‡ฆ"},{"name":"Republic of the Congo","code":"CG","languages":["fra","kon","lin"],"flag":"๐Ÿ‡จ๐Ÿ‡ฌ"},{"name":"Romania","code":"RO","languages":["ron"],"flag":"๐Ÿ‡ท๐Ÿ‡ด"},{"name":"Russia","code":"RU","languages":["rus"],"flag":"๐Ÿ‡ท๐Ÿ‡บ"},{"name":"Rwanda","code":"RW","languages":["eng","fra","kin"],"flag":"๐Ÿ‡ท๐Ÿ‡ผ"},{"name":"Rรฉunion","code":"RE","languages":["fra"],"flag":"๐Ÿ‡ท๐Ÿ‡ช"},{"name":"Saint Barthรฉlemy","code":"BL","languages":["fra"],"flag":"๐Ÿ‡ง๐Ÿ‡ฑ"},{"name":"Saint Helena","code":"SH","languages":["eng"],"flag":"๐Ÿ‡ธ๐Ÿ‡ญ"},{"name":"Saint Kitts and Nevis","code":"KN","languages":["eng"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ณ"},{"name":"Saint Lucia","code":"LC","languages":["eng"],"flag":"๐Ÿ‡ฑ๐Ÿ‡จ"},{"name":"Saint Martin","code":"MF","languages":["fra"],"flag":"๐Ÿ‡ฒ๐Ÿ‡ซ"},{"name":"Saint Pierre and Miquelon","code":"PM","languages":["fra"],"flag":"๐Ÿ‡ต๐Ÿ‡ฒ"},{"name":"Saint Vincent and the Grenadines","code":"VC","languages":["eng"],"flag":"๐Ÿ‡ป๐Ÿ‡จ"},{"name":"Samoa","code":"WS","languages":["eng","smo"],"flag":"๐Ÿ‡ผ๐Ÿ‡ธ"},{"name":"San Marino","code":"SM","languages":["ita"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฒ"},{"name":"Saudi Arabia","code":"SA","languages":["ara"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฆ"},{"name":"Senegal","code":"SN","languages":["fra"],"flag":"๐Ÿ‡ธ๐Ÿ‡ณ"},{"name":"Serbia","code":"RS","languages":["srp"],"flag":"๐Ÿ‡ท๐Ÿ‡ธ"},{"name":"Seychelles","code":"SC","languages":["crs","eng","fra"],"flag":"๐Ÿ‡ธ๐Ÿ‡จ"},{"name":"Sierra Leone","code":"SL","languages":["eng"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฑ"},{"name":"Singapore","code":"SG","languages":["zho","eng","msa","tam"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฌ"},{"name":"Sint Maarten","code":"SX","languages":["eng","fra","nld"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฝ"},{"name":"Slovakia","code":"SK","languages":["slk"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฐ"},{"name":"Slovenia","code":"SI","languages":["slv"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฎ"},{"name":"Solomon Islands","code":"SB","languages":["eng"],"flag":"๐Ÿ‡ธ๐Ÿ‡ง"},{"name":"Somalia","code":"SO","languages":["ara","som"],"flag":"๐Ÿ‡ธ๐Ÿ‡ด"},{"name":"South Africa","code":"ZA","languages":["afr","eng","nbl","nso","sot","ssw","tsn","tso","ven","xho","zul"],"flag":"๐Ÿ‡ฟ๐Ÿ‡ฆ"},{"name":"South Georgia and the South Sandwich Islands","code":"GS","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ธ"},{"name":"South Korea","code":"KR","languages":["kor"],"flag":"๐Ÿ‡ฐ๐Ÿ‡ท"},{"name":"South Sudan","code":"SS","languages":["eng"],"flag":"๐Ÿ‡ธ๐Ÿ‡ธ"},{"name":"Spain","code":"ES","languages":["spa"],"flag":"๐Ÿ‡ช๐Ÿ‡ธ"},{"name":"Sri Lanka","code":"LK","languages":["sin","tam"],"flag":"๐Ÿ‡ฑ๐Ÿ‡ฐ"},{"name":"Sudan","code":"SD","languages":["ara","eng"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฉ"},{"name":"Suriname","code":"SR","languages":["nld"],"flag":"๐Ÿ‡ธ๐Ÿ‡ท"},{"name":"Svalbard and Jan Mayen","code":"SJ","languages":["nor"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฏ"},{"name":"Swaziland","code":"SZ","languages":["eng","ssw"],"flag":"๐Ÿ‡ธ๐Ÿ‡ฟ"},{"name":"Sweden","code":"SE","languages":["swe"],"flag":"๐Ÿ‡ธ๐Ÿ‡ช"},{"name":"Switzerland","code":"CH","languages":["fra","gsw","ita","roh"],"flag":"๐Ÿ‡จ๐Ÿ‡ญ"},{"name":"Syria","code":"SY","languages":["ara"],"flag":"๐Ÿ‡ธ๐Ÿ‡พ"},{"name":"Sรฃo Tomรฉ and Prรญncipe","code":"ST","languages":["por"],"flag":"๐Ÿ‡ธ๐Ÿ‡น"},{"name":"Taiwan","code":"TW","languages":["zho"],"flag":"๐Ÿ‡น๐Ÿ‡ผ"},{"name":"Tajikistan","code":"TJ","languages":["rus","tgk"],"flag":"๐Ÿ‡น๐Ÿ‡ฏ"},{"name":"Tanzania","code":"TZ","languages":["eng","swa"],"flag":"๐Ÿ‡น๐Ÿ‡ฟ"},{"name":"Thailand","code":"TH","languages":["tha"],"flag":"๐Ÿ‡น๐Ÿ‡ญ"},{"name":"Togo","code":"TG","languages":["fra"],"flag":"๐Ÿ‡น๐Ÿ‡ฌ"},{"name":"Tokelau","code":"TK","languages":["eng","smo","tkl"],"flag":"๐Ÿ‡น๐Ÿ‡ฐ"},{"name":"Tonga","code":"TO","languages":["eng","ton"],"flag":"๐Ÿ‡น๐Ÿ‡ด"},{"name":"Trinidad and Tobago","code":"TT","languages":["eng"],"flag":"๐Ÿ‡น๐Ÿ‡น"},{"name":"Tunisia","code":"TN","languages":["ara"],"flag":"๐Ÿ‡น๐Ÿ‡ณ"},{"name":"Turkey","code":"TR","languages":["tur"],"flag":"๐Ÿ‡น๐Ÿ‡ท"},{"name":"Turkmenistan","code":"TM","languages":["rus","tuk"],"flag":"๐Ÿ‡น๐Ÿ‡ฒ"},{"name":"Turks and Caicos Islands","code":"TC","languages":["eng"],"flag":"๐Ÿ‡น๐Ÿ‡จ"},{"name":"Tuvalu","code":"TV","languages":["eng","tvl"],"flag":"๐Ÿ‡น๐Ÿ‡ป"},{"name":"U.S. Minor Outlying Islands","code":"UM","languages":["eng"],"flag":"๐Ÿ‡บ๐Ÿ‡ฒ"},{"name":"U.S. Virgin Islands","code":"VI","languages":["eng"],"flag":"๐Ÿ‡ป๐Ÿ‡ฎ"},{"name":"Uganda","code":"UG","languages":["eng","swa"],"flag":"๐Ÿ‡บ๐Ÿ‡ฌ"},{"name":"Ukraine","code":"UA","languages":["ukr"],"flag":"๐Ÿ‡บ๐Ÿ‡ฆ"},{"name":"United Arab Emirates","code":"AE","languages":["ara"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ช"},{"name":"United Kingdom","code":"UK","languages":["eng"],"flag":"๐Ÿ‡ฌ๐Ÿ‡ง"},{"name":"United States","code":"US","languages":["eng"],"flag":"๐Ÿ‡บ๐Ÿ‡ธ"},{"name":"Uruguay","code":"UY","languages":["spa"],"flag":"๐Ÿ‡บ๐Ÿ‡พ"},{"name":"Uzbekistan","code":"UZ","languages":["rus","uzb"],"flag":"๐Ÿ‡บ๐Ÿ‡ฟ"},{"name":"Vanuatu","code":"VU","languages":["bis","eng","fra"],"flag":"๐Ÿ‡ป๐Ÿ‡บ"},{"name":"Vatican City","code":"VA","languages":["ita","lat"],"flag":"๐Ÿ‡ป๐Ÿ‡ฆ"},{"name":"Venezuela","code":"VE","languages":["spa"],"flag":"๐Ÿ‡ป๐Ÿ‡ช"},{"name":"Vietnam","code":"VN","languages":["vie"],"flag":"๐Ÿ‡ป๐Ÿ‡ณ"},{"name":"Wallis and Futuna","code":"WF","languages":["fra"],"flag":"๐Ÿ‡ผ๐Ÿ‡ซ"},{"name":"Western Sahara","code":"EH","languages":["zgh","mey","spa"],"flag":"๐Ÿ‡ช๐Ÿ‡ญ"},{"name":"Yemen","code":"YE","languages":["ara"],"flag":"๐Ÿ‡พ๐Ÿ‡ช"},{"name":"Zambia","code":"ZM","languages":["eng"],"flag":"๐Ÿ‡ฟ๐Ÿ‡ฒ"},{"name":"Zimbabwe","code":"ZW","languages":["bwg","eng","kck","hio","ndc","nde","nya","sna","sot","toi","tsn","tso","ven","xho","zib"],"flag":"๐Ÿ‡ฟ๐Ÿ‡ผ"},{"name":"ร…land","code":"AX","languages":["swe"],"flag":"๐Ÿ‡ฆ๐Ÿ‡ฝ"}] \ No newline at end of file diff --git a/tests/__data__/input/data/regions.json b/tests/__data__/input/data/regions.json new file mode 100644 index 00000000..0741930a --- /dev/null +++ b/tests/__data__/input/data/regions.json @@ -0,0 +1 @@ +[{"code":"AFR","name":"Africa","countries":["AO","BF","BI","BJ","BW","CD","CF","CG","CI","CM","CV","DJ","DZ","EG","EH","ER","ET","GA","GH","GM","GN","GQ","GW","KE","KM","LR","LS","LY","MA","MG","ML","MR","MU","MW","MZ","NA","NE","NG","RE","RW","SC","SD","SH","SL","SN","SO","SS","ST","SZ","TD","TF","TG","TN","TZ","UG","YT","ZA","ZM","ZW"]},{"code":"AMER","name":"Americas","countries":["AG","AI","AR","AW","BB","BL","BM","BO","BR","BS","BV","BZ","CA","CL","CO","CR","CU","CW","DM","DO","EC","FK","GD","GF","GL","GP","GS","GT","GY","HN","HT","JM","KN","KY","LC","MF","MQ","MS","MX","NI","PA","PE","PM","PR","PY","SR","SV","SX","TC","TT","US","UY","VC","VE","VG","VI"]},{"code":"APAC","name":"Asia-Pacific","countries":["AF","AS","AU","BD","BN","BT","CK","CN","FJ","FM","GU","ID","IN","JP","KH","KI","KP","KR","LA","LK","MH","MM","MN","MP","MV","MY","NC","NF","NP","NR","NU","NZ","PF","PG","PH","PK","PN","PW","SB","SG","TH","TK","TL","TO","TV","TW","VN","VU","WF","WS"]},{"code":"ARAB","name":"Arab world","countries":["AE","BH","DJ","DZ","EG","IQ","JO","KM","KW","LB","LY","MA","MR","OM","PS","QA","SA","SD","SO","SY","TN","YE"]},{"code":"ASEAN","name":"Association of Southeast Asian Nations","countries":["BN","KH","ID","LA","MY","MM","PH","SG","TH","VN"]},{"code":"ASIA","name":"Asia","countries":["AE","AF","AM","AZ","BD","BH","BN","BT","CN","CY","GE","ID","IL","IN","IQ","IR","JO","JP","KG","KH","KP","KR","KW","KZ","LA","LB","LK","MM","MN","MV","MY","NP","OM","PH","PK","PS","QA","RU","SA","SG","SY","TH","TJ","TL","TM","TR","TW","UZ","VN","YE"]},{"code":"CARIB","name":"Caribbean","countries":["AG","AI","AW","BB","BL","BS","CU","CW","DM","DO","GD","GP","HT","JM","KN","KY","LC","MF","MQ","MS","PR","SX","TC","TT","VC","VG","VI"]},{"code":"CAS","name":"Central Asia","countries":["KG","KZ","TJ","TM","UZ"]},{"code":"CENAMER","name":"Central America","countries":["BZ","CR","SV","GT","HN","NI","PA"]},{"code":"CIS","name":"Commonwealth of Independent States","countries":["AM","AZ","BY","KG","KZ","MD","RU","TJ","UZ"]},{"code":"EMEA","name":"Europe, the Middle East and Africa","countries":["AD","AE","AL","AM","AO","AT","AZ","BA","BE","BF","BG","BH","BI","BJ","BW","BY","CD","CF","CG","CH","CI","CM","CV","CY","CZ","DE","DJ","DK","DZ","EE","EG","EH","ER","ES","ET","FI","FR","GA","GE","GH","GM","GN","GQ","GR","GW","HR","HU","IE","IQ","IR","IS","IT","JO","KE","KM","KW","KZ","LB","LI","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MG","MK","ML","MR","MT","MU","MW","MZ","NA","NE","NG","NL","NO","OM","PL","PS","PT","QA","RE","RO","RS","RU","RW","SA","SC","SD","SE","SH","SI","SK","SL","SM","SN","SO","SS","ST","SY","SZ","TD","TF","TG","TN","TR","TZ","UA","UG","UK","VA","YE","YT","ZA","ZM","ZW"]},{"code":"EUR","name":"Europe","countries":["AD","AL","AM","AT","AZ","BA","BE","BG","BY","CH","CY","CZ","DE","DK","EE","ES","FI","FR","GE","GR","HR","HU","IE","IS","IT","KZ","LI","LT","LU","LV","MC","MD","ME","MK","MT","NL","NO","PL","PT","RO","RS","RU","SE","SI","SK","SM","TR","UA","UK","VA"]},{"code":"HISPAM","name":"Hispanic America","countries":["AR","BO","CL","CO","CR","CU","DO","EC","GT","HN","MX","NI","PA","PE","PR","PY","SV","UY","VE"]},{"code":"INT","name":"Worldwide","countries":["AD","AE","AF","AG","AI","AL","AM","AO","AQ","AR","AS","AT","AU","AW","AX","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BL","BM","BN","BO","BQ","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CW","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","GA","UK","GD","GE","GF","GG","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IM","IN","IO","IQ","IR","IS","IT","JE","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","ME","MF","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","RE","RO","RS","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","SS","ST","SV","SX","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TL","TM","TN","TO","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","XK","YE","YT","ZA","ZM","ZW"]},{"code":"LAC","name":"Latin America and the Caribbean","countries":["AG","AI","AR","AW","BB","BL","BO","BR","BS","CL","CO","CR","CU","CW","DM","DO","EC","GD","GF","GP","GT","HN","HT","JM","KN","KY","LC","MF","MQ","MS","MX","NI","PA","PE","PR","PY","SV","SX","TC","TT","UY","VC","VE","VG","VI"]},{"code":"LATAM","name":"Latin America","countries":["AR","BL","BO","BR","CL","CO","CR","CU","DO","EC","GF","GP","GT","HN","HT","MF","MQ","MX","NI","PA","PE","PR","PY","SV","UY","VE"]},{"code":"MAGHREB","name":"Maghreb","countries":["DZ","LY","MA","MR","TN"]},{"code":"MENA","name":"Middle East and North Africa","countries":["AE","BH","CY","DJ","DZ","EG","EH","IL","IQ","IR","JO","KW","LB","LY","MA","OM","PS","QA","SA","SD","SY","TN","TR","YE"]},{"code":"MIDEAST","name":"Middle East","countries":["AE","BH","CY","EG","IL","IQ","IR","JO","KW","LB","OM","PS","QA","SA","SY","TR","YE"]},{"code":"NAM","name":"Northern America","countries":["BM","CA","GL","PM","US"]},{"code":"NORAM","name":"North America","countries":["AG","AI","AW","BB","BL","BM","BS","BZ","CA","CR","CU","CW","DM","DO","GD","GL","GP","GT","HN","HT","JM","KN","KY","LC","MF","MQ","MS","MX","NI","PA","PM","PR","SV","SX","TC","TT","US","VC","VG","VI"]},{"code":"NORD","name":"Nordics","countries":["AX","DK","FO","FI","IS","NO","SE"]},{"code":"OCE","name":"Oceania","countries":["AS","AU","CK","FJ","FM","GU","KI","MH","MP","NC","NF","NR","NU","NZ","PF","PG","PN","PW","SB","TK","TO","TV","VU","WF","WS"]},{"code":"SAS","name":"South Asia","countries":["AF","BD","BT","IN","LK","MV","NP","PK"]},{"code":"SOUTHAM","name":"South America","countries":["AR","BO","BR","CL","CO","EC","PY","PE","UY","VE","BV","FK","GF","GY","GS","SR"]},{"code":"SSA","name":"Sub-Saharan Africa","countries":["AO","BF","BI","BJ","BW","CD","CF","CG","CI","CM","CV","DJ","ER","ET","GA","GH","GM","GN","GQ","GW","KE","KM","LR","LS","MG","ML","MR","MU","MW","MZ","NA","NE","NG","RW","SC","SD","SL","SN","SO","SS","ST","SZ","TD","TG","TZ","UG","ZA","ZM","ZW"]},{"code":"WAFR","name":"West Africa","countries":["BF","BJ","CI","CV","GH","GM","GN","GW","LR","ML","MR","NE","NG","SH","SL","SN","TG"]}] \ No newline at end of file diff --git a/tests/__data__/input/database/update-guides/programs.db b/tests/__data__/input/database/update-guides/programs.db index 9adefaa9..c9fde18e 100644 --- a/tests/__data__/input/database/update-guides/programs.db +++ b/tests/__data__/input/database/update-guides/programs.db @@ -1,4 +1 @@ -{"titles":[{"lang":"fr","value":"World Sport"}],"descriptions":[{"lang":"fr","value":"ะ’ัะต ะพ ะณะปะฐะฒะฝั‹ั… ัะฟะพั€ั‚ะธะฒะฝั‹ั… ัะพะฑั‹ั‚ะธัั… ะผะธั€ะฐ. ะžะฑะทะพั€ั‹ ัะฐะผั‹ั… ะฒะฐะถะฝั‹ั… ัะฟะพั€ั‚ะธะฒะฝั‹ั… ัะพะฑั‹ั‚ะธะน, ะฐะฝะฐะปะธั‚ะธะบะฐ, ะผะฝะตะฝะธั ัะบัะฟะตั€ั‚ะพะฒ."}],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"CNNInternationalEurope.us","start":1641825900000,"stop":1641826800000,"site":"chaines-tv.orange.fr","_qid":"0Wefq0oMR3feCcuY","_id":"12AJc0GeEJE9p4c3"} -{"titles":[{"lang":"fr","value":"Connecting Africa. 114-ั ัะตั€ะธั"}],"descriptions":[{"lang":"fr","value":"114-ั ัะตั€ะธั. ะŸั€ะพะตะบั‚, ั€ะฐััะบะฐะทั‹ะฒะฐัŽั‰ะธะน ะพ ะปัŽะดัั… ะธ ะบะพะผะฟะฐะฝะธัั…, ะบะพั‚ะพั€ั‹ะต ัะพะฒะตั€ัˆะฐัŽั‚ ั€ะตะฒะพะปัŽั†ะธัŽ ะฒ ะฐั„ั€ะธะบะฐะฝัะบะพะผ ะฑะธะทะฝะตัะต, ะธ ะพ ั‚ะตั…, ะบั‚ะพ ะพะฑัŠะตะดะธะฝัะตั‚ ะบะพะฝั‚ะธะฝะตะฝั‚, ะฒั‹ัั‚ัƒะฟะฐั ะทะฐ ัะฒะพะฑะพะดะฝัƒัŽ ั‚ะพั€ะณะพะฒะปัŽ ะฒ ะั„ั€ะธะบะต."}],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"CNNInternationalEurope.us","start":1641843900000,"stop":1641844800000,"site":"chaines-tv.orange.fr","_qid":"0Wefq0oMR3feCcuY","_id":"1dxcT34nyxzOlxBL"} -{"titles":[{"lang":"en","value":"Robin Hood"}],"descriptions":[],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[{"system":"xmltv_ns","value":"8.256.0/1"},{"system":"onscreen","value":"S09E257"}],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"MNetMovies2.za","start":1641822300000,"stop":1641829200000,"site":"dstv.com","_qid":"1lnhXpN7g0ER5XwN","_id":"1AoKArQw6MxP6pVU"} -{"titles":[{"lang":"en","value":"Robin Hood"}],"descriptions":[],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[{"system":"xmltv_ns","value":"8.256.0/1"},{"system":"onscreen","value":"S09E257"}],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"MNetMovies2.za","start":1641822300000,"stop":1641829200000,"site":"dstv.com","_qid":"1lnhXpN7g0ER5XwN","_id":"12oKArQw6MxP6pVU"} +{"site":"allente.se","channel":"6eren.dk","titles":[{"value":"Diners, Drive-Ins and Dives","lang":"da"}],"sub_titles":[],"descriptions":[{"value":"Underholdning","lang":"da"}],"icon":{"src":"https://viasatps.api.comspace.se/PS/channeldate/image/viasat.ps/487/2022-10-24/se.cs.6eren.event.B_0254194276971024040000.jpg?size=2560x1440"},"episodeNumbers":[{"system":"xmltv_ns","value":"23.5.0/1"},{"system":"onscreen","value":"S24E06"}],"date":null,"start":1666584000000,"stop":1666585500000,"urls":[],"ratings":[],"categories":[{"value":"series","lang":"da"}],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"_qid":"f6cxSM73LfZ8TdYz","_id":"HxsrTRTFj1z05TAK"} \ No newline at end of file diff --git a/tests/__data__/input/database/update-guides/no-programs.db b/tests/__data__/input/database/update-guides_legacy/no-programs.db similarity index 100% rename from tests/__data__/input/database/update-guides/no-programs.db rename to tests/__data__/input/database/update-guides_legacy/no-programs.db diff --git a/tests/__data__/input/database/update-guides_legacy/programs.db b/tests/__data__/input/database/update-guides_legacy/programs.db new file mode 100644 index 00000000..9adefaa9 --- /dev/null +++ b/tests/__data__/input/database/update-guides_legacy/programs.db @@ -0,0 +1,4 @@ +{"titles":[{"lang":"fr","value":"World Sport"}],"descriptions":[{"lang":"fr","value":"ะ’ัะต ะพ ะณะปะฐะฒะฝั‹ั… ัะฟะพั€ั‚ะธะฒะฝั‹ั… ัะพะฑั‹ั‚ะธัั… ะผะธั€ะฐ. ะžะฑะทะพั€ั‹ ัะฐะผั‹ั… ะฒะฐะถะฝั‹ั… ัะฟะพั€ั‚ะธะฒะฝั‹ั… ัะพะฑั‹ั‚ะธะน, ะฐะฝะฐะปะธั‚ะธะบะฐ, ะผะฝะตะฝะธั ัะบัะฟะตั€ั‚ะพะฒ."}],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"CNNInternationalEurope.us","start":1641825900000,"stop":1641826800000,"site":"chaines-tv.orange.fr","_qid":"0Wefq0oMR3feCcuY","_id":"12AJc0GeEJE9p4c3"} +{"titles":[{"lang":"fr","value":"Connecting Africa. 114-ั ัะตั€ะธั"}],"descriptions":[{"lang":"fr","value":"114-ั ัะตั€ะธั. ะŸั€ะพะตะบั‚, ั€ะฐััะบะฐะทั‹ะฒะฐัŽั‰ะธะน ะพ ะปัŽะดัั… ะธ ะบะพะผะฟะฐะฝะธัั…, ะบะพั‚ะพั€ั‹ะต ัะพะฒะตั€ัˆะฐัŽั‚ ั€ะตะฒะพะปัŽั†ะธัŽ ะฒ ะฐั„ั€ะธะบะฐะฝัะบะพะผ ะฑะธะทะฝะตัะต, ะธ ะพ ั‚ะตั…, ะบั‚ะพ ะพะฑัŠะตะดะธะฝัะตั‚ ะบะพะฝั‚ะธะฝะตะฝั‚, ะฒั‹ัั‚ัƒะฟะฐั ะทะฐ ัะฒะพะฑะพะดะฝัƒัŽ ั‚ะพั€ะณะพะฒะปัŽ ะฒ ะั„ั€ะธะบะต."}],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"CNNInternationalEurope.us","start":1641843900000,"stop":1641844800000,"site":"chaines-tv.orange.fr","_qid":"0Wefq0oMR3feCcuY","_id":"1dxcT34nyxzOlxBL"} +{"titles":[{"lang":"en","value":"Robin Hood"}],"descriptions":[],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[{"system":"xmltv_ns","value":"8.256.0/1"},{"system":"onscreen","value":"S09E257"}],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"MNetMovies2.za","start":1641822300000,"stop":1641829200000,"site":"dstv.com","_qid":"1lnhXpN7g0ER5XwN","_id":"1AoKArQw6MxP6pVU"} +{"titles":[{"lang":"en","value":"Robin Hood"}],"descriptions":[],"sub_titles":[],"urls":[],"categories":[],"icon":{},"episodeNumbers":[{"system":"xmltv_ns","value":"8.256.0/1"},{"system":"onscreen","value":"S09E257"}],"date":null,"ratings":[],"directors":[],"actors":[],"writers":[],"adapters":[],"producers":[],"composers":[],"editors":[],"presenters":[],"commentators":[],"guests":[],"channel":"MNetMovies2.za","start":1641822300000,"stop":1641829200000,"site":"dstv.com","_qid":"1lnhXpN7g0ER5XwN","_id":"12oKArQw6MxP6pVU"} diff --git a/tests/__data__/input/database/update-guides/queue.db b/tests/__data__/input/database/update-guides_legacy/queue.db similarity index 100% rename from tests/__data__/input/database/update-guides/queue.db rename to tests/__data__/input/database/update-guides_legacy/queue.db diff --git a/tests/__data__/input/logs/guides/update.log b/tests/__data__/input/logs/guides/update.log index d71cf47e..87eb14a2 100644 --- a/tests/__data__/input/logs/guides/update.log +++ b/tests/__data__/input/logs/guides/update.log @@ -1,7 +1,8 @@ -{"group":"us/magticom.ge","count":74,"status":0} -{"group":"za/dstv.com","count":1,"status":0} -{"group":"us-pr/tvtv.us","count":14,"status":0} -{"group":"us-pr/gatotv.com","count":7,"status":1} -{"group":"us-pr/directv.com","count":0,"status":0} -{"group":"ca-nl/tvtv.us","count":1,"status":0} -{"group":"us/tvtv.us","count":372,"status":0} +{"country":"DK","lang":"da","site":"allente.se","channel":"6eren.dk","filename":"dk_da"} +{"country":"CA","lang":"fr","site":"tvhebdo.com","channel":"ABCSpark.ca","filename":"ca_fr"} +{"country":"CA","lang":"fr","site":"tvhebdo.com","channel":"BBCEarthCanada.ca","filename":"ca_fr"} +{"country":"CA","lang":"fr","site":"tvhebdo.com","channel":"beINSportsCanada.ca","filename":"ca_fr"} +{"country":"CA","lang":"fr","site":"tvhebdo.com","channel":"CanalVie.ca","filename":"ca_fr"} +{"country":"CA","lang":"fr","site":"tvhebdo.com","channel":"CFMTDT.ca","filename":"ca_fr"} +{"country":"CA","lang":"en","site":"tvhebdo.com","channel":"CanalVie.ca","filename":"ca_en"} +{"country":"CA","lang":"en","site":"tvhebdo.com","channel":"CFMTDT.ca","filename":"ca_en"} \ No newline at end of file diff --git a/tests/commands/api/update.test.js b/tests/commands/api/update.test.js index 75090f99..0ac1313b 100644 --- a/tests/commands/api/update.test.js +++ b/tests/commands/api/update.test.js @@ -6,7 +6,7 @@ beforeEach(() => { fs.emptyDirSync('tests/__data__/output') const stdout = execSync( - 'CHANNELS_PATH=tests/__data__/input/sites/example.com_ca-*.channels.xml OUTPUT_DIR=tests/__data__/output/api npm run api:update', + 'LOGS_DIR=tests/__data__/input/logs OUTPUT_DIR=tests/__data__/output/api npm run api:update', { encoding: 'utf8' } ) }) diff --git a/tests/commands/guides/update.test.js b/tests/commands/guides/update.test.js index 92b74ab8..fe47a2c9 100644 --- a/tests/commands/guides/update.test.js +++ b/tests/commands/guides/update.test.js @@ -6,23 +6,19 @@ const glob = require('glob') beforeEach(() => { fs.emptyDirSync('tests/__data__/output') fs.copyFileSync( - 'tests/__data__/input/database/update-guides/queue.db', - 'tests/__data__/output/queue.db' + 'tests/__data__/input/database/update-guides/programs.db', + 'tests/__data__/output/programs.db' + ) + + const stdout = execSync( + 'DB_DIR=tests/__data__/output LOGS_DIR=tests/__data__/output/logs DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output CURR_DATE=2022-10-20 npm run guides:update', + { encoding: 'utf8' } ) }) it('can generate /guides', () => { - fs.copyFileSync( - 'tests/__data__/input/database/update-guides/programs.db', - 'tests/__data__/output/programs.db' - ) - const stdout = execSync( - 'DB_DIR=tests/__data__/output DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output CURR_DATE=2022-05-05 npm run guides:update', - { encoding: 'utf8' } - ) - const uncompressed = glob - .sync('tests/__data__/expected/guides/**/*.epg.xml') + .sync('tests/__data__/expected/guides/*.xml') .map(f => f.replace('tests/__data__/expected/', '')) uncompressed.forEach(filepath => { @@ -30,7 +26,7 @@ it('can generate /guides', () => { }) const compressed = glob - .sync('tests/__data__/expected/guides/**/*.epg.xml.gz') + .sync('tests/__data__/expected/guides/*.xml.gz') .map(f => f.replace('tests/__data__/expected/', '')) compressed.forEach(filepath => { @@ -38,29 +34,16 @@ it('can generate /guides', () => { }) const json = glob - .sync('tests/__data__/expected/guides/**/*.json') + .sync('tests/__data__/expected/guides/*.json') .map(f => f.replace('tests/__data__/expected/', '')) json.forEach(filepath => { expect(content(`output/${filepath}`), filepath).toBe(content(`expected/${filepath}`)) }) -}) -it('will terminate process if programs not found', () => { - fs.copyFileSync( - 'tests/__data__/input/database/update-guides/no-programs.db', - 'tests/__data__/output/programs.db' + expect(content('output/logs/guides/update.log')).toEqual( + content('expected/logs/guides/update.log') ) - try { - const stdout = execSync( - 'DB_DIR=tests/__data__/output DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output npm run guides:update', - { encoding: 'utf8' } - ) - process.exit(1) - } catch (err) { - expect(err.status).toBe(1) - expect(err.stdout.includes('Error: No programs found')).toBe(true) - } }) function content(filepath) { diff --git a/tests/commands/guides/update_legacy.test.js b/tests/commands/guides/update_legacy.test.js new file mode 100644 index 00000000..8152f5f3 --- /dev/null +++ b/tests/commands/guides/update_legacy.test.js @@ -0,0 +1,70 @@ +const { execSync } = require('child_process') +const fs = require('fs-extra') +const path = require('path') +const glob = require('glob') + +beforeEach(() => { + fs.emptyDirSync('tests/__data__/output') + fs.copyFileSync( + 'tests/__data__/input/database/update-guides_legacy/queue.db', + 'tests/__data__/output/queue.db' + ) +}) + +it('can generate /guides', () => { + fs.copyFileSync( + 'tests/__data__/input/database/update-guides_legacy/programs.db', + 'tests/__data__/output/programs.db' + ) + const stdout = execSync( + 'DB_DIR=tests/__data__/output DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output CURR_DATE=2022-05-05 npm run guides:update_legacy', + { encoding: 'utf8' } + ) + + const uncompressed = glob + .sync('tests/__data__/expected/guides/!()/*.epg.xml') + .map(f => f.replace('tests/__data__/expected/', '')) + + uncompressed.forEach(filepath => { + expect(content(`output/${filepath}`), filepath).toBe(content(`expected/${filepath}`)) + }) + + const compressed = glob + .sync('tests/__data__/expected/guides/!()/*.epg.xml.gz') + .map(f => f.replace('tests/__data__/expected/', '')) + + compressed.forEach(filepath => { + expect(content(`output/${filepath}`), filepath).toBe(content(`expected/${filepath}`)) + }) + + const json = glob + .sync('tests/__data__/expected/guides/!()/*.json') + .map(f => f.replace('tests/__data__/expected/', '')) + + json.forEach(filepath => { + expect(content(`output/${filepath}`), filepath).toBe(content(`expected/${filepath}`)) + }) +}) + +it('will terminate process if programs not found', () => { + fs.copyFileSync( + 'tests/__data__/input/database/update-guides_legacy/no-programs.db', + 'tests/__data__/output/programs.db' + ) + try { + const stdout = execSync( + 'DB_DIR=tests/__data__/output DATA_DIR=tests/__data__/input/data PUBLIC_DIR=tests/__data__/output npm run guides:update_legacy', + { encoding: 'utf8' } + ) + process.exit(1) + } catch (err) { + expect(err.status).toBe(1) + expect(err.stdout.includes('Error: No programs found')).toBe(true) + } +}) + +function content(filepath) { + return fs.readFileSync(`tests/__data__/${filepath}`, { + encoding: 'utf8' + }) +} diff --git a/tests/commands/queue/create.test.js b/tests/commands/queue/create.test.js index 3497b6c7..8275ad24 100644 --- a/tests/commands/queue/create.test.js +++ b/tests/commands/queue/create.test.js @@ -6,7 +6,7 @@ beforeEach(() => { fs.emptyDirSync('tests/__data__/output') const stdout = execSync( - 'DB_DIR=tests/__data__/output/database LOGS_DIR=tests/__data__/output/logs CHANNELS_PATH=tests/__data__/input/sites/example.com_ca-*.channels.xml npm run queue:create -- --max-clusters=1 --days=2', + 'DB_DIR=tests/__data__/output/database CHANNELS_PATH=tests/__data__/input/sites/example.com_ca-*.channels.xml npm run queue:create -- --max-clusters=1 --days=2', { encoding: 'utf8' } ) }) diff --git a/tests/commands/readme/update.test.js b/tests/commands/readme/update.test.js index f1648fb5..739fa772 100644 --- a/tests/commands/readme/update.test.js +++ b/tests/commands/readme/update.test.js @@ -6,7 +6,7 @@ beforeEach(() => { fs.emptyDirSync('tests/__data__/output') const stdout = execSync( - 'CHANNELS_PATH=tests/__data__/input/sites/example.com_ca-*.channels.xml DATA_DIR=tests/__data__/input/data npm run readme:update -- --config=tests/__data__/input/readme.json', + 'LOGS_DIR=tests/__data__/input/logs DATA_DIR=tests/__data__/input/data npm run readme:update -- --config=tests/__data__/input/readme.json', { encoding: 'utf8' } ) }) diff --git a/yarn.lock b/yarn.lock index 19853bfb..8c776370 100644 --- a/yarn.lock +++ b/yarn.lock @@ -565,6 +565,77 @@ "semver" "^7.3.5" "tar" "^6.1.11" +"@octokit/auth-token@^3.0.0": + "integrity" "sha512-pq7CwIMV1kmzkFTimdwjAINCXKTajZErLB4wMLYapR2nuB/Jpr66+05wOTZMSCBXP6n4DdDWT2W19Bm17vU69Q==" + "resolved" "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "@octokit/types" "^8.0.0" + +"@octokit/core@^4.1.0": + "integrity" "sha512-Czz/59VefU+kKDy+ZfDwtOIYIkFjExOKf+HA92aiTZJ6EfWpFzYQWw0l54ji8bVmyhc+mGaLUbSUmXazG7z5OQ==" + "resolved" "https://registry.npmjs.org/@octokit/core/-/core-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "@octokit/auth-token" "^3.0.0" + "@octokit/graphql" "^5.0.0" + "@octokit/request" "^6.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + "before-after-hook" "^2.2.0" + "universal-user-agent" "^6.0.0" + +"@octokit/endpoint@^7.0.0": + "integrity" "sha512-57gRlb28bwTsdNXq+O3JTQ7ERmBTuik9+LelgcLIVfYwf235VHbN9QNo4kXExtp/h8T423cR5iJThKtFYxC7Lw==" + "resolved" "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.3.tgz" + "version" "7.0.3" + dependencies: + "@octokit/types" "^8.0.0" + "is-plain-object" "^5.0.0" + "universal-user-agent" "^6.0.0" + +"@octokit/graphql@^5.0.0": + "integrity" "sha512-amO1M5QUQgYQo09aStR/XO7KAl13xpigcy/kI8/N1PnZYSS69fgte+xA4+c2DISKqUZfsh0wwjc2FaCt99L41A==" + "resolved" "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.4.tgz" + "version" "5.0.4" + dependencies: + "@octokit/request" "^6.0.0" + "@octokit/types" "^8.0.0" + "universal-user-agent" "^6.0.0" + +"@octokit/openapi-types@^14.0.0": + "integrity" "sha512-HNWisMYlR8VCnNurDU6os2ikx0s0VyEjDYHNS/h4cgb8DeOxQ0n72HyinUtdDVxJhFy3FWLGl0DJhfEWk3P5Iw==" + "resolved" "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-14.0.0.tgz" + "version" "14.0.0" + +"@octokit/request-error@^3.0.0": + "integrity" "sha512-WMNOFYrSaX8zXWoJg9u/pKgWPo94JXilMLb2VManNOby9EZxrQaBe/QSC4a1TzpAlpxofg2X/jMnCyZgL6y7eg==" + "resolved" "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "@octokit/types" "^8.0.0" + "deprecation" "^2.0.0" + "once" "^1.4.0" + +"@octokit/request@^6.0.0": + "integrity" "sha512-6VDqgj0HMc2FUX2awIs+sM6OwLgwHvAi4KCK3mT2H2IKRt6oH9d0fej5LluF5mck1lRR/rFWN0YIDSYXYSylbw==" + "resolved" "https://registry.npmjs.org/@octokit/request/-/request-6.2.2.tgz" + "version" "6.2.2" + dependencies: + "@octokit/endpoint" "^7.0.0" + "@octokit/request-error" "^3.0.0" + "@octokit/types" "^8.0.0" + "is-plain-object" "^5.0.0" + "node-fetch" "^2.6.7" + "universal-user-agent" "^6.0.0" + +"@octokit/types@^8.0.0": + "integrity" "sha512-65/TPpOJP1i3K4lBJMnWqPUJ6zuOtzhtagDvydAWbEXpbFYA0oMKKyLb95NFZZP0lSh/4b6K+DQlzvYQJQQePg==" + "resolved" "https://registry.npmjs.org/@octokit/types/-/types-8.0.0.tgz" + "version" "8.0.0" + dependencies: + "@octokit/openapi-types" "^14.0.0" + "@seald-io/binary-search-tree@^1.0.2": "integrity" "sha512-+pYGvPFAk7wUR+ONMOlc6A+LUN4kOCFwyPLjyaeS7wVibADPHWYJNYsNtyIAwjF1AXQkuaXElnIc4XjKt55QZA==" "resolved" "https://registry.npmjs.org/@seald-io/binary-search-tree/-/binary-search-tree-1.0.2.tgz" @@ -973,6 +1044,11 @@ "resolved" "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" "version" "1.5.1" +"before-after-hook@^2.2.0": + "integrity" "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + "resolved" "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz" + "version" "2.2.3" + "bindings@~1.5.0": "integrity" "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==" "resolved" "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" @@ -1446,6 +1522,11 @@ "resolved" "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" "version" "1.0.0" +"deprecation@^2.0.0": + "integrity" "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "resolved" "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz" + "version" "2.3.1" + "detect-libc@^2.0.0": "integrity" "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" "resolved" "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz" @@ -2213,6 +2294,11 @@ "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" "version" "7.0.0" +"is-plain-object@^5.0.0": + "integrity" "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + "resolved" "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz" + "version" "5.0.0" + "is-potential-custom-element-name@^1.0.1": "integrity" "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" "resolved" "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz" @@ -2805,6 +2891,11 @@ "resolved" "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" "version" "2.0.0" +"langs@^2.0.0": + "integrity" "sha512-v4pxOBEQVN1WBTfB1crhTtxzNLZU9HPWgadlwzWKISJtt6Ku/CnpBrwVy+jFv8StjxsPfwPFzO0CMwdZLJ0/BA==" + "resolved" "https://registry.npmjs.org/langs/-/langs-2.0.0.tgz" + "version" "2.0.0" + "leven@^3.1.0": "integrity" "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" "resolved" "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" @@ -3894,6 +3985,11 @@ dependencies: "is-typedarray" "^1.0.0" +"universal-user-agent@^6.0.0": + "integrity" "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" + "resolved" "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz" + "version" "6.0.0" + "universalify@^0.1.2": "integrity" "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" "resolved" "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" @@ -3904,6 +4000,13 @@ "resolved" "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz" "version" "2.0.0" +"unzipit@^1.4.0": + "integrity" "sha512-hjoB8j1igXJgmxqaAvqkIW+faKTpG9cPK6QvkBhNCyd8OPWqODXTBVqTEmZbz62K5J/dX4Xa8lTa0NRikQwSjQ==" + "resolved" "https://registry.npmjs.org/unzipit/-/unzipit-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "uzip-module" "^1.0.2" + "uri-js@^4.2.2": "integrity" "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==" "resolved" "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -3916,6 +4019,11 @@ "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" "version" "1.0.2" +"uzip-module@^1.0.2": + "integrity" "sha512-AMqwWZaknLM77G+VPYNZLEruMGWGzyigPK3/Whg99B3S6vGHuqsyl5ZrOv1UUF3paGK1U6PM0cnayioaryg/fA==" + "resolved" "https://registry.npmjs.org/uzip-module/-/uzip-module-1.0.3.tgz" + "version" "1.0.3" + "v8-compile-cache@^2.0.3": "integrity" "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==" "resolved" "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"