diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c36fb13 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,67 @@ +--- +name: "🐞 Bug Report" +about: Create a bug report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + +### Context + +Please provide any relevant information about your setup + +* Add-on Version: +* Kodi Version: +* Kodi GUI Language: +* Operating System: +* Operating System Language: + + + +------ + +### Expected Behavior + +Please describe the behavior you are expecting. + + + +------ + +### Current Behavior + +What is the current behavior? + + + +------ + +### Steps to Reproduce + +Please provide detailed steps for reproducing the issue. + +1. +2. +3. + + + +------ + +### Log + +Please include a complete [debug log](https://kodi.wiki/view/Log_file). + + + +------ + +### Additional Information + +Please provide any additional information that may be helpful. + + + +------ + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..933227e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: ❓ Questions + url: https://twitchaddon.page.link/forum + about: Please ask and answer questions here diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..77853ad --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,27 @@ +--- +name: "🚀 Feature Request" +about: Request a new feature to help us improve +title: '' +labels: 'enhancement' +assignees: '' + +--- + +### What Problem Does This Solve? + +Please provide a concise description of the problem this request solves. + + + +------ + + + +### Suggest a Solution (optional) + +Please provide a concise description of your suggested solution. If there are multiple solutions, describe them independently and optionally follow them with a comparison. + + + +------ + diff --git a/.github/workflows/addon-validations.yml b/.github/workflows/addon-validations.yml new file mode 100644 index 0000000..a090dad --- /dev/null +++ b/.github/workflows/addon-validations.yml @@ -0,0 +1,33 @@ +name: Add-on Validations + +on: + push: + branches: [ master, main ] + + pull_request: + branches: [ master, main ] + +jobs: + addon-validations: + if: github.repository == 'anxdpanic/script.module.python.twitch' + + runs-on: ubuntu-latest + name: Add-on Validations + + steps: + - name: Checkout Add-on + uses: actions/checkout@v3 + with: + path: ${{ github.event.repository.name }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install git+https://github.com/xbmc/addon-check.git + sudo apt-get update + sudo apt-get install xmlstarlet + + - name: Kodi Add-on Checker (Nexus) + id: kodi-addon-checker-nexus + run: | + kodi-addon-checker ${{ github.event.repository.name }} --branch=nexus diff --git a/.github/workflows/close-translation-prs.yml b/.github/workflows/close-translation-prs.yml new file mode 100644 index 0000000..fc3ddc3 --- /dev/null +++ b/.github/workflows/close-translation-prs.yml @@ -0,0 +1,62 @@ +name: Close Translation Pull Requests + +on: + pull_request: + branches: [ master, main, dev ] + +jobs: + + close-translation-prs: + if: github.actor != 'weblate' && github.actor != 'anxdpanic' && github.repository == 'anxdpanic/script.module.python.twitch' + + name: Close Translation Pull Requests + runs-on: ubuntu-latest + + steps: + - name: Get changed files + id: modified_files + uses: trilom/file-changes-action@v1.2.4 + with: + output: "," + + - name: Check the PR for translations + id: check + run: | + shopt -s nocasematch + if [[ "${{ steps.modified_files.outputs.files_modified }}" == *"en_gb/strings.po"* ]]; then + echo "Found modified en_gb, likely a valid PR" + unset CLOSE + elif [[ "${{ steps.modified_files.outputs.files_modified }}" == *"strings.po"* ]]; then + echo "Found modified strings.po, unwanted." + CLOSE="true" + elif [[ "${{ steps.modified_files.outputs.files_added }}" == *"strings.po"* ]]; then + echo "Found added strings.po, unwanted." + CLOSE="true" + elif [[ "${{ steps.modified_files.outputs.files_removed }}" == *"strings.po"* ]]; then + echo "Found removed strings.po, unwanted." + CLOSE="true" + else + echo "No strings.po were modified or added, not a translation." + unset CLOSE + fi + echo ::set-output name=close::${CLOSE} + + - name: Comment on the PR + uses: mshick/add-pr-comment@v1 + if: ${{ steps.check.outputs.close }} + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token-user-login: 'github-actions[bot]' + allow-repeats: true + message: | + A modified strings.po file was detected. + + Translations are not accepted through PRs, please use Weblate if you'd like to contribute to the translations. + For more information see Issue #104. + + If you feel this PR was closed in error, please reply below. + Thank you for your interest in improving this add-on. + + - name: Close the PR + uses: peter-evans/close-pull@v1 + if: ${{ steps.check.outputs.close }} diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml new file mode 100644 index 0000000..f9ddb0e --- /dev/null +++ b/.github/workflows/make-release.yml @@ -0,0 +1,85 @@ +# Based on https://github.com/im85288/service.upnext/blob/master/.github/workflows/release.yml +name: Make Release +on: + push: + tags: + - 'v*' + - '*-dev' + +jobs: + release: + if: github.repository == 'anxdpanic/script.module.python.twitch' + + name: Make Release + runs-on: ubuntu-latest + + steps: + - name: Release Status + id: release + run: | + version=${GITHUB_REF/refs\/tags\//} + if [[ $version == *-dev ]] ; + then + echo ::set-output name=pre-release::true + else + echo ::set-output name=pre-release::false + fi + + - name: Checkout Add-on + uses: actions/checkout@v3 + with: + path: ${{ github.event.repository.name }} + + - name: Install dependencies + run: | + sudo apt-get install libxml2-utils xmlstarlet zip + + - name: Get Changelog + id: changelog + run: | + changes=$(xmlstarlet sel -t -v '//news' -n addon.xml) + changes="${changes//'%'/'%25'}" + changes="${changes//$'\n'/'%0A'}" + changes="${changes//$'\r'/'%0D'}" + echo ::set-output name=changes::$changes + working-directory: ${{ github.event.repository.name }} + + - name: Remove Unwanted Files + run: | + mv .git .. + rm -rf .??* + rm *.md + working-directory: ${{ github.event.repository.name }} + + - name: Create Zip (Nexus) + id: zip-nexus + run: | + version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) + filename=${{ github.event.repository.name }}-${version}.zip + cd .. + zip -r $filename ${{ github.event.repository.name }} + echo ::set-output name=filename::$filename + working-directory: ${{ github.event.repository.name }} + + - name: Create Release + id: create-release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + body: ${{ steps.changelog.outputs.changes }} + draft: false + prerelease: ${{ steps.release.outputs.pre-release }} + + - name: Upload Zip (Nexus) + id: upload-nexus + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create-release.outputs.upload_url }} + asset_name: ${{ steps.zip-nexus.outputs.filename }} + asset_path: ${{ steps.zip-nexus.outputs.filename }} + asset_content_type: application/zip diff --git a/.github/workflows/submit-release.yml b/.github/workflows/submit-release.yml new file mode 100644 index 0000000..95be198 --- /dev/null +++ b/.github/workflows/submit-release.yml @@ -0,0 +1,60 @@ +name: Submit Release +on: + push: + tags: + - 'v*' + +jobs: + release: + if: github.repository == 'anxdpanic/script.module.python.twitch' + + name: Submit Release + runs-on: ubuntu-latest + + strategy: + + fail-fast: false + matrix: + python-version: [ 3.9 ] + + steps: + - name: Checkout Add-on + uses: actions/checkout@v3 + with: + path: ${{ github.event.repository.name }} + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + sudo apt-get install libxml2-utils xmlstarlet + python -m pip install --upgrade pip + python -m pip install git+https://github.com/xbmc/kodi-addon-submitter.git + + - name: Configure git + run: | + git config --global user.name "anxdpanic" + git config --global user.email "anxdpanic@users.noreply.github.com" + + - name: Remove Unwanted Files + run: | + mv .git .. + rm -rf .??* + mv ../.git . + rm *.md + git add . + git commit -m "Remove unwanted files" + working-directory: ${{ github.event.repository.name }} + + - name: Submit to Official Repository (Nexus) + id: submit-nexus + run: | + submit-addon -r repo-scripts -b nexus --pull-request ${{ github.event.repository.name }} + working-directory: ${{ github.event.repository.name }} + env: + GH_USERNAME: anxdpanic + GH_TOKEN: ${{ secrets.ADDON_SUBMISSION_TOKEN }} + EMAIL: anxdpanic@users.noreply.github.com diff --git a/.github/workflows/sync-addon-metadata-translations.yml b/.github/workflows/sync-addon-metadata-translations.yml new file mode 100644 index 0000000..0042913 --- /dev/null +++ b/.github/workflows/sync-addon-metadata-translations.yml @@ -0,0 +1,58 @@ +name: Sync addon metadata translations + +on: + push: + branches: [ master, main ] + paths: + - '**addon.xml' + - '**resource.language.**strings.po' + +jobs: + default: + if: github.repository == 'anxdpanic/script.module.python.twitch' + + runs-on: ubuntu-latest + + strategy: + + fail-fast: false + matrix: + python-version: [ 3.9 ] + + steps: + + - name: Checkout repository + uses: actions/checkout@v2 + with: + path: project + + - name: Checkout sync_addon_metadata_translations repository + uses: actions/checkout@v2 + with: + repository: xbmc/sync_addon_metadata_translations + path: sync_addon_metadata_translations + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install sync_addon_metadata_translations/ + + - name: Run sync-addon-metadata-translations + run: | + sync-addon-metadata-translations + working-directory: ./project + + - name: Create PR for sync-addon-metadata-translations changes + uses: peter-evans/create-pull-request@v3.10.0 + with: + commit-message: Sync of addon metadata translations + title: Sync of addon metadata translations + body: Sync of addon metadata translations triggered by ${{ github.sha }} + branch: amt-sync + delete-branch: true + path: ./project diff --git a/LICENSE b/LICENSES/GPL-3.0-only similarity index 100% rename from LICENSE rename to LICENSES/GPL-3.0-only diff --git a/README.md b/README.md index e2c935a..43555de 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ # python-twitch for Kodi + +[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fanxdpanic%2Fscript.module.python.twitch%2Fbadge&logo=none)](https://actions-badge.atrox.dev/anxdpanic/script.module.python.twitch/goto) +![License](https://img.shields.io/badge/license-GPL--3.0--only-success.svg) +![Kodi Version](https://img.shields.io/badge/kodi-nexus%2B-success.svg) +![Contributors](https://img.shields.io/github/contributors/anxdpanic/script.module.python.twitch.svg) + ###### script.module.python.twitch python-twitch for Kodi is module for interaction with the Twitch.tv API #### Usage: -Example can be found [MrSprigster/Twitch-on-Kodi/master/resources/lib/addon/api.py](https://github.com/MrSprigster/Twitch-on-Kodi/blob/master/resources/lib/addon/api.py) +Example can be found [anxdpanic/plugin.video.twitch/master/resources/lib/twitch_addon/addon/api.py](https://github.com/anxdpanic/Twitch-on-Kodi/blob/master/resources/lib/twitch_addon/addon/api.py) #### API Documentation: https://dev.twitch.tv/docs diff --git a/addon.xml b/addon.xml index 62e2dd1..c9173fc 100644 --- a/addon.xml +++ b/addon.xml @@ -1,27 +1,49 @@ - + - - - + + + all -*** Twitch API V5 is deprecated and will be removed entirely on 2/14/18 -*** script.module.python.twitch v2 will include the Twitch helix API, and may include breaking changes to current implementations. If you rely on this module follow development at https://github.com/MrSprigster/script.module.python.twitch - -[fix/upd] v5.channels deprecated notations for community endpoints, add new communities endpoints +[fix] Following Channels - icon.png + resources/media/icon.png all - Module for interaction with the Twitch.tv API - python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu. - GNU GENERAL PUBLIC LICENSE. Version 3, 29 June 2007 - - https://github.com/MrSprigster/script.module.python.twitch + GPL-3.0-only + https://github.com/anxdpanic/script.module.python.twitch + https://twitchaddon.panicked.xyz/forum + true + Modul til interaktion med Twitch.tv API + Modul für die Interaktion mit der Twitch.tv-API + Module for interaction with the Twitch.tv API + Módulo para interactuar con la API de Twitch.tv + Moduuli Twitch.tv-rajapinnan käyttöön + Module pour interagir avec l'API de Twitch.tv + Modul za interakciju s Twitch.tv API + Modul a Twitch.tv API -val való interakcióhoz + Modulo per l'interazione con l'API di Twitch.tv + Twitch.tv API와의 상호작용을 위한 모듈 + Moduł do interakcji z API Twitch.tv + Модуль для взаимодействия с Twitch.tv API + 与 Twitch.tv API 交互的模块 + python-twitch til Kodi er et modul til interaktion med Twitch.tv API baseret på python-twitch af ingwinlu. + python-twitch für Kodi ist ein Modul für die Interaktion mit der Twitch.tv-API, basierend auf python-twitch von ingwinlu. + python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu. + python-twitch para Kodi es un módulo para interactuar con la API de Twitch.tv basado en python-twitch de ingwinlu. + 'Python-twitch for Kodi' on Twitch.tv-palvelun rajapinnan hyödyntämisen mahdollistava moduuli, joka perustuu kehittäjän ingwinlu luomaan python-twitch-moduuliin. + python-twitch pour Kodi est un module pour interagir avec l'API Twitch.tv basé sur python-twitch de ingwinlu. + python-twitch za Kodi je modul za interakciju s Twitch.tv API-jem temeljenom na python-twitch by ingwinlu. + A python-twitch a Kodi számára a Twitch.tv API-val való interakció modulja, amely az ingwinlu python-twitch-jén alapul. + python-twitch per Kodi è un modulo per l'interazione con l'API di Twitch.tv basato su python-twitch di ingwinlu. + Kodi용 python-twitch는 ingwinlu의 python-twitch를 기반으로 하는 Twitch.tv API와 상호 작용하기 위한 모듈입니다. + python-twitch dla Kodi to moduł do interakcji z API Twitch.tv oparty na python-twitch autorstwa ingwinlu. + python-twitch это модуль для взаимодействия с Twitch.tv API. Основан на модуле python-twitch от ingwinlu. + Kodi 的 python-twitch 是从 ingwinlu 开发版本移植的与 twitch.tv API 交互的模块。 diff --git a/changelog.txt b/changelog.txt index e4e44d3..6643b4e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,95 @@ +3.0.0 +[rem] removed support for Kodi versions pre-Nexus v20 +[rem] removed python 2 support +[rem] removed dependency on six +[lang] updated translations from Weblate + +2.0.21 +[upd] fixups and additions to Helix API +[rem] remove v5 API +[lang] updated translations from Weblate + +2.0.19 +[fix] fix v5.users.follow_channel request - apo86 +[fix] fix v5.users.unfollow_channel request - apo86 +[lang] updated translations from Weblate + +2.0.18 +[fix] fix v5.streams.by_id request - apo86 + +2.0.17 +[fix] playback of clips + +2.0.16 +[fix] playback, change access token to use gql endpoints +[chg] change followed, following, and unfollowing games to use gql endpoints + +2.0.15 +[upd] Update material to reflect repository changes + +2.0.14 +[fix] logging in Kodi 19 + +2.0.13 +[fix] stream language + +2.0.12 +[fix] clip usher + +2.0.11 +[fix] Python 3 - don't decode string when processing clips + +2.0.10 +[upd] allow overriding headers for all hidden api calls + +2.0.9 +[upd] allow overriding headers for usher and hidden api calls +[fix] decode byte responses in scraper +[fix] usher/parser error handling + +2.0.8 +[upd] Mark communities endpoints and queries as deprecated + +2.0.7 +[add] add platform parameter to usher + +2.0.6 +[add] add missing helix api endpoints + +2.0.5 +[add] add frame rate and resolution to returned stream information +[upd] update usher parameters +[upd] use SPDX license identifiers + +2.0.4 +[fix] video/live stream token encoding + +2.0.3 +[chg] redact user ip from token logging + +2.0.2 +[fix] Python 3 compat. +[add] fast_bread param to usher +[add/upd] helix endpoints + +2.0.1 +[fix] clips usher +[chg] to relative imports +[upd] deprecation/removal dates + +2.0.0 +[chg] refactor oauth for inclusion of helix +- adds MobileClient().prepare_token_uri() for generating app access tokens, adds client_secret requirement +- adds MobileClient().prepare_revoke_uri() for revoking oauth tokens +- adds helix oauth scopes +- twitch.scopes -> twitch.oauth..scopes +- twitch.oauth.MobileClient() -> twitch.oauth.clients.MobileClient() +[add] helix api +[add] add usher.live_request and usher.video_request +[add] MobileClient().revoke_token and MobileClient().get_app_access_token queries +[add] Kodi logging +[add] v5 users.users + 1.1.0 *** Twitch API V5 is deprecated and will be removed entirely on 2/14/18 *** script.module.python.twitch v2 will include the Twitch helix API, and may include breaking changes to current implementations. diff --git a/resources/__init__.py b/resources/__init__.py index e69de29..a5da79b 100644 --- a/resources/__init__.py +++ b/resources/__init__.py @@ -0,0 +1,9 @@ +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" diff --git a/resources/language/resource.language.af_za/strings.po b/resources/language/resource.language.af_za/strings.po new file mode 100644 index 0000000..f9caee4 --- /dev/null +++ b/resources/language/resource.language.af_za/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: af_za\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.am_et/strings.po b/resources/language/resource.language.am_et/strings.po new file mode 100644 index 0000000..3219e71 --- /dev/null +++ b/resources/language/resource.language.am_et/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: am_et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ar_sa/strings.po b/resources/language/resource.language.ar_sa/strings.po new file mode 100644 index 0000000..49309ef --- /dev/null +++ b/resources/language/resource.language.ar_sa/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ar_sa\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ast_es/strings.po b/resources/language/resource.language.ast_es/strings.po new file mode 100644 index 0000000..2c13e49 --- /dev/null +++ b/resources/language/resource.language.ast_es/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ast_es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.az_az/strings.po b/resources/language/resource.language.az_az/strings.po new file mode 100644 index 0000000..7caeff7 --- /dev/null +++ b/resources/language/resource.language.az_az/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: az_az\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.be_by/strings.po b/resources/language/resource.language.be_by/strings.po new file mode 100644 index 0000000..94699ad --- /dev/null +++ b/resources/language/resource.language.be_by/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: be_by\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.bg_bg/strings.po b/resources/language/resource.language.bg_bg/strings.po new file mode 100644 index 0000000..ca764b9 --- /dev/null +++ b/resources/language/resource.language.bg_bg/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: bg_bg\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.bs_ba/strings.po b/resources/language/resource.language.bs_ba/strings.po new file mode 100644 index 0000000..a8ff29d --- /dev/null +++ b/resources/language/resource.language.bs_ba/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: bs_ba\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ca_es/strings.po b/resources/language/resource.language.ca_es/strings.po new file mode 100644 index 0000000..40a64bc --- /dev/null +++ b/resources/language/resource.language.ca_es/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ca_es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.cs_cz/strings.po b/resources/language/resource.language.cs_cz/strings.po new file mode 100644 index 0000000..5efb062 --- /dev/null +++ b/resources/language/resource.language.cs_cz/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: cs_cz\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.cy_gb/strings.po b/resources/language/resource.language.cy_gb/strings.po new file mode 100644 index 0000000..4ed549f --- /dev/null +++ b/resources/language/resource.language.cy_gb/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: cy_gb\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=(n==0) ? 0 : (n==1) ? 1 : (n==2) ? 2 : (n==3) ? 3 :(n==6) ? 4 : 5;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.da_dk/strings.po b/resources/language/resource.language.da_dk/strings.po new file mode 100644 index 0000000..1373d1b --- /dev/null +++ b/resources/language/resource.language.da_dk/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-07-26 22:29+0000\n" +"Last-Translator: Christian Gade \n" +"Language-Team: Danish \n" +"Language: da_dk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.7.2\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Modul til interaktion med Twitch.tv API" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch til Kodi er et modul til interaktion med Twitch.tv API baseret på python-twitch af ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.de_de/strings.po b/resources/language/resource.language.de_de/strings.po new file mode 100644 index 0000000..984c040 --- /dev/null +++ b/resources/language/resource.language.de_de/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-07-26 22:29+0000\n" +"Last-Translator: Kai Sommerfeld \n" +"Language-Team: German \n" +"Language: de_de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.7.2\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Modul für die Interaktion mit der Twitch.tv-API" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch für Kodi ist ein Modul für die Interaktion mit der Twitch.tv-API, basierend auf python-twitch von ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.el_gr/strings.po b/resources/language/resource.language.el_gr/strings.po new file mode 100644 index 0000000..a8abdb8 --- /dev/null +++ b/resources/language/resource.language.el_gr/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: el_gr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.en_au/strings.po b/resources/language/resource.language.en_au/strings.po new file mode 100644 index 0000000..538c342 --- /dev/null +++ b/resources/language/resource.language.en_au/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_au\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po new file mode 100644 index 0000000..6c97c21 --- /dev/null +++ b/resources/language/resource.language.en_gb/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.en_nz/strings.po b/resources/language/resource.language.en_nz/strings.po new file mode 100644 index 0000000..0bf3c89 --- /dev/null +++ b/resources/language/resource.language.en_nz/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_nz\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.en_us/strings.po b/resources/language/resource.language.en_us/strings.po new file mode 100644 index 0000000..c03bd8b --- /dev/null +++ b/resources/language/resource.language.en_us/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_us\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.eo/strings.po b/resources/language/resource.language.eo/strings.po new file mode 100644 index 0000000..5d972fb --- /dev/null +++ b/resources/language/resource.language.eo/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: eo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.es_ar/strings.po b/resources/language/resource.language.es_ar/strings.po new file mode 100644 index 0000000..feb15b2 --- /dev/null +++ b/resources/language/resource.language.es_ar/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: es_ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.es_es/strings.po b/resources/language/resource.language.es_es/strings.po new file mode 100644 index 0000000..aadd794 --- /dev/null +++ b/resources/language/resource.language.es_es/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-07-03 12:29+0000\n" +"Last-Translator: Alfonso Cachero \n" +"Language-Team: Spanish (Spain) \n" +"Language: es_es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.7.1\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Módulo para interactuar con la API de Twitch.tv" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch para Kodi es un módulo para interactuar con la API de Twitch.tv basado en python-twitch de ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.es_mx/strings.po b/resources/language/resource.language.es_mx/strings.po new file mode 100644 index 0000000..1c570be --- /dev/null +++ b/resources/language/resource.language.es_mx/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: es_mx\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.et_ee/strings.po b/resources/language/resource.language.et_ee/strings.po new file mode 100644 index 0000000..1e4e6ef --- /dev/null +++ b/resources/language/resource.language.et_ee/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: et_ee\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.eu_es/strings.po b/resources/language/resource.language.eu_es/strings.po new file mode 100644 index 0000000..de8b73e --- /dev/null +++ b/resources/language/resource.language.eu_es/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: eu_es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fa_af/strings.po b/resources/language/resource.language.fa_af/strings.po new file mode 100644 index 0000000..5827aa1 --- /dev/null +++ b/resources/language/resource.language.fa_af/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fa_af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fa_ir/strings.po b/resources/language/resource.language.fa_ir/strings.po new file mode 100644 index 0000000..9a5629a --- /dev/null +++ b/resources/language/resource.language.fa_ir/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fa_ir\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fi_fi/strings.po b/resources/language/resource.language.fi_fi/strings.po new file mode 100644 index 0000000..59078cb --- /dev/null +++ b/resources/language/resource.language.fi_fi/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2022-03-19 06:51+0000\n" +"Last-Translator: Oskari Lavinto \n" +"Language-Team: Finnish \n" +"Language: fi_fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.11.2\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Moduuli Twitch.tv-rajapinnan käyttöön" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "'Python-twitch for Kodi' on Twitch.tv-palvelun rajapinnan hyödyntämisen mahdollistava moduuli, joka perustuu kehittäjän ingwinlu luomaan python-twitch-moduuliin." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fo_fo/strings.po b/resources/language/resource.language.fo_fo/strings.po new file mode 100644 index 0000000..e33bf91 --- /dev/null +++ b/resources/language/resource.language.fo_fo/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fo_fo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fr_ca/strings.po b/resources/language/resource.language.fr_ca/strings.po new file mode 100644 index 0000000..42403e3 --- /dev/null +++ b/resources/language/resource.language.fr_ca/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr_ca\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.fr_fr/strings.po b/resources/language/resource.language.fr_fr/strings.po new file mode 100644 index 0000000..8ec67af --- /dev/null +++ b/resources/language/resource.language.fr_fr/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2023-07-18 17:11+0000\n" +"Last-Translator: skypichat \n" +"Language-Team: French (France) \n" +"Language: fr_fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.18.2\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Module pour interagir avec l'API de Twitch.tv" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch pour Kodi est un module pour interagir avec l'API Twitch.tv basé sur python-twitch de ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.gl_es/strings.po b/resources/language/resource.language.gl_es/strings.po new file mode 100644 index 0000000..f3b0710 --- /dev/null +++ b/resources/language/resource.language.gl_es/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: gl_es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.he_il/strings.po b/resources/language/resource.language.he_il/strings.po new file mode 100644 index 0000000..ce7d95a --- /dev/null +++ b/resources/language/resource.language.he_il/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: he_il\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && n % 10 == 0) ? 2 : 3));\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.hi_in/strings.po b/resources/language/resource.language.hi_in/strings.po new file mode 100644 index 0000000..815f961 --- /dev/null +++ b/resources/language/resource.language.hi_in/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: hi_in\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.hr_hr/strings.po b/resources/language/resource.language.hr_hr/strings.po new file mode 100644 index 0000000..5824f81 --- /dev/null +++ b/resources/language/resource.language.hr_hr/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2023-01-11 18:15+0000\n" +"Last-Translator: gogogogi \n" +"Language-Team: Croatian \n" +"Language: hr_hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.15\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Modul za interakciju s Twitch.tv API" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch za Kodi je modul za interakciju s Twitch.tv API-jem temeljenom na python-twitch by ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.hu_hu/strings.po b/resources/language/resource.language.hu_hu/strings.po new file mode 100644 index 0000000..f55904d --- /dev/null +++ b/resources/language/resource.language.hu_hu/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-09-03 07:29+0000\n" +"Last-Translator: Frodo19 \n" +"Language-Team: Hungarian \n" +"Language: hu_hu\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.8\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Modul a Twitch.tv API -val való interakcióhoz" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "A python-twitch a Kodi számára a Twitch.tv API-val való interakció modulja, amely az ingwinlu python-twitch-jén alapul." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.hy_am/strings.po b/resources/language/resource.language.hy_am/strings.po new file mode 100644 index 0000000..0731cc1 --- /dev/null +++ b/resources/language/resource.language.hy_am/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: hy_am\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.id_id/strings.po b/resources/language/resource.language.id_id/strings.po new file mode 100644 index 0000000..6c224a7 --- /dev/null +++ b/resources/language/resource.language.id_id/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: id_id\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.is_is/strings.po b/resources/language/resource.language.is_is/strings.po new file mode 100644 index 0000000..ba59c92 --- /dev/null +++ b/resources/language/resource.language.is_is/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: is_is\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n % 10 != 1 || n % 100 == 11;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.it_it/strings.po b/resources/language/resource.language.it_it/strings.po new file mode 100644 index 0000000..3d590cb --- /dev/null +++ b/resources/language/resource.language.it_it/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2023-04-24 04:16+0000\n" +"Last-Translator: Massimo Pissarello \n" +"Language-Team: Italian \n" +"Language: it_it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.15.2\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Modulo per l'interazione con l'API di Twitch.tv" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch per Kodi è un modulo per l'interazione con l'API di Twitch.tv basato su python-twitch di ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ja_jp/strings.po b/resources/language/resource.language.ja_jp/strings.po new file mode 100644 index 0000000..f86cb14 --- /dev/null +++ b/resources/language/resource.language.ja_jp/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ja_jp\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.kn_in/strings.po b/resources/language/resource.language.kn_in/strings.po new file mode 100644 index 0000000..5cd9c36 --- /dev/null +++ b/resources/language/resource.language.kn_in/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: kn_in\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ko_kr/strings.po b/resources/language/resource.language.ko_kr/strings.po new file mode 100644 index 0000000..fd61f62 --- /dev/null +++ b/resources/language/resource.language.ko_kr/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-08-28 07:29+0000\n" +"Last-Translator: Minho Park \n" +"Language-Team: Korean \n" +"Language: ko_kr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.8\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Twitch.tv API와의 상호작용을 위한 모듈" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "Kodi용 python-twitch는 ingwinlu의 python-twitch를 기반으로 하는 Twitch.tv API와 상호 작용하기 위한 모듈입니다." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.lt_lt/strings.po b/resources/language/resource.language.lt_lt/strings.po new file mode 100644 index 0000000..6915094 --- /dev/null +++ b/resources/language/resource.language.lt_lt/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: lt_lt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n % 10 == 1 && (n % 100 < 11 || n % 100 > 19)) ? 0 : ((n % 10 >= 2 && n % 10 <= 9 && (n % 100 < 11 || n % 100 > 19)) ? 1 : 2);\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.lv_lv/strings.po b/resources/language/resource.language.lv_lv/strings.po new file mode 100644 index 0000000..29af1b6 --- /dev/null +++ b/resources/language/resource.language.lv_lv/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: lv_lv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= 19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.mi/strings.po b/resources/language/resource.language.mi/strings.po new file mode 100644 index 0000000..8139e5f --- /dev/null +++ b/resources/language/resource.language.mi/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: mi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.mk_mk/strings.po b/resources/language/resource.language.mk_mk/strings.po new file mode 100644 index 0000000..9d585c3 --- /dev/null +++ b/resources/language/resource.language.mk_mk/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: mk_mk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n==1 || n%10==1 ? 0 : 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ml_in/strings.po b/resources/language/resource.language.ml_in/strings.po new file mode 100644 index 0000000..91be51d --- /dev/null +++ b/resources/language/resource.language.ml_in/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ml_in\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.mn_mn/strings.po b/resources/language/resource.language.mn_mn/strings.po new file mode 100644 index 0000000..1ab5565 --- /dev/null +++ b/resources/language/resource.language.mn_mn/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: mn_mn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ms_my/strings.po b/resources/language/resource.language.ms_my/strings.po new file mode 100644 index 0000000..b3e6455 --- /dev/null +++ b/resources/language/resource.language.ms_my/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ms_my\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.mt_mt/strings.po b/resources/language/resource.language.mt_mt/strings.po new file mode 100644 index 0000000..cb4f4c6 --- /dev/null +++ b/resources/language/resource.language.mt_mt/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: mt_mt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n==1 ? 0 : n==0 || ( n%100>1 && n%100<11) ? 1 : (n%100>10 && n%100<20 ) ? 2 : 3;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.my_mm/strings.po b/resources/language/resource.language.my_mm/strings.po new file mode 100644 index 0000000..2403bdb --- /dev/null +++ b/resources/language/resource.language.my_mm/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: my_mm\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.nb_no/strings.po b/resources/language/resource.language.nb_no/strings.po new file mode 100644 index 0000000..23d1b7b --- /dev/null +++ b/resources/language/resource.language.nb_no/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: nb_no\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po new file mode 100644 index 0000000..467771d --- /dev/null +++ b/resources/language/resource.language.nl_nl/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: nl_nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.oc_fr/strings.po b/resources/language/resource.language.oc_fr/strings.po new file mode 100644 index 0000000..3456503 --- /dev/null +++ b/resources/language/resource.language.oc_fr/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: oc_fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.os_os/strings.po b/resources/language/resource.language.os_os/strings.po new file mode 100644 index 0000000..68122ae --- /dev/null +++ b/resources/language/resource.language.os_os/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: os_os\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.pl_pl/strings.po b/resources/language/resource.language.pl_pl/strings.po new file mode 100644 index 0000000..28d29c3 --- /dev/null +++ b/resources/language/resource.language.pl_pl/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-07-16 19:29+0000\n" +"Last-Translator: Marek Adamski \n" +"Language-Team: Polish \n" +"Language: pl_pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.7.1\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Moduł do interakcji z API Twitch.tv" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch dla Kodi to moduł do interakcji z API Twitch.tv oparty na python-twitch autorstwa ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.pt_br/strings.po b/resources/language/resource.language.pt_br/strings.po new file mode 100644 index 0000000..2a06737 --- /dev/null +++ b/resources/language/resource.language.pt_br/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_br\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.pt_pt/strings.po b/resources/language/resource.language.pt_pt/strings.po new file mode 100644 index 0000000..1149431 --- /dev/null +++ b/resources/language/resource.language.pt_pt/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: pt_pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ro_ro/strings.po b/resources/language/resource.language.ro_ro/strings.po new file mode 100644 index 0000000..a215c77 --- /dev/null +++ b/resources/language/resource.language.ro_ro/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ro_ro\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ru_ru/strings.po b/resources/language/resource.language.ru_ru/strings.po new file mode 100644 index 0000000..c94f879 --- /dev/null +++ b/resources/language/resource.language.ru_ru/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-06-25 00:29+0000\n" +"Last-Translator: Dmitry Petrov \n" +"Language-Team: Russian \n" +"Language: ru_ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.7\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "Модуль для взаимодействия с Twitch.tv API" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "python-twitch это модуль для взаимодействия с Twitch.tv API. Основан на модуле python-twitch от ingwinlu." + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.si_lk/strings.po b/resources/language/resource.language.si_lk/strings.po new file mode 100644 index 0000000..669f34d --- /dev/null +++ b/resources/language/resource.language.si_lk/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: si_lk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sk_sk/strings.po b/resources/language/resource.language.sk_sk/strings.po new file mode 100644 index 0000000..4289d32 --- /dev/null +++ b/resources/language/resource.language.sk_sk/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sk_sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sl_si/strings.po b/resources/language/resource.language.sl_si/strings.po new file mode 100644 index 0000000..e2c5089 --- /dev/null +++ b/resources/language/resource.language.sl_si/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sl_si\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sq_al/strings.po b/resources/language/resource.language.sq_al/strings.po new file mode 100644 index 0000000..f6ec84b --- /dev/null +++ b/resources/language/resource.language.sq_al/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sq_al\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sr_rs/strings.po b/resources/language/resource.language.sr_rs/strings.po new file mode 100644 index 0000000..93395c6 --- /dev/null +++ b/resources/language/resource.language.sr_rs/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sr_rs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sr_rs@latin/strings.po b/resources/language/resource.language.sr_rs@latin/strings.po new file mode 100644 index 0000000..a25e3ce --- /dev/null +++ b/resources/language/resource.language.sr_rs@latin/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sr_Latn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.sv_se/strings.po b/resources/language/resource.language.sv_se/strings.po new file mode 100644 index 0000000..e6465e7 --- /dev/null +++ b/resources/language/resource.language.sv_se/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: sv_se\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.szl/strings.po b/resources/language/resource.language.szl/strings.po new file mode 100644 index 0000000..237d479 --- /dev/null +++ b/resources/language/resource.language.szl/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: szl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.ta_in/strings.po b/resources/language/resource.language.ta_in/strings.po new file mode 100644 index 0000000..85acf56 --- /dev/null +++ b/resources/language/resource.language.ta_in/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: ta_in\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.te_in/strings.po b/resources/language/resource.language.te_in/strings.po new file mode 100644 index 0000000..af21ffc --- /dev/null +++ b/resources/language/resource.language.te_in/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: te_in\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.tg_tj/strings.po b/resources/language/resource.language.tg_tj/strings.po new file mode 100644 index 0000000..3a3a014 --- /dev/null +++ b/resources/language/resource.language.tg_tj/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: tg_tj\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.th_th/strings.po b/resources/language/resource.language.th_th/strings.po new file mode 100644 index 0000000..b57ce77 --- /dev/null +++ b/resources/language/resource.language.th_th/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: th_th\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.tr_tr/strings.po b/resources/language/resource.language.tr_tr/strings.po new file mode 100644 index 0000000..c623f4a --- /dev/null +++ b/resources/language/resource.language.tr_tr/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: tr_tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.uk_ua/strings.po b/resources/language/resource.language.uk_ua/strings.po new file mode 100644 index 0000000..6d43b02 --- /dev/null +++ b/resources/language/resource.language.uk_ua/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: uk_ua\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.uz_uz/strings.po b/resources/language/resource.language.uz_uz/strings.po new file mode 100644 index 0000000..9b19a10 --- /dev/null +++ b/resources/language/resource.language.uz_uz/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: uz_uz\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.vi_vn/strings.po b/resources/language/resource.language.vi_vn/strings.po new file mode 100644 index 0000000..ac7fb23 --- /dev/null +++ b/resources/language/resource.language.vi_vn/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: vi_vn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.zh_cn/strings.po b/resources/language/resource.language.zh_cn/strings.po new file mode 100644 index 0000000..13e2188 --- /dev/null +++ b/resources/language/resource.language.zh_cn/strings.po @@ -0,0 +1,26 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: 2021-11-09 03:30+0000\n" +"Last-Translator: taxigps \n" +"Language-Team: Chinese (China) \n" +"Language: zh_cn\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.8.1\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "与 Twitch.tv API 交互的模块" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "Kodi 的 python-twitch 是从 ingwinlu 开发版本移植的与 twitch.tv API 交互的模块。" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/language/resource.language.zh_tw/strings.po b/resources/language/resource.language.zh_tw/strings.po new file mode 100644 index 0000000..4e24d8b --- /dev/null +++ b/resources/language/resource.language.zh_tw/strings.po @@ -0,0 +1,25 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 00:54+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: zh_tw\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +msgctxt "Addon Summary" +msgid "Module for interaction with the Twitch.tv API" +msgstr "" + +msgctxt "Addon Description" +msgid "python-twitch for Kodi is module for interaction with the Twitch.tv API based on python-twitch by ingwinlu." +msgstr "" + +# msgctxt "#30000" +# msgid "" +# msgstr "" diff --git a/resources/lib/__init__.py b/resources/lib/__init__.py index e69de29..af6ec44 100644 --- a/resources/lib/__init__.py +++ b/resources/lib/__init__.py @@ -0,0 +1,13 @@ +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['twitch'] + +from . import twitch diff --git a/resources/lib/twitch/__init__.py b/resources/lib/twitch/__init__.py index e4249b9..9daac67 100644 --- a/resources/lib/twitch/__init__.py +++ b/resources/lib/twitch/__init__.py @@ -1,5 +1,30 @@ # -*- encoding: utf-8 -*- +""" -VERSION = '1.3.0' + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['VERSION', 'CLIENT_ID', 'CLIENT_SECRET', 'OAUTH_TOKEN', 'APP_TOKEN', 'api', 'oauth', + 'exceptions', 'keys', 'log', 'methods', 'parser', 'queries', 'scraper'] + +VERSION = '2.0.0' CLIENT_ID = '' +CLIENT_SECRET = '' OAUTH_TOKEN = '' +APP_TOKEN = '' + +from . import api +from . import oauth +from . import exceptions +from . import keys +from . import log +from . import methods +from . import parser +from . import queries +from . import scraper diff --git a/resources/lib/twitch/api/__init__.py b/resources/lib/twitch/api/__init__.py index 344db26..2eff873 100644 --- a/resources/lib/twitch/api/__init__.py +++ b/resources/lib/twitch/api/__init__.py @@ -1,6 +1,18 @@ # -*- encoding: utf-8 -*- +""" -from twitch.api import v5 # V5 is deprecated and will be removed entirely on 2/14/18 -from twitch.api import v5 as default + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch -__all__ = ['v5', 'default'] + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['default', 'helix', 'parameters', 'usher'] + +from . import helix as default +from . import helix +from . import parameters +from . import usher diff --git a/resources/lib/twitch/api/helix/__init__.py b/resources/lib/twitch/api/helix/__init__.py new file mode 100644 index 0000000..2a5eeb1 --- /dev/null +++ b/resources/lib/twitch/api/helix/__init__.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/ + + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['analytics', 'bits', 'clips', 'entitlements', 'games', 'search', 'streams', + 'subscriptions', 'tags', 'users', 'videos', 'webhooks'] + +from . import analytics # NOQA +from . import bits # NOQA +from . import clips # NOQA +from . import entitlements # NOQA +from . import games # NOQA +from . import search # NOQA +from . import streams # NOQA +from . import subscriptions # NOQA +from . import tags # NOQA +from . import users # NOQA +from . import videos # NOQA +from . import webhooks # NOQA diff --git a/resources/lib/twitch/api/helix/analytics.py b/resources/lib/twitch/api/helix/analytics.py new file mode 100644 index 0000000..15ca000 --- /dev/null +++ b/resources/lib/twitch/api/helix/analytics.py @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import IntRange, Cursor, ReportType +from ... import keys +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: analytics:read:extensions +@query +def extensions(started_at='', ended_at='', extension_id='', report_type='', after='MA==', first=20, use_app_token=False): + q = Qry('analytics/extensions', use_app_token=use_app_token) + q.add_param(keys.STARTED_AT, started_at, '') + q.add_param(keys.ENDED_AT, ended_at, '') + q.add_param(keys.EXTENSION_ID, extension_id, '') + if report_type: + q.add_param(keys.TYPE, ReportType.validate(report_type)) + if not extension_id: + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q + + +# required scope: analytics:read:games +@query +def games(started_at='', ended_at='', game_id='', report_type='', after='MA==', first=20, use_app_token=False): + q = Qry('analytics/games', use_app_token=use_app_token) + q.add_param(keys.STARTED_AT, started_at, '') + q.add_param(keys.ENDED_AT, ended_at, '') + q.add_param(keys.GAME_ID, game_id, '') + if report_type: + q.add_param(keys.TYPE, ReportType.validate(report_type)) + if not game_id: + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q diff --git a/resources/lib/twitch/api/helix/bits.py b/resources/lib/twitch/api/helix/bits.py new file mode 100644 index 0000000..da94817 --- /dev/null +++ b/resources/lib/twitch/api/helix/bits.py @@ -0,0 +1,29 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import PeriodHelix, IntRange +from ... import keys +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: bits:read +@query +def get_bits_leaderboard(count=10, period=PeriodHelix.ALL, started_at='', user_id='', use_app_token=False): + q = Qry('bits/leaderboard', use_app_token=use_app_token) + q.add_param(keys.COUNT, IntRange(1, 100).validate(count), 10) + q.add_param(keys.PERIOD, PeriodHelix.validate(period), PeriodHelix.ALL) + if period != PeriodHelix.ALL: + q.add_param(keys.STARTED_AT, started_at, '') + q.add_param(keys.USER_ID, user_id, '') + + return q diff --git a/resources/lib/twitch/api/helix/clips.py b/resources/lib/twitch/api/helix/clips.py new file mode 100644 index 0000000..af1a090 --- /dev/null +++ b/resources/lib/twitch/api/helix/clips.py @@ -0,0 +1,43 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import Boolean, Cursor, IntRange, ItemCount +from ... import keys, methods +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +@query +def get_clip(broadcaster_id='', game_id='', clip_id=list(), + after='MA==', before='MA==', first=20, use_app_token=False): + q = Qry('clips', use_app_token=use_app_token) + q.add_param(keys.ID, ItemCount().validate(clip_id), list()) + if len(clip_id) != 1: + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.BROADCASTER_ID, broadcaster_id, '') + q.add_param(keys.GAME_ID, game_id, '') + q.add_param(keys.ID, ItemCount().validate(clip_id), list()) + + return q + + +# required scope: clips:edit +@query +def create_clip(broadcaster_id, has_delay=Boolean.FALSE, use_app_token=False): + q = Qry('clips', use_app_token=use_app_token, method=methods.POST) + q.add_param(keys.BROADCASTER_ID, broadcaster_id, '') + q.add_param(keys.HAS_DELAY, Boolean.validate(has_delay), Boolean.FALSE) + + return q diff --git a/resources/lib/twitch/api/helix/entitlements.py b/resources/lib/twitch/api/helix/entitlements.py new file mode 100644 index 0000000..e146ad4 --- /dev/null +++ b/resources/lib/twitch/api/helix/entitlements.py @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import EntitlementType, ItemCount +from ... import keys +from ... import methods +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +# requires app access token +@query +def upload(manifest_id, entitlement_type=EntitlementType.BULK_DROPS_GRANT): + q = Qry('entitlements/upload', use_app_token=True) + q.add_param(keys.MANIFEST_ID, manifest_id) + q.add_param(keys.TYPE, EntitlementType.validate(entitlement_type)) + + return q + + +# required scope: none +# requires app access token +@query +def get_code_status(code, user_id): + q = Qry('entitlements/codes', use_app_token=True, method=methods.GET) + q.add_param(keys.CODE, ItemCount(max_items=20).validate(code), list()) + q.add_param(keys.USER_ID, user_id) + + return q + + +# required scope: none +# requires app access token +@query +def redeem_code(code, user_id): + q = Qry('entitlements/codes', use_app_token=True, method=methods.POST) + q.add_param(keys.CODE, ItemCount(max_items=20).validate(code), list()) + q.add_param(keys.USER_ID, user_id) + + return q diff --git a/resources/lib/twitch/api/helix/games.py b/resources/lib/twitch/api/helix/games.py new file mode 100644 index 0000000..19238fc --- /dev/null +++ b/resources/lib/twitch/api/helix/games.py @@ -0,0 +1,114 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ... import keys +from ...api.parameters import Cursor, IntRange, ItemCount +from ...queries import HelixQuery as Qry +from ...queries import GQLQuery as GQLQry +from ...queries import HiddenApiQuery as HQry +from ...queries import query + + +# required scope: none +@query +def get_games(game_id=list(), game_name=list(), use_app_token=False): + q = Qry('games', use_app_token=use_app_token) + q.add_param(keys.ID, ItemCount().validate(game_id), list()) + q.add_param(keys.NAME, ItemCount().validate(game_name), list()) + + return q + + +# required scope: none +@query +def get_top(after='MA==', before='MA==', first=20, use_app_token=False): + q = Qry('games/top', use_app_token=use_app_token) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q + + +# required scope: none +# undocumented / unsupported +@query +def _check_follows(username, name, headers={}): + q = HQry('users/{username}/follows/games/isFollowing', headers=headers, use_token=False) + q.add_urlkw(keys.USERNAME, username) + q.add_param(keys.NAME, name) + return q + + +# required scope: none +# undocumented / unsupported +@query +def _get_followed(limit=100, headers={}): + data = [{ + "operationName": "FollowingGames_CurrentUser", + "variables": { + "limit": limit, + "type": "LIVE" + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "8446d4d234005813dc1f024f487ce95434c3e4202f451dd42777935b5ed035ce" + } + } + }] + q = GQLQry('', headers=headers, data=data, use_token=False) + return q + + +# required scope: user_follows_edit +# undocumented / unsupported +@query +def _follow(game_id, headers={}): + data = [{ + "operationName": "FollowGameButton_FollowGame", + "variables": { + "input": { + "gameID": str(game_id) + } + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "b846b65ba4bc9a3561dbe2d069d95deed9b9e031bcfda2482d1bedd84a1c2eb3" + } + } + }] + q = GQLQry('', headers=headers, data=data, use_token=False) + return q + + +# required scope: user_follows_edit +# undocumented / unsupported +@query +def _unfollow(game_id, headers={}): + data = [{ + "operationName": "FollowGameButton_UnfollowGame", + "variables": { + "input": { + "gameID": str(game_id) + } + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "811e02e396ebba0664f21ff002f2eff3c6f57e8af9aedb4f4dfa77cefd0db43d" + } + } + }] + q = GQLQry('', headers=headers, data=data, use_token=False) + return q diff --git a/resources/lib/twitch/api/helix/search.py b/resources/lib/twitch/api/helix/search.py new file mode 100644 index 0000000..7819e23 --- /dev/null +++ b/resources/lib/twitch/api/helix/search.py @@ -0,0 +1,39 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2022- script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ... import keys +from ...api.parameters import Boolean, Cursor, IntRange +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +@query +def get_categories(search_query, after='MA==', first=20, use_app_token=False): + q = Qry('search/categories', use_app_token=use_app_token) + q.add_param(keys.QUERY, search_query) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q + + +# required scope: none +@query +def get_channels(search_query, after='MA==', first=20, live_only=True, use_app_token=False): + q = Qry('search/channels', use_app_token=use_app_token) + q.add_param(keys.QUERY, search_query) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.LIVE_ONLY, Boolean.validate(live_only), Boolean.FALSE) + + return q diff --git a/resources/lib/twitch/api/helix/streams.py b/resources/lib/twitch/api/helix/streams.py new file mode 100644 index 0000000..b3b5268 --- /dev/null +++ b/resources/lib/twitch/api/helix/streams.py @@ -0,0 +1,93 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ... import keys +from ...api.parameters import Cursor, Language, IntRange, ItemCount +from ... import methods +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +@query +def get_streams(game_id=list(), user_id=list(), + user_login=list(), language=list(), after='MA==', + before='MA==', first=20, use_app_token=False): + q = Qry('streams', use_app_token=use_app_token) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.GAME_ID, ItemCount().validate(game_id), list()) + q.add_param(keys.USER_ID, ItemCount().validate(user_id), list()) + q.add_param(keys.USER_LOGIN, ItemCount().validate(user_login), list()) + if isinstance(language, list): + _language = [lang for lang in language if lang in Language.valid()] + q.add_param(keys.LANGUAGE, ItemCount().validate(_language), list()) + else: + q.add_param(keys.LANGUAGE, Language.validate(language), '') + + return q + + +# required scope: user:read:follows +@query +def get_followed(user_id, after='MA==', first=20, use_app_token=False): + q = Qry('streams/followed', use_app_token=use_app_token) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.USER_ID, user_id) + + return q + + +# required scope: none +@query +def get_metadata(game_id=list(), user_id=list(), + user_login=list(), language=list(), after='MA==', + before='MA==', first=20, use_app_token=False): + q = Qry('streams/metadata', use_app_token=use_app_token) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.GAME_ID, ItemCount().validate(game_id), list()) + q.add_param(keys.USER_ID, ItemCount().validate(user_id), list()) + q.add_param(keys.USER_LOGIN, ItemCount().validate(user_login), list()) + if isinstance(language, list): + _language = [lang for lang in language if lang in Language.valid()] + q.add_param(keys.LANGUAGE, ItemCount().validate(_language), list()) + else: + q.add_param(keys.LANGUAGE, Language.validate(language), '') + + return q + + +# required scope: user:edit:broadcast +@query +def create_stream_marker(user_id, description=''): + q = Qry('streams/markers', use_app_token=False, method=methods.POST) + q.add_param(keys.USER_ID, user_id) + q.add_param(keys.DESCRIPTION, description, '') + + return q + + +# required scope: user:read:broadcast +@query +def get_stream_markers(user_id, video_id, after='MA==', before='MA==', first=20): + q = Qry('streams/markers', use_app_token=False, method=methods.GET) + q.add_param(keys.USER_ID, user_id) + q.add_param(keys.VIDEO_ID, video_id) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q diff --git a/resources/lib/twitch/api/helix/subscriptions.py b/resources/lib/twitch/api/helix/subscriptions.py new file mode 100644 index 0000000..5b23843 --- /dev/null +++ b/resources/lib/twitch/api/helix/subscriptions.py @@ -0,0 +1,37 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import Cursor, IntRange, ItemCount +from ... import keys, methods +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: channel:read:subscriptions +@query +def get_broadcaster_subscriptions(broadcaster_id, user_id=list(), after='MA==', first=20): + q = Qry('subscriptions', use_app_token=False, method=methods.GET) + q.add_param(keys.BROADCASTER_ID, broadcaster_id) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.USER_ID, ItemCount().validate(user_id), list()) + return q + + +# required scope: channel:read:subscriptions +@query +def get_user_subscriptions(broadcaster_id, user_id): + q = Qry('subscriptions/user', use_app_token=False, method=methods.GET) + q.add_param(keys.BROADCASTER_ID, broadcaster_id) + q.add_param(keys.USER_ID, ItemCount().validate(user_id), list()) + + return q diff --git a/resources/lib/twitch/api/helix/tags.py b/resources/lib/twitch/api/helix/tags.py new file mode 100644 index 0000000..aa9d022 --- /dev/null +++ b/resources/lib/twitch/api/helix/tags.py @@ -0,0 +1,48 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import Cursor, IntRange, ItemCount +from ... import keys, methods +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +# requires app access token +@query +def get_all_stream_tags(tag_id, after='MA==', first=20): + q = Qry('tags/streams', use_app_token=True, method=methods.GET) + q.add_param(keys.TAG_ID, ItemCount().validate(tag_id), list()) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q + + +# required scope: none +# requires app access token +@query +def get_stream_tags(broadcaster_id): + q = Qry('streams/tags', use_app_token=True, method=methods.GET) + q.add_param(keys.BROADCASTER_ID, broadcaster_id) + + return q + + +# required scope: user:edit:broadcast +@query +def replace_stream_tags(broadcaster_id, tag_ids=list()): + q = Qry('tags/streams', use_app_token=False, method=methods.PUT) + q.add_param(keys.BROADCASTER_ID, broadcaster_id) + q.add_param(keys.TAG_IDS, ItemCount().validate(tag_ids), list()) + + return q diff --git a/resources/lib/twitch/api/helix/users.py b/resources/lib/twitch/api/helix/users.py new file mode 100644 index 0000000..4da035d --- /dev/null +++ b/resources/lib/twitch/api/helix/users.py @@ -0,0 +1,117 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ... import keys, methods +from ...api.parameters import Cursor, IntRange, ItemCount +from ...queries import GQLQuery as GQLQry +from ...queries import HelixQuery as Qry +from ...queries import query + + +# optional scope: user:read:email +@query +def get_users(user_id=list(), user_login=list(), use_app_token=False): + use_token = (not user_id and not user_login) + use_app_token = False if use_token else use_app_token + q = Qry('users', use_app_token=use_app_token) + q.add_param(keys.ID, ItemCount().validate(user_id), list()) + q.add_param(keys.LOGIN, ItemCount().validate(user_login), list()) + + return q + + +# required scope: none +@query +def get_follows(user_id='', after='MA==', first=20, use_app_token=False): + q = Qry('channels/followed', use_app_token=use_app_token) + q.add_param(keys.USER_ID, user_id, '') + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q + + +# required scope: user:edit +@query +def put_users(description): + q = Qry('users', method=methods.PUT) + q.add_param(keys.DESCRIPTION, description, '') + + return q + + +# required scope: user:read:broadcast +@query +def get_extensions(): + q = Qry('users/extensions/list') + + return q + + +# optional scope: user:read:broadcast or user:edit:broadcast +@query +def get_active_extensions(user_id=''): + q = Qry('users/extensions') + q.add_param(keys.USER_ID, user_id, '') + + return q + + +# required scope: user:edit:broadcast +@query +def update_extensions(): + q = Qry('users/extensions', method=methods.PUT) + + return q + + +# required scope: user_follows_edit +@query +def _follow_channel(channel_id, headers={}, notifications=False): + data = [{ + "operationName": "FollowButton_FollowUser", + "variables": { + "input": { + "disableNotifications": notifications, + "targetID": str(channel_id) + } + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "14319edb840c1dfce880dc64fa28a1f4eb69d821901e9e96eb9610d2e52b54f2" + } + } + }] + q = GQLQry('', headers=headers, data=data, use_token=False) + return q + + +# required scope: user_follows_edit +@query +def _unfollow_channel(channel_id, headers={}): + data = [{ + "operationName": "FollowButton_UnfollowUser", + "variables": { + "input": { + "targetID": str(channel_id) + } + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "29783a1dac24124e02f7295526241a9f1476cd2f5ce1e394f93ea50c253d8628" + } + } + }] + q = GQLQry('', headers=headers, data=data, use_token=False) + return q diff --git a/resources/lib/twitch/api/helix/videos.py b/resources/lib/twitch/api/helix/videos.py new file mode 100644 index 0000000..cf3c3be --- /dev/null +++ b/resources/lib/twitch/api/helix/videos.py @@ -0,0 +1,50 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ... import keys +from ...api.parameters import Cursor, Language, BroadcastTypeHelix, VideoSortHelix, PeriodHelix, IntRange, ItemCount +from ...queries import HelixQuery as Qry +from ...queries import HiddenApiQuery as HQry +from ...queries import query + + +# required scope: none +@query +def get_videos(video_id=list(), game_id='', user_id='', + broadcast_type=BroadcastTypeHelix.ALL, language='', + after='MA==', before='MA==', first=20, + sort_order=VideoSortHelix.TIME, period=PeriodHelix.ALL, use_app_token=False): + q = Qry('videos', use_app_token=use_app_token) + if not video_id: + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.BEFORE, Cursor.validate(before), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + q.add_param(keys.GAME_ID, game_id, '') + q.add_param(keys.USER_ID, user_id, '') + q.add_param(keys.TYPE, BroadcastTypeHelix.validate(broadcast_type), BroadcastTypeHelix.ALL) + q.add_param(keys.SORT, VideoSortHelix.validate(sort_order), VideoSortHelix.TIME) + q.add_param(keys.PERIOD, PeriodHelix.validate(period), PeriodHelix.ALL) + if language: + q.add_param(keys.LANGUAGE, Language.validate(language), '') + else: + q.add_param(keys.ID, ItemCount().validate(video_id), list()) + + return q + + +# required scope: none +# undocumented / unsupported +@query +def _by_id(video_id, headers={}): + q = HQry('videos/{video_id}', headers=headers, use_token=False) + q.add_urlkw(keys.VIDEO_ID, video_id) + return q diff --git a/resources/lib/twitch/api/helix/webhooks.py b/resources/lib/twitch/api/helix/webhooks.py new file mode 100644 index 0000000..bfe9249 --- /dev/null +++ b/resources/lib/twitch/api/helix/webhooks.py @@ -0,0 +1,27 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/api/reference + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..parameters import Cursor, IntRange +from ... import keys +from ...queries import HelixQuery as Qry +from ...queries import query + + +# required scope: none +# requires app access token +@query +def subscriptions(after='MA==', first=20): + q = Qry('webhooks/subscriptions', use_app_token=True) + q.add_param(keys.AFTER, Cursor.validate(after), 'MA==') + q.add_param(keys.FIRST, IntRange(1, 100).validate(first), 20) + + return q diff --git a/resources/lib/twitch/api/parameters.py b/resources/lib/twitch/api/parameters.py index 4c29cec..d5be526 100644 --- a/resources/lib/twitch/api/parameters.py +++ b/resources/lib/twitch/api/parameters.py @@ -1,4 +1,15 @@ # -*- encoding: utf-8 -*- +""" + + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + from base64 import b64decode @@ -23,6 +34,14 @@ class Period(_Parameter): _valid = [WEEK, MONTH, ALL] +class PeriodHelix(_Parameter): + DAY = 'day' + WEEK = 'week' + MONTH = 'month' + ALL = 'all' + _valid = [DAY, WEEK, MONTH, ALL] + + class ClipPeriod(_Parameter): DAY = 'day' WEEK = 'week' @@ -60,6 +79,14 @@ class VideoSort(_Parameter): _valid = [VIEWS, TIME] +class VideoSortHelix(_Parameter): + VIEWS = 'views' + TIME = 'time' + TRENDING = 'trending' + + _valid = [VIEWS, TIME, TRENDING] + + class BroadcastType(_Parameter): ARCHIVE = 'archive' HIGHLIGHT = 'highlight' @@ -76,6 +103,23 @@ def validate(cls, value): return value +class BroadcastTypeHelix(_Parameter): + ARCHIVE = 'archive' + HIGHLIGHT = 'highlight' + UPLOAD = 'upload' + ALL = 'all' + + _valid = [ALL, ARCHIVE, HIGHLIGHT, UPLOAD] + + @classmethod + def validate(cls, value): + split_values = value.split(',') + for val in split_values: + if val not in cls._valid: + raise ValueError(value) + return value + + class StreamType(_Parameter): LIVE = 'live' PLAYLIST = 'playlist' @@ -84,6 +128,14 @@ class StreamType(_Parameter): _valid = [LIVE, PLAYLIST, ALL] +class StreamTypeHelix(_Parameter): + LIVE = 'live' + VODCAST = 'vodcast' + ALL = 'all' + + _valid = [LIVE, VODCAST, ALL] + + class Platform(_Parameter): XBOX_ONE = 'xbox_one' PS4 = 'ps4' @@ -105,7 +157,7 @@ def validate(cls, value): class Language(_Parameter): - ALL = 'all' + ALL = '' EN = 'en' DA = 'da' DE = 'de' @@ -141,12 +193,48 @@ class Language(_Parameter): @classmethod def validate(cls, value): - split_values = value.split(',') - for val in split_values: - if val not in cls._valid: - raise ValueError(value) + if value not in cls._valid: + raise ValueError(value) return value class Duration(_Parameter): _valid = [30, 60, 90, 120, 150, 180] + + +class ReportType(_Parameter): + OVERVIEW_V1 = 'overview_v1' + OVERVIEW_V2 = 'overview_v2' + + _valid = [OVERVIEW_V1, OVERVIEW_V2] + + +class EntitlementType(_Parameter): + BULK_DROPS_GRANT = 'bulk_drops_grant' + + _valid = [BULK_DROPS_GRANT] + + +class IntRange(_Parameter): + + @classmethod + def __init__(cls, first, last): + cls._valid = [i for i in range(first, last + 1)] + + +class ItemCount(object): + _max_items = 100 + + @classmethod + def __init__(cls, max_items=100): + cls._max_items = max_items + + @classmethod + def valid(cls): + raise NotImplementedError + + @classmethod + def validate(cls, value): + if len(value) <= cls._max_items: + return value + raise ValueError(value) diff --git a/resources/lib/twitch/api/usher.py b/resources/lib/twitch/api/usher.py index 28656cc..b03edff 100644 --- a/resources/lib/twitch/api/usher.py +++ b/resources/lib/twitch/api/usher.py @@ -1,29 +1,83 @@ # -*- encoding: utf-8 -*- +# By using this module you are violating the Twitch TOS +""" -from twitch.logging import log # NOQA + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch -log.warning('By using this module you are violating the Twitch TOS') # NOQA + This file is part of script.module.python.twitch -from twitch import keys -from twitch.api.parameters import Boolean -from twitch.parser import m3u8, clip_embed -from twitch.queries import ClipsQuery, HiddenApiQuery, UsherQuery -from twitch.queries import query + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +import json +from urllib.parse import urlencode + +from .. import keys +from ..api.parameters import Boolean +from ..parser import m3u8, clip_embed +from ..queries import ClipsQuery, HiddenApiQuery, UsherQuery, GQLQuery +from ..queries import query +from ..log import log + +ACCESS_TOKEN_EXCEPTION = { + 'error': 'Error', + 'message': 'Failed to retrieve access token', + 'status': 404 +} + + +def get_access_token(token): + stream_access_token = None + video_access_token = None + if isinstance(token, list): + if token: + data = token[0].get(keys.DATA, {}) + stream_access_token = data.get(keys.STREAM_PLAYBACK_ACCESS_TOKEN) + video_access_token = data.get(keys.VIDEO_PLAYBACK_ACCESS_TOKEN) + return stream_access_token or video_access_token or token + + +def valid_video_id(video_id): + if video_id.startswith('videos'): + video_id = 'v' + video_id[6:] + if video_id.startswith(('a', 'c', 'v')): + return video_id[1:] + return video_id @query -def channel_token(channel): - q = HiddenApiQuery('channels/{channel}/access_token') - q.add_urlkw(keys.CHANNEL, channel) - q.add_param(keys.NEED_HTTPS, Boolean.TRUE) +def channel_token(channel, platform=keys.WEB, headers={}): + data = [{ + "operationName": "PlaybackAccessToken_Template", + "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "variables": { + "isLive": True, + "login": channel, + "isVod": False, + "vodID": "", + "playerType": "site" + } + }] + q = GQLQuery('', headers=headers, data=data, use_token=True) return q @query -def vod_token(video_id): - q = HiddenApiQuery('vods/{vod}/access_token') - q.add_urlkw(keys.VOD, video_id) - q.add_param(keys.NEED_HTTPS, Boolean.TRUE) +def vod_token(video_id, platform=keys.WEB, headers={}): + data = [{ + "operationName": "PlaybackAccessToken_Template", + "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "variables": { + "isLive": False, + "login": "", + "isVod": True, + "vodID": video_id, + "playerType": "site" + } + }] + q = GQLQuery('', headers=headers, data=data, use_token=True) return q @@ -34,63 +88,164 @@ def _legacy_video(video_id): return q +def live_request(channel, platform=keys.WEB, headers={}): + token = channel_token(channel, platform=platform, headers=headers) + token = get_access_token(token) + + if not token: + return ACCESS_TOKEN_EXCEPTION + elif isinstance(token, dict) and 'error' in token: + return token + else: + signature = token[keys.SIGNATURE] + access_token = token[keys.VALUE] + q = UsherQuery('api/channel/hls/{channel}.m3u8', headers=headers) + q.add_urlkw(keys.CHANNEL, channel) + q.add_param(keys.SIG, signature.encode('utf-8')) + q.add_param(keys.TOKEN, access_token.encode('utf-8')) + q.add_param(keys.ALLOW_SOURCE, Boolean.TRUE) + q.add_param(keys.ALLOW_SPECTRE, Boolean.TRUE) + q.add_param(keys.ALLOW_AUDIO_ONLY, Boolean.TRUE) + q.add_param(keys.FAST_BREAD, Boolean.TRUE) + q.add_param(keys.CDM, keys.WV) + q.add_param(keys.REASSIGNMENT_SUPPORTED, Boolean.TRUE) + q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) + q.add_param(keys.RTQOS, keys.CONTROL) + q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) + url = '?'.join([q.url, urlencode(q.params)]) + request_dict = { + 'url': url, + 'headers': q.headers + } + log.debug('live_request: |{0}|'.format(str(request_dict))) + return request_dict + + @query -def _live(channel, token): - q = UsherQuery('api/channel/hls/{channel}.m3u8') +def _live(channel, token, headers={}): + signature = token[keys.SIGNATURE] + access_token = token[keys.VALUE] + + q = UsherQuery('api/channel/hls/{channel}.m3u8', headers=headers) q.add_urlkw(keys.CHANNEL, channel) - q.add_param(keys.SIG, token[keys.SIG]) - q.add_param(keys.TOKEN, token[keys.TOKEN]) + q.add_param(keys.SIG, signature.encode('utf-8')) + q.add_param(keys.TOKEN, access_token.encode('utf-8')) q.add_param(keys.ALLOW_SOURCE, Boolean.TRUE) q.add_param(keys.ALLOW_SPECTRE, Boolean.TRUE) q.add_param(keys.ALLOW_AUDIO_ONLY, Boolean.TRUE) + q.add_param(keys.FAST_BREAD, Boolean.TRUE) + q.add_param(keys.CDM, keys.WV) + q.add_param(keys.REASSIGNMENT_SUPPORTED, Boolean.TRUE) + q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) + q.add_param(keys.RTQOS, keys.CONTROL) + q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) return q @m3u8 -def live(channel): - token = channel_token(channel) - if keys.ERROR in token: +def live(channel, platform=keys.WEB, headers={}): + token = channel_token(channel, platform=platform, headers=headers) + token = get_access_token(token) + if not token: + return ACCESS_TOKEN_EXCEPTION + elif isinstance(token, dict) and 'error' in token: return token else: - return _live(channel, token) + return _live(channel, token, headers=headers) + + +def video_request(video_id, platform=keys.WEB, headers={}): + video_id = valid_video_id(video_id) + if video_id: + token = vod_token(video_id, platform=platform, headers=headers) + token = get_access_token(token) + + if not token: + return ACCESS_TOKEN_EXCEPTION + elif isinstance(token, dict) and 'error' in token: + return token + else: + signature = token[keys.SIGNATURE] + access_token = token[keys.VALUE] + q = UsherQuery('vod/{id}', headers=headers) + q.add_urlkw(keys.ID, video_id) + q.add_param(keys.NAUTHSIG, signature.encode('utf-8')) + q.add_param(keys.NAUTH, access_token.encode('utf-8')) + q.add_param(keys.ALLOW_SOURCE, Boolean.TRUE) + q.add_param(keys.ALLOW_AUDIO_ONLY, Boolean.TRUE) + q.add_param(keys.CDM, keys.WV) + q.add_param(keys.REASSIGNMENT_SUPPORTED, Boolean.TRUE) + q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) + q.add_param(keys.RTQOS, keys.CONTROL) + q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) + q.add_param(keys.BAKING_BREAD, Boolean.TRUE) + q.add_param(keys.BAKING_BROWNIES, Boolean.TRUE) + q.add_param(keys.BAKING_BROWNIES_TIMEOUT, 1050) + url = '?'.join([q.url, urlencode(q.params)]) + request_dict = { + 'url': url, + 'headers': q.headers + } + log.debug('video_request: |{0}|'.format(str(request_dict))) + return request_dict + else: + raise NotImplementedError('Unknown Video Type') -@m3u8 @query -def _vod(video_id, token): - q = UsherQuery('vod/{id}') +def _vod(video_id, token, headers={}): + signature = token[keys.SIGNATURE] + access_token = token[keys.VALUE] + + q = UsherQuery('vod/{id}', headers=headers) q.add_urlkw(keys.ID, video_id) - q.add_param(keys.NAUTHSIG, token[keys.SIG]) - q.add_param(keys.NAUTH, token[keys.TOKEN]) + q.add_param(keys.NAUTHSIG, signature.encode('utf-8')) + q.add_param(keys.NAUTH, access_token.encode('utf-8')) q.add_param(keys.ALLOW_SOURCE, Boolean.TRUE) q.add_param(keys.ALLOW_AUDIO_ONLY, Boolean.TRUE) + q.add_param(keys.CDM, keys.WV) + q.add_param(keys.REASSIGNMENT_SUPPORTED, Boolean.TRUE) + q.add_param(keys.PLAYLIST_INCLUDE_FRAMERATE, Boolean.TRUE) + q.add_param(keys.RTQOS, keys.CONTROL) + q.add_param(keys.PLAYER_BACKEND, keys.MEDIAPLAYER) + q.add_param(keys.BAKING_BREAD, Boolean.TRUE) + q.add_param(keys.BAKING_BROWNIES, Boolean.TRUE) + q.add_param(keys.BAKING_BROWNIES_TIMEOUT, 1050) return q -def video(video_id): - if video_id.startswith('videos') or video_id.startswith('v'): - if video_id.startswith('videos'): - video_id = 'v' + video_id[6:] - video_id = video_id[1:] - token = vod_token(video_id) - if keys.ERROR in token: - return token - else: - return _vod(video_id, token) - elif video_id.startswith(('a', 'c')): - video_id = video_id[1:] - token = vod_token(video_id) - if keys.ERROR in token: +@m3u8 +def video(video_id, platform=keys.WEB, headers={}): + video_id = valid_video_id(video_id) + if video_id: + token = vod_token(video_id, platform=platform, headers=headers) + token = get_access_token(token) + + if not token: + return ACCESS_TOKEN_EXCEPTION + elif isinstance(token, dict) and 'error' in token: return token else: - return _vod(video_id, token) + return _vod(video_id, token, headers=headers) else: raise NotImplementedError('Unknown Video Type') @clip_embed @query -def clip(slug): - q = ClipsQuery('embed') - q.add_param(keys.CLIP, slug) +def clip(slug, headers={}): + qry = { + "operationName": "VideoAccessToken_Clip", + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "36b89d2507fce29e5ca551df756d27c1cfe079e2609642b4390aa4c35796eb11" + } + }, + "variables": { + "slug": slug + } + } + + q = ClipsQuery(headers=headers, data=json.dumps(qry)) return q diff --git a/resources/lib/twitch/api/v5/__init__.py b/resources/lib/twitch/api/v5/__init__.py deleted file mode 100644 index 24e2477..0000000 --- a/resources/lib/twitch/api/v5/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/ -# V5 is deprecated and will be removed entirely on 2/14/18 - -from twitch.api.v5 import bits # NOQA -from twitch.api.v5 import channel_feed # NOQA -from twitch.api.v5 import channels # NOQA -from twitch.api.v5 import chat # NOQA -from twitch.api.v5 import clips # NOQA -from twitch.api.v5 import collections # NOQA -from twitch.api.v5 import communities # NOQA -from twitch.api.v5 import games # NOQA -from twitch.api.v5 import ingests # NOQA -from twitch.api.v5.root import root # NOQA -from twitch.api.v5 import search # NOQA -from twitch.api.v5 import streams # NOQA -from twitch.api.v5 import teams # NOQA -from twitch.api.v5 import users # NOQA -from twitch.api.v5 import videos # NOQA diff --git a/resources/lib/twitch/api/v5/bits.py b/resources/lib/twitch/api/v5/bits.py deleted file mode 100644 index 58d2d78..0000000 --- a/resources/lib/twitch/api/v5/bits.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/bits/ - -from twitch import keys -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: None -@query -def get_cheermotes(channel_id=None): - q = Qry('bits/actions', use_token=False) - q.add_param(keys.CHANNEL_ID, channel_id, None) - return q diff --git a/resources/lib/twitch/api/v5/channel_feed.py b/resources/lib/twitch/api/v5/channel_feed.py deleted file mode 100644 index 23e999f..0000000 --- a/resources/lib/twitch/api/v5/channel_feed.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/channel-feed/ - -from twitch import keys, methods -from twitch.api.parameters import Boolean, Cursor -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: any/none -@query -def get_posts(channel_id, limit=10, cursor='MA==', comments=5): - q = Qry('feed/{channel_id}/posts') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - q.add_param(keys.COMMENTS, comments, 5) - return q - - -# required scope: any/none -@query -def get_post(channel_id, post_id, comments=5): - q = Qry('feed/{channel_id}/posts/{post_id}') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_param(keys.COMMENTS, comments, 5) - return q - - -# required scope: channel_feed_edit -@query -def create_post(channel_id, content, share=Boolean.FALSE): - q = Qry('feed/{channel_id}/posts/', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.SHARE, Boolean.validate(share)) - q.add_data(keys.CONTENT, content) - return q - - -# required scope: channel_feed_edit -@query -def delete_post(channel_id, post_id): - q = Qry('feed/{channel_id}/posts/{post_id}', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - return q - - -# required scope: channel_feed_edit -@query -def create_post_reaction(channel_id, post_id, emote_id): - q = Qry('feed/{channel_id}/posts/{post_id}/reactions', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_param(keys.EMOTE_ID, emote_id) - return q - - -# required scope: channel_feed_edit -@query -def delete_post_reaction(channel_id, post_id, emote_id): - q = Qry('feed/{channel_id}/posts/{post_id}/reactions', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_param(keys.EMOTE_ID, emote_id) - return q - - -# required scope: any/none -@query -def get_comments(channel_id, post_id, limit=10, cursor='MA=='): - q = Qry('feed/{channel_id}/posts/{post_id}/comments') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q - - -# required scope: channel_feed_edit -@query -def comment(channel_id, post_id, content): - q = Qry('feed/{channel_id}/posts/{post_id}/comments', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_data(keys.CONTENT, content) - return q - - -# required scope: channel_feed_edit -@query -def delete_comment(channel_id, post_id, comment_id): - q = Qry('feed/{channel_id}/posts/{post_id}/comments/{comment_id}', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_urlkw(keys.COMMENT_ID, comment_id) - return q - - -# required scope: channel_feed_edit -@query -def create_comment_reaction(channel_id, post_id, comment_id, emote_id): - q = Qry('feed/{channel_id}/posts/{post_id}/comments/{comment_id}/reactions', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_urlkw(keys.COMMENT_ID, comment_id) - q.add_param(keys.EMOTE_ID, emote_id) - return q - - -# required scope: channel_feed_edit -@query -def delete_comment_reaction(channel_id, post_id, comment_id, emote_id): - q = Qry('feed/{channel_id}/posts/{post_id}/comments/{comment_id}/reactions', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.POST_ID, post_id) - q.add_urlkw(keys.COMMENT_ID, comment_id) - q.add_param(keys.EMOTE_ID, emote_id) - return q diff --git a/resources/lib/twitch/api/v5/channels.py b/resources/lib/twitch/api/v5/channels.py deleted file mode 100644 index caaf103..0000000 --- a/resources/lib/twitch/api/v5/channels.py +++ /dev/null @@ -1,167 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/channels/ - -from twitch import keys, methods -from twitch.api.parameters import Boolean, BroadcastType, Cursor, Direction, Duration, Language, VideoSort -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: channel_read -@query -def channel(): - q = Qry('channels') - return q - - -# required scope: none -@query -def by_id(channel_id): - q = Qry('channels/{channel_id}', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: channel_editor -@query -def update(channel_id, status=None, game=None, delay=None, channel_feed_enabled=None): - q = Qry('channels/{channel_id}', method=methods.PUT) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - channel = {} - if status is not None: - channel[keys.STATUS] = status - if game is not None: - channel[keys.GAME] = game - if delay is not None: - channel[keys.DELAY] = delay - if channel_feed_enabled is not None: - channel[keys.CHANNEL_FEED_ENABLED] = Boolean.validate(channel_feed_enabled) - q.add_data(keys.CHANNEL, channel) - return q - - -# required scope: channel_read -@query -def get_editors(channel_id): - q = Qry('channels/{channel_id}/editors') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: none -@query -def get_followers(channel_id, limit=25, offset=0, cursor='MA==', direction=Direction.DESC): - q = Qry('channels/{channel_id}/follows', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - q.add_param(keys.DIRECTION, direction, Direction.DESC) - return q - - -# required scope: none -@query -def get_teams(channel_id): - q = Qry('channels/{channel_id}/teams', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: channel_subscriptions -@query -def get_subscribers(channel_id, limit=25, offset=0, direction=Direction.ASC): - q = Qry('channels/{channel_id}/subscriptions') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.DIRECTION, direction, Direction.DESC) - return q - - -# required scope: channel_check_subscription -@query -def check_subscription(channel_id, user_id): - q = Qry('channels/{channel_id}/subscriptions/{user_id}') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: none -@query -def get_videos(channel_id, limit=10, offset=0, - broadcast_type=BroadcastType.HIGHLIGHT, - hls=Boolean.FALSE, sort_by=VideoSort.TIME, - language=Language.ALL): - q = Qry('channels/{id}/videos', use_token=False) - q.add_urlkw(keys.ID, channel_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.BROADCAST_TYPE, BroadcastType.validate(broadcast_type)) - q.add_param(keys.SORT, VideoSort.validate(sort_by), VideoSort.TIME) - q.add_param(keys.LANGUAGE, Language.validate(language), Language.ALL) - q.add_param(keys.HLS, Boolean.validate(hls), Boolean.FALSE) - return q - - -# required scope: channel_commercial -@query -def start_commercial(channel_id, duration=30): - q = Qry('channels/{channel_id}/commercial', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_data(keys.DURATION, Duration.validate(duration)) - return q - - -# required scope: channel_stream -@query -def reset_stream_key(channel_id): - q = Qry('channels/{channel_id}/stream_key', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: channel_editor -# deprecated -@query -def get_community(channel_id): - q = Qry('channels/{channel_id}/community') - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: none -@query -def get_communities(channel_id): - q = Qry('channels/{channel_id}/communities', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: channel_editor -# deprecated -@query -def set_community(channel_id, community_id): - q = Qry('channels/{channel_id}/community/{community_id}', method=methods.PUT) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: channel_editor -@query -def set_communities(channel_id, community_ids): - q = Qry('channels/{channel_id}/communities', method=methods.PUT) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_data(keys.COMMUNITY_IDS, community_ids) - return q - - -# required scope: channel_editor -# deprecated action: act on single community, new action: act on all communities -@query -def delete_from_community(channel_id): - q = Qry('channels/{channel_id}/community', method=methods.DELETE) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q diff --git a/resources/lib/twitch/api/v5/chat.py b/resources/lib/twitch/api/v5/chat.py deleted file mode 100644 index 6098fc3..0000000 --- a/resources/lib/twitch/api/v5/chat.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/chat/ - -from twitch import keys -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def get_emoticons_by_set(emotesets=None): - q = Qry('chat/emoticon_images', use_token=False) - q.add_param(keys.EMOTESETS, emotesets, None) - return q - - -# required scope: none -@query -def get_badges(channel_id): - q = Qry('chat/{channel_id}/badges', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: none -@query -def get_emoticons(): - q = Qry('chat/emoticons', use_token=False) - return q diff --git a/resources/lib/twitch/api/v5/clips.py b/resources/lib/twitch/api/v5/clips.py deleted file mode 100644 index 0438100..0000000 --- a/resources/lib/twitch/api/v5/clips.py +++ /dev/null @@ -1,41 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/clips/ - -from twitch import keys -from twitch.api.parameters import Boolean, ClipPeriod, Cursor, Language -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: None -@query -def by_slug(slug): - q = Qry('clips/{slug}', use_token=False) - q.add_urlkw(keys.SLUG, slug) - return q - - -# required scope: None -@query -def get_top(channels=None, games=None, period=ClipPeriod.WEEK, trending=Boolean.FALSE, - language=Language.ALL, cursor='MA==', limit=10): - q = Qry('clips/top', use_token=False) - q.add_param(keys.CHANNEL, channels, None) - q.add_param(keys.GAME, games, None) - q.add_param(keys.PERIOD, ClipPeriod.validate(period), ClipPeriod.WEEK) - q.add_param(keys.TRENDING, Boolean.validate(trending), Boolean.FALSE) - q.add_param(keys.LANGUAGE, Language.validate(language), Language.ALL) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q - - -# required scope: user_read -@query -def get_followed(trending=Boolean.FALSE, language=Language.ALL, cursor='MA==', limit=10): - q = Qry('clips/followed') - q.add_param(keys.TRENDING, Boolean.validate(trending), Boolean.FALSE) - q.add_param(keys.LANGUAGE, Language.validate(language), Language.ALL) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q diff --git a/resources/lib/twitch/api/v5/collections.py b/resources/lib/twitch/api/v5/collections.py deleted file mode 100644 index f44878e..0000000 --- a/resources/lib/twitch/api/v5/collections.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/collections/ - -from twitch import keys, methods -from twitch.api.parameters import Boolean, Cursor -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def get_metadata(collection_id): - q = Qry('collections/{collection_id}', use_token=False) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - return q - - -# required scope: none -@query -def by_id(collection_id, include_all=Boolean.FALSE): - q = Qry('collections/{collection_id}/items', use_token=False) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_param(keys.INCLUDE_ALL_ITEMS, Boolean.validate(include_all), Boolean.FALSE) - return q - - -# required scope: none -@query -def get_collections(channel_id, limit=10, cursor='MA==', containing_item=None): - q = Qry('channels/{channel_id}/collections', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - q.add_param(keys.CONTAINING_ITEM, containing_item, None) # 'video:' - return q - - -# required scope: collections_edit -@query -def create(channel_id, title): - q = Qry('channels/{channel_id}/collections', method=methods.POST) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_data(keys.TITLE, title) - return q - - -# required scope: collections_edit -@query -def update(collection_id, title): - q = Qry('collections/{collection_id}', method=methods.PUT) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_data(keys.TITLE, title) - return q - - -# required scope: collections_edit -@query -def create_thumbnail(collection_id, item_id): - q = Qry('collections/{collection_id}/thumbnail', method=methods.PUT) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_data(keys.ITEM_ID, item_id) - return q - - -# required scope: collections_edit -@query -def delete(collection_id): - q = Qry('collections/{collection_id}', method=methods.DELETE) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - return q - - -# required scope: collections_edit -@query -def add_to_collection(collection_id, video_id): - q = Qry('collections/{collection_id}/items', method=methods.POST) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_data(keys.ID, video_id) - q.add_data(keys.TYPE, 'video') # must be 'video' - return q - - -# required scope: collections_edit -@query -def delete_from_collection(collection_id, item_id): - q = Qry('collections/{collection_id}/items/{item_id}', method=methods.DELETE) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_urlkw(keys.ITEM_ID, item_id) - return q - - -# required scope: collections_edit -@query -def move_in_collection(collection_id, item_id, position): - q = Qry('collections/{collection_id}/items/{item_id}', method=methods.PUT) - q.add_urlkw(keys.COLLECTION_ID, collection_id) - q.add_urlkw(keys.ITEM_ID, item_id) - q.add_data(keys.POSITION, position) - return q diff --git a/resources/lib/twitch/api/v5/communities.py b/resources/lib/twitch/api/v5/communities.py deleted file mode 100644 index bb537d4..0000000 --- a/resources/lib/twitch/api/v5/communities.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/communities/ - -from twitch import keys, methods -from twitch.api.parameters import Cursor -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def by_name(name): - q = Qry('communities', use_token=False) - q.add_param(keys.NAME, name) - return q - - -# required scope: none -@query -def by_id(community_id): - q = Qry('communities/{community_id}', use_token=False) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: communities_edit -@query -def update(community_id, summary=None, description=None, - rules=None, email=None): - q = Qry('communities/{community_id}', method=methods.PUT) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_data(keys.SUMMARY, summary) - q.add_data(keys.DESCRIPTION, description) - q.add_data(keys.RULES, rules) - q.add_data(keys.EMAIL, email) - return q - - -# required scope: none -@query -def get_top(limit=10, cursor='MA=='): - q = Qry('communities/top', use_token=False) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q - - -# required scope: communities_moderate -@query -def get_bans(community_id, limit=10, cursor='MA=='): - q = Qry('communities/{community_id}/bans') - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q - - -# required scope: communities_moderate -@query -def ban_user(community_id, user_id): - q = Qry('communities/{community_id}/bans/{user_id}', method=methods.PUT) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: communities_moderate -@query -def unban_user(community_id, user_id): - q = Qry('communities/{community_id}/bans/{user_id}', method=methods.DELETE) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: communities_edit -@query -def create_avatar(community_id, avatar_image): - q = Qry('communities/{community_id}/images/avatar', method=methods.POST) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.AVATAR_IMAGE, avatar_image) - return q - - -# required scope: communities_edit -@query -def delete_avatar(community_id): - q = Qry('communities/{community_id}/images/avatar', method=methods.DELETE) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: communities_edit -@query -def create_cover(community_id, cover_image): - q = Qry('communities/{community_id}/images/cover', method=methods.POST) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.COVER_IMAGE, cover_image) - return q - - -# required scope: communities_edit -@query -def delete_cover(community_id): - q = Qry('communities/{community_id}/images/cover', method=methods.DELETE) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: communities_edit -@query -def get_moderators(community_id): - q = Qry('communities/{community_id}/moderators') - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: communities_edit -@query -def add_moderator(community_id, user_id): - q = Qry('communities/{community_id}/moderators/{user_id}', method=methods.PUT) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: communities_edit -@query -def delete_moderator(community_id, user_id): - q = Qry('communities/{community_id}/moderators/{user_id}', method=methods.DELETE) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: any -@query -def get_permissions(community_id): - q = Qry('communities/{community_id}/permissions') - q.add_urlkw(keys.COMMUNITY_ID, community_id) - return q - - -# required scope: none -@query -def report_violation(community_id, channel_id): - q = Qry('communities/{community_id}/report_channel', use_token=False, method=methods.POST) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_data(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: communities_moderate -@query -def get_timeouts(community_id, limit=10, cursor='MA=='): - q = Qry('communities/{community_id}/timeouts') - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.CURSOR, Cursor.validate(cursor), 'MA==') - return q - - -# required scope: communities_moderate -@query -def add_timeout(community_id, user_id, duration=1, reason=None): - q = Qry('communities/{community_id}/timeouts/{user_id}', method=methods.PUT) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - q.add_data(keys.DURATION, duration) - q.add_data(keys.REASON, reason) - return q - - -# required scope: communities_moderate -@query -def delete_timeout(community_id, user_id): - q = Qry('communities/{community_id}/timeouts/{user_id}', method=methods.DELETE) - q.add_urlkw(keys.COMMUNITY_ID, community_id) - q.add_urlkw(keys.USER_ID, user_id) - return q diff --git a/resources/lib/twitch/api/v5/games.py b/resources/lib/twitch/api/v5/games.py deleted file mode 100644 index f29ab18..0000000 --- a/resources/lib/twitch/api/v5/games.py +++ /dev/null @@ -1,57 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/games/ - -from twitch import keys, methods -from twitch.queries import V5Query as Qry -from twitch.queries import HiddenApiQuery as HQry -from twitch.queries import query - - -# required scope: none -@query -def get_top(limit=10, offset=0): - q = Qry('games/top', use_token=False) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: none -# undocumented / unsupported -@query -def _check_follows(username, name): - q = HQry('users/{username}/follows/games/isFollowing', use_token=False) - q.add_urlkw(keys.USERNAME, username) - q.add_param(keys.NAME, name) - return q - - -# required scope: none -# undocumented / unsupported -@query -def _get_followed(username, limit=25, offset=0): - q = HQry('users/{username}/follows/games', use_token=False) - q.add_urlkw(keys.USERNAME, username) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: user_follows_edit -# undocumented / unsupported -@query -def _follow(username, name): - q = HQry('users/{username}/follows/games/follow', method=methods.PUT) - q.add_urlkw(keys.USERNAME, username) - q.add_data(keys.NAME, name) - return q - - -# required scope: user_follows_edit -# undocumented / unsupported -@query -def _unfollow(username, name): - q = HQry('users/{username}/follows/games/unfollow', method=methods.DELETE) - q.add_urlkw(keys.USERNAME, username) - q.add_data(keys.NAME, name) - return q diff --git a/resources/lib/twitch/api/v5/ingests.py b/resources/lib/twitch/api/v5/ingests.py deleted file mode 100644 index 61f9e53..0000000 --- a/resources/lib/twitch/api/v5/ingests.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/ingests/ - -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def ingests(): - q = Qry('ingests', use_token=False) - return q diff --git a/resources/lib/twitch/api/v5/root.py b/resources/lib/twitch/api/v5/root.py deleted file mode 100644 index 78beadd..0000000 --- a/resources/lib/twitch/api/v5/root.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/guides/using-the-twitch-api/ - -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: any -@query -def root(): - return Qry('') diff --git a/resources/lib/twitch/api/v5/search.py b/resources/lib/twitch/api/v5/search.py deleted file mode 100644 index 09f6701..0000000 --- a/resources/lib/twitch/api/v5/search.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/search/ - -from twitch import keys -from twitch.api.parameters import Boolean -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def channels(search_query, limit=25, offset=0): - q = Qry('search/channels', use_token=False) - q.add_param(keys.QUERY, search_query) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: none -@query -def games(search_query, live=Boolean.FALSE): - q = Qry('search/games', use_token=False) - q.add_param(keys.QUERY, search_query) - q.add_param(keys.TYPE, 'suggest') # 'type' can currently only be suggest, so it is hardcoded - - q.add_param(keys.LIVE, live, Boolean.FALSE) - return q - - -# required scope: none -@query -def streams(search_query, limit=25, offset=0, hls=Boolean.FALSE): - q = Qry('search/streams', use_token=False) - q.add_param(keys.QUERY, search_query) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.HLS, hls, Boolean.FALSE) - return q diff --git a/resources/lib/twitch/api/v5/streams.py b/resources/lib/twitch/api/v5/streams.py deleted file mode 100644 index 3b3e21c..0000000 --- a/resources/lib/twitch/api/v5/streams.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/streams/ - -from twitch import keys -from twitch.api.parameters import Boolean, StreamType, Language, Platform -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def by_id(channel_id, stream_type=StreamType.LIVE): - q = Qry('streams/{channel_id}', use_token=False) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_param(keys.STREAM_TYPE, StreamType.validate(stream_type), StreamType.LIVE) - return q - - -# required scope: none -# platform undocumented / unsupported -@query -def get_all(game=None, channel_ids=None, community_id=None, language=Language.ALL, - stream_type=StreamType.LIVE, platform=Platform.ALL, limit=25, offset=0): - q = Qry('streams', use_token=False) - q.add_param(keys.GAME, game) - q.add_param(keys.CHANNEL, channel_ids) - q.add_param(keys.COMMUNITY_ID, community_id) - q.add_param(keys.BROADCASTER_LANGUAGE, Language.validate(language), Language.ALL) - q.add_param(keys.STREAM_TYPE, StreamType.validate(stream_type), StreamType.LIVE) - platform = Platform.validate(platform) - if platform == Platform.XBOX_ONE: - q.add_param(keys.XBOX_HEARTBEAT, Boolean.TRUE) - elif platform == Platform.PS4: - q.add_param(keys.SCE_PLATFORM, 'PS4') - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: none -@query -def get_summary(game=None): - q = Qry('streams/summary', use_token=False) - q.add_param(keys.GAME, game) - return q - - -# required scope: none -@query -def get_featured(limit=25, offset=0): - q = Qry('streams/featured', use_token=False) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: user_read -@query -def get_followed(stream_type=StreamType.LIVE, limit=25, offset=0): - q = Qry('streams/followed') - q.add_param(keys.STREAM_TYPE, StreamType.validate(stream_type), StreamType.LIVE) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q diff --git a/resources/lib/twitch/api/v5/teams.py b/resources/lib/twitch/api/v5/teams.py deleted file mode 100644 index c883af0..0000000 --- a/resources/lib/twitch/api/v5/teams.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/teams/ - -from twitch import keys -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: none -@query -def get_active(limit=25, offset=0): - q = Qry('teams', use_token=False) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: none -@query -def by_name(name): - q = Qry('teams/{team}', use_token=False) - q.add_urlkw(keys.TEAM, name) - return q diff --git a/resources/lib/twitch/api/v5/users.py b/resources/lib/twitch/api/v5/users.py deleted file mode 100644 index a955c09..0000000 --- a/resources/lib/twitch/api/v5/users.py +++ /dev/null @@ -1,130 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/users/ - -from twitch import keys, methods -from twitch.api.parameters import Boolean, Direction, SortBy -from twitch.queries import V5Query as Qry -from twitch.queries import query - - -# required scope: user_read -@query -def user(): - q = Qry('user') - return q - - -# required scope: none -@query -def by_id(user_id): - q = Qry('users/{user_id}', use_token=False) - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: user_subscriptions -@query -def get_emotes(user_id): - q = Qry('users/{user_id}/emotes') - q.add_urlkw(keys.USER_ID, user_id) - return q - - -# required scope: user_subscriptions -@query -def check_subscription(user_id, channel_id): - q = Qry('users/{user_id}/subscriptions/{channel_id}') - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: none -@query -def get_follows(user_id, limit=25, offset=0, direction=Direction.DESC, - sort_by=SortBy.CREATED_AT): - q = Qry('users/{user_id}/follows/channels', use_token=False) - q.add_urlkw(keys.USER_ID, user_id) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.DIRECTION, direction, Direction.DESC) - q.add_param(keys.SORT_BY, sort_by, SortBy.CREATED_AT) - return q - - -# required scope: none -@query -def check_follows(user_id, channel_id): - q = Qry('users/{user_id}/follows/channels/{channel_id}', use_token=False) - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: user_follows_edit -@query -def follow_channel(user_id, channel_id, notifications=Boolean.FALSE): - q = Qry('users/{user_id}/follows/channels/{channel_id}', method=methods.PUT) - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - q.add_data(keys.NOTIFICATIONS, Boolean.validate(notifications), Boolean.FALSE) - return q - - -# required scope: user_follows_edit -@query -def unfollow_channel(user_id, channel_id): - q = Qry('users/{user_id}/follows/channels/{channel_id}', method=methods.DELETE) - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.CHANNEL_ID, channel_id) - return q - - -# required scope: user_blocks_read -@query -def get_blocks(user_id, limit=25, offset=0): - q = Qry('users/{user_id}/blocks') - q.add_urlkw(keys.USER_ID, user_id) - q.add_param(keys.LIMIT, limit, 25) - q.add_param(keys.OFFSET, offset, 0) - return q - - -# required scope: user_blocks_edit -@query -def block_user(user_id, target_id): - q = Qry('users/{user_id}/blocks/{target_id}', method=methods.PUT) - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.TARGET_ID, target_id) - return q - - -# required scope: user_blocks_edit -@query -def unblock_user(user_id, target_id): - q = Qry('users/{user_id}/blocks/{target_id}', method=methods.DELETE) - q.add_urlkw(keys.USER_ID, user_id) - q.add_urlkw(keys.TARGET_ID, target_id) - return q - - -# required scope: viewing_activity_read -@query -def create_connection_to_vhs(identifier): - q = Qry('user/vhs', method=methods.PUT) - q.add_data(keys.IDENTIFIER, identifier) - return q - - -# required scope: user_read -@query -def check_connection_to_vhs(): - q = Qry('user/vhs') - return q - - -# required scope: viewing_activity_read -@query -def delete_connection_to_vhs(): - q = Qry('user/vhs', method=methods.DELETE) - return q diff --git a/resources/lib/twitch/api/v5/videos.py b/resources/lib/twitch/api/v5/videos.py deleted file mode 100644 index 86a5524..0000000 --- a/resources/lib/twitch/api/v5/videos.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- encoding: utf-8 -*- -# https://dev.twitch.tv/docs/v5/reference/videos/ - -from twitch import keys, methods -from twitch.api.parameters import BroadcastType, Period, Language -from twitch.queries import V5Query as Qry -from twitch.queries import HiddenApiQuery as HQry -from twitch.queries import UploadsQuery as UQry -from twitch.queries import query - - -# required scope: none -@query -def by_id(video_id): - q = Qry('videos/{video_id}', use_token=False) - q.add_urlkw(keys.VIDEO_ID, video_id) - return q - - -# required scope: none -@query -def get_top(limit=10, offset=0, game=None, period=Period.WEEK, broadcast_type=BroadcastType.HIGHLIGHT): - q = Qry('videos/top', use_token=False) - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.GAME, game) - q.add_param(keys.PERIOD, Period.validate(period), Period.WEEK) - q.add_param(keys.BROADCAST_TYPE, BroadcastType.validate(broadcast_type)) - return q - - -# required scope: user_read -@query -def get_followed(limit=10, offset=0, broadcast_type=BroadcastType.HIGHLIGHT): - q = Qry('videos/followed') - q.add_param(keys.LIMIT, limit, 10) - q.add_param(keys.OFFSET, offset, 0) - q.add_param(keys.BROADCAST_TYPE, BroadcastType.validate(broadcast_type)) - return q - - -# required scope: channel_editor -@query -def create(channel_id, title, description=None, game=None, language=None, tag_list=None): - q = Qry('videos/', method=methods.POST) - q.add_param(keys.CHANNEL_ID, channel_id) - q.add_param(keys.TITLE, title) - q.add_param(keys.DESCRIPTION, description) - q.add_param(keys.GAME, game) - if language is not None: - q.add_param(keys.LANGUAGE, Language.validate(language)) - q.add_param(keys.TAG_LIST, tag_list) - return q - - -# required scope: channel_editor -@query -def update(video_id, title=None, description=None, game=None, language=None, tag_list=None): - q = Qry('videos/{video_id}', method=methods.PUT) - q.add_urlkw(keys.VIDEO_ID, video_id) - q.add_param(keys.TITLE, title) - q.add_param(keys.DESCRIPTION, description) - q.add_param(keys.GAME, game) - if language is not None: - q.add_param(keys.LANGUAGE, Language.validate(language)) - q.add_param(keys.TAG_LIST, tag_list) - return q - - -# required scope: channel_editor -@query -def delete(video_id): - q = Qry('videos/{video_id}', method=methods.DELETE) - q.add_urlkw(keys.VIDEO_ID, video_id) - return q - - -# requires upload token -@query -def upload_part(video_id, part, upload_token, content_length, data): - q = UQry('upload/{video_id}', method=methods.PUT) - q.set_headers({'Content-Length': content_length, 'Content-Type': 'application/octet-stream'}) - q.add_urlkw(keys.VIDEO_ID, video_id) - q.add_param(keys.PART, part) - q.add_param(keys.UPLOAD_TOKEN, upload_token) - q.add_bin(data) - return q - - -# requires upload token -@query -def complete_upload(video_id, upload_token): - q = UQry('upload/{video_id}/complete', method=methods.POST) - q.add_urlkw(keys.VIDEO_ID, video_id) - q.add_param(keys.UPLOAD_TOKEN, upload_token) - return q - - -# required scope: none -# undocumented / unsupported -@query -def _by_id(video_id): - q = HQry('videos/{video_id}', use_token=False) - q.add_urlkw(keys.VIDEO_ID, video_id) - return q diff --git a/resources/lib/twitch/exceptions.py b/resources/lib/twitch/exceptions.py index ac42919..a764f1a 100644 --- a/resources/lib/twitch/exceptions.py +++ b/resources/lib/twitch/exceptions.py @@ -1,4 +1,14 @@ # -*- encoding: utf-8 -*- +""" + + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" class ResourceUnavailableException(Exception): diff --git a/resources/lib/twitch/keys.py b/resources/lib/twitch/keys.py index 94c9da9..c33d0e3 100644 --- a/resources/lib/twitch/keys.py +++ b/resources/lib/twitch/keys.py @@ -1,21 +1,35 @@ # -*- encoding: utf-8 -*- """ - keys - string constants + + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2019 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. """ +AFTER = 'after' ALLOW_AUDIO_ONLY = 'allow_audio_only' ALLOW_SOURCE = 'allow_source' ALLOW_SPECTRE = 'allow_spectre' AVATAR_IMAGE = 'avatar_image' +BAKING_BREAD = 'baking_bread' +BAKING_BROWNIES = 'baking_brownies' +BAKING_BROWNIES_TIMEOUT = 'baking_brownies_timeout' +BEFORE = 'before' BROADCAST_TYPE = 'broadcast_type' +BROADCASTER_ID = 'broadcaster_id' BROADCASTER_LANGUAGE = 'broadcaster_language' BROADCASTS = 'broadcasts' +CDM = 'cdm' CHANNEL = 'channel' CHANNEL_FEED_ENABLED = 'channel_feed_enabled' CHANNEL_ID = 'channel_id' CLIP = 'clip' +CODE = 'code' COLLECTION_ID = 'collection_id' COMMENT_ID = 'comment_id' COMMENTS = 'comments' @@ -24,9 +38,12 @@ COMMUNITY_IDS = 'community_ids' CONTAINING_ITEM = 'containing_item' CONTENT = 'content' +CONTROL = 'control' +COUNT = 'count' COVER_IMAGE = 'cover_image' CURSOR = 'cursor' CLIENT_ID = 'client_id' +DATA = 'data' DELAY = 'delay' DESCRIPTION = 'description' DIRECTION = 'direction' @@ -34,10 +51,17 @@ EMAIL = 'email' EMOTE_ID = 'emote_id' EMOTESETS = 'emotesets' +ENDED_AT = 'ended_at' ERROR = 'error' +EXTENSION_ID = 'extension_id' +FAST_BREAD = 'fast_bread' FEATURED = 'featured' +FIRST = 'first' FOLLOWS = 'follows' +FROM_ID = 'from_id' GAME = 'game' +GAME_ID = 'game_id' +HAS_DELAY = 'has_delay' HLS = 'hls' ID = 'id' IDENTIFIER = 'identifier' @@ -46,6 +70,10 @@ LANGUAGE = 'language' LIMIT = 'limit' LIVE = 'live' +LIVE_ONLY = 'live_only' +LOGIN = 'login' +MANIFEST_ID = 'manifest_id' +MEDIAPLAYER = 'mediaplayer' MESSAGE = 'message' NAME = 'name' NAUTH = 'nauth' @@ -55,24 +83,35 @@ OFFSET = 'offset' PART = 'part' PERIOD = 'period' +PLATFORM = 'platform' +PLAYER_BACKEND = 'player_backend' +PLAYLIST_INCLUDE_FRAMERATE = 'playlist_include_framerate' POSITION = 'position' POST_ID = 'post_id' QUERY = 'query' REASON = 'reason' +REASSIGNMENT_SUPPORTED = 'reassignment_supported' +RTQOS = 'rtqos' RULES = 'rules' SCE_PLATFORM = 'sce_platform' SHARE = 'share' SIG = 'sig' +SIGNATURE = 'signature' SLUG = 'slug' SORT = 'sort' SORT_BY = 'sortby' +STARTED_AT = 'started_at' STATUS = 'status' +STREAM_PLAYBACK_ACCESS_TOKEN = 'streamPlaybackAccessToken' STREAM_TYPE = 'stream_type' SUMMARY = 'summary' +TAG_ID = 'tag_id' +TAG_IDS = 'tag_ids' TAG_LIST = 'tag_list' TARGET_ID = 'target_id' TEAM = 'team' TITLE = 'title' +TO_ID = 'to_id' TOKEN = 'token' TRENDING = 'trending' TYPE = 'type' @@ -82,7 +121,12 @@ USER_AGENT_STRING = ('Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) ' 'Gecko/20100101 Firefox/6.0') USER_ID = 'user_id' +USER_LOGIN = 'user_login' UPLOAD_TOKEN = 'upload_token' +VALUE = 'value' VIDEO_ID = 'video_id' +VIDEO_PLAYBACK_ACCESS_TOKEN = 'videoPlaybackAccessToken' VOD = 'vod' +WEB = 'web' +WV = 'wv' XBOX_HEARTBEAT = 'xbox_heartbeat' diff --git a/resources/lib/twitch/log.py b/resources/lib/twitch/log.py new file mode 100644 index 0000000..cc50925 --- /dev/null +++ b/resources/lib/twitch/log.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +import re +import logging +import copy + +try: + from logging import NullHandler +except ImportError: + class NullHandler(logging.Handler): + def emit(self, record): + pass + +try: + import xbmc +except ImportError: + xbmc = None + + +def _mask(message): + mask = '*' * 11 + masked_message = re.sub(r'((?:OAuth|Bearer)\s)[^\'"]+', r'\1' + mask, message) + masked_message = re.sub(r'(["\']email["\']:\s*[\'"])[^\'"]+', r'\1' + mask, masked_message) + masked_message = re.sub(r'(USER-IP=[\'"])[^\'"]+', r'\1' + mask, masked_message) + masked_message = re.sub(r'(["\']client_secret["\']:\s*[\'"])[^\'"]+', r'\1' + mask, masked_message) + masked_message = re.sub(r'(client_secret=).+?(&|$|\|)', r'\1' + mask + r'\2', masked_message) + masked_message = re.sub(r'(\\*["\']user_ip\\*["\']:\\*["\']).+?(\\*["\'])', r'\1' + mask + r'\2', masked_message) + masked_message = re.sub(r'(["\'](?:nauth)*sig["\']: ["\'])[^\'"]+', r'\1' + mask, masked_message) + return masked_message + + +def _add_leader(message): + if xbmc: + message = 'script.module.python.twitch: %s' % message + return message + + +def prep_log_message(message): + message = copy.deepcopy(message) + message = _mask(message) + message = _add_leader(message) + return message + + +class Log: + def __init__(self): + if xbmc: + self._log = xbmc.log + else: + self._log = logging.getLogger('twitch') + self._log.addHandler(NullHandler()) + + def info(self, message): + message = prep_log_message(message) + if xbmc: + self._log(message, xbmc.LOGINFO) + else: + self._log.info(message) + + def debug(self, message): + message = prep_log_message(message) + if xbmc: + self._log(message, xbmc.LOGDEBUG) + else: + self._log.debug(message) + + def warning(self, message): + message = prep_log_message(message) + if xbmc: + self._log(message, xbmc.LOGWARNING) + else: + self._log.debug(message) + + def error(self, message): + message = prep_log_message(message) + if xbmc: + self._log(message, xbmc.LOGERROR) + else: + self._log.error(message) + + def critical(self, message): + message = prep_log_message(message) + if xbmc: + self._log(message, xbmc.LOGFATAL) + else: + self._log.critical(message) + + def deprecated_query(self, old, new=None): + if new: + self.warning('DEPRECATED call to |{0}| detected, please use |{1}| instead'.format(old, new)) + else: + self.warning('DEPRECATED call to |{0}| detected, no alternatives available'.format(old)) + + def deprecated_endpoint(self, old): + self.warning('DEPRECATED call to |{0}| endpoint detected'.format(old)) + + def deprecated_api_version(self, old, new, eol_date): + self.warning('API version |{0}| is deprecated, update to |{1}| by |{2}|'.format(old, new, eol_date)) + + +log = Log() diff --git a/resources/lib/twitch/logging.py b/resources/lib/twitch/logging.py deleted file mode 100644 index c1145b2..0000000 --- a/resources/lib/twitch/logging.py +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import - -import logging - -try: - from logging import NullHandler -except ImportError: - class NullHandler(logging.Handler): - def emit(self, record): - pass - -log = logging.getLogger('twitch') -log.addHandler(NullHandler()) - - -def deprecation_warning(logger, old, new): - logger.warning("DEPRECATED call to '%s\' detected, " - "please use '%s' instead", - old, new) diff --git a/resources/lib/twitch/methods.py b/resources/lib/twitch/methods.py index 6fdefae..574ff00 100644 --- a/resources/lib/twitch/methods.py +++ b/resources/lib/twitch/methods.py @@ -1,4 +1,13 @@ # -*- encoding: utf-8 -*- +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" GET = 'GET' POST = 'POST' diff --git a/resources/lib/twitch/oauth.py b/resources/lib/twitch/oauth.py deleted file mode 100644 index d51dd6b..0000000 --- a/resources/lib/twitch/oauth.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- encoding: utf-8 -*- - -from twitch import CLIENT_ID -from twitch import scopes -from six.moves.urllib_parse import urlsplit, urlencode - - -class MobileClient: - _auth_base_url = 'https://api.twitch.tv/kraken/oauth2/authorize' - - def __init__(self, client_id=''): - self.client_id = client_id if client_id else CLIENT_ID - - def prepare_request_uri(self, redirect_uri='http://localhost:3000/', scope=list(), force_verify=False, state=''): - params = {'response_type': 'token', - 'client_id': self.client_id, - 'redirect_uri': redirect_uri, - 'scope': ' '.join(scope), - 'force_verify': str(force_verify).lower(), - 'state': state} - params = urlencode(params) - url = '{base_uri}?{params}'.format(base_uri=self._auth_base_url, params=params) - return url - - @staticmethod - def parse_implicit_response(url): - pairs = urlsplit(url).fragment.split('&') - fragment = dict() - for pair in pairs: - key, value = pair.split('=') - fragment[key] = value - return {'access_token': fragment.get('access_token'), 'scope': fragment.get('scope', '').split('+'), 'state': fragment.get('state')} diff --git a/resources/lib/twitch/oauth/__init__.py b/resources/lib/twitch/oauth/__init__.py new file mode 100644 index 0000000..c19f376 --- /dev/null +++ b/resources/lib/twitch/oauth/__init__.py @@ -0,0 +1,17 @@ +# -*- encoding: utf-8 -*- +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['default', 'helix', 'clients', 'validation'] + +from . import helix +from . import helix as default +from . import clients +from . import validation diff --git a/resources/lib/twitch/oauth/clients.py b/resources/lib/twitch/oauth/clients.py new file mode 100644 index 0000000..0b73f13 --- /dev/null +++ b/resources/lib/twitch/oauth/clients.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from urllib.parse import urlsplit +from urllib.parse import urlencode + +from .. import CLIENT_ID, CLIENT_SECRET, methods +from ..queries import OAuthQuery as Qry +from ..queries import query + +class MobileClient: + def __init__(self, client_id='', client_secret=''): + self.client_id = client_id if client_id else CLIENT_ID + self.client_secret = client_secret if client_secret else CLIENT_SECRET + + def prepare_request_uri(self, redirect_uri='http://localhost:3000/', scope=list(), force_verify=False, state=''): + q = Qry('authorize') + q.add_param('response_type', 'token') + q.add_param('client_id', self.client_id) + q.add_param('redirect_uri', redirect_uri) + q.add_param('scope', ' '.join(scope)) + q.add_param('force_verify', str(force_verify).lower()) + q.add_param('state', state) + return '?'.join([q.url, urlencode(q.params)]) + + def prepare_token_uri(self, scope=list()): + q = Qry('token') + q.add_param('client_id', self.client_id) + q.add_param('client_secret', self.client_secret) + q.add_param('grant_type', 'client_credentials') + q.add_param('scope', ' '.join(scope)) + return '?'.join([q.url, urlencode(q.params)]) + + def prepare_revoke_uri(self, token): + q = Qry('revoke') + q.add_param('client_id', self.client_id) + q.add_param('token', token) + return '?'.join([q.url, urlencode(q.params)]) + + @query + def revoke_token(self, token): + q = Qry('revoke', method=methods.POST) + q.add_param('client_id', self.client_id) + q.add_param('token', token) + return q + + @query + def get_app_access_token(self, scope=list()): + q = Qry('token', method=methods.POST) + q.add_param('client_id', self.client_id) + q.add_param('client_secret', self.client_secret) + q.add_param('grant_type', 'client_credentials') + q.add_param('scope', ' '.join(scope)) + return q + + @staticmethod + def parse_implicit_response(url): + pairs = urlsplit(url).fragment.split('&') + fragment = dict() + for pair in pairs: + key, value = pair.split('=') + fragment[key] = value + return {'access_token': fragment.get('access_token'), 'scope': fragment.get('scope', '').split('+'), 'state': fragment.get('state')} diff --git a/resources/lib/twitch/oauth/helix/__init__.py b/resources/lib/twitch/oauth/helix/__init__.py new file mode 100644 index 0000000..652caae --- /dev/null +++ b/resources/lib/twitch/oauth/helix/__init__.py @@ -0,0 +1,14 @@ +# -*- encoding: utf-8 -*- +""" + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +__all__ = ['scopes'] + +from . import scopes diff --git a/resources/lib/twitch/oauth/helix/scopes.py b/resources/lib/twitch/oauth/helix/scopes.py new file mode 100644 index 0000000..d388d31 --- /dev/null +++ b/resources/lib/twitch/oauth/helix/scopes.py @@ -0,0 +1,54 @@ +# -*- encoding: utf-8 -*- +""" + Reference: https://dev.twitch.tv/docs/authentication#scopes + + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +analytics_read_extensions = 'analytics:read:extensions' # View analytics data for the Twitch Extensions owned by the authenticated account. +analytics_read_games = 'analytics:read:games' # View analytics data for the games owned by the authenticated account. +bits_read = 'bits:read' # View Bits information for a channel. +channel_edit_commercial = 'channel:edit:commercial' # Run commercials on a channel. +channel_manage_broadcast = 'channel:manage:broadcast' # Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags. +channel_manage_extensions = 'channel:manage:extensions' # Manage a channel’s Extension configuration, including activating Extensions. +channel_manage_polls = 'channel:manage:polls' # Manage a channel’s polls. +channel_manage_predictions = 'channel:manage:predictions' # Manage of channel’s Channel Points Predictions +channel_manage_redemptions = 'channel:manage:redemptions' # Manage Channel Points custom rewards and their redemptions on a channel. +channel_manage_schedule = 'channel:manage:schedule' # Manage a channel’s stream schedule. +channel_manage_videos = 'channel:manage:videos' # Manage a channel’s videos, including deleting videos. +channel_read_editors = 'channel:read:editors' # View a list of users with the editor role for a channel. +channel_read_goals = 'channel:read:goals' # View Creator Goals for a channel. +channel_read_hype_train = 'channel:read:hype_train' # View Hype Train information for a channel. +channel_read_polls = 'channel:read:polls' # View a channel’s polls. +channel_read_predictions = 'channel:read:predictions' # View a channel’s Channel Points Predictions. +channel_read_redemptions = 'channel:read:redemptions' # View Channel Points custom rewards and their redemptions on a channel. +channel_read_stream_key = 'channel:read:stream_key' # View an authorized user’s stream key. +channel_read_subscriptions = 'channel:read:subscriptions' # View a list of all subscribers to a channel and check if a user is subscribed to a channel. +clips_edit = 'clips:edit' # Manage Clips for a channel. +moderation_read = 'moderation:read' # View a channel’s moderation data including Moderators, Bans, Timeouts, and Automod settings. +moderator_manage_banned_users = 'moderator:manage:banned_users' # Ban and unban users. +moderator_read_blocked_terms = 'moderator:read:blocked_terms' # View a broadcaster’s list of blocked terms. +moderator_manage_blocked_terms = 'moderator:manage:blocked_terms' # Manage a broadcaster’s list of blocked terms. +moderator_manage_automod = 'moderator:manage:automod' # Manage messages held for review by AutoMod in channels where you are a moderator. +moderator_read_automod_settings = 'moderator:read:automod_settings' # View a broadcaster’s AutoMod settings. +moderator_manage_automod_settings = 'moderator:manage:automod_settings' # Manage a broadcaster’s AutoMod settings. +moderator_read_chat_settings = 'moderator:read:chat_settings' # View a broadcaster’s chat room settings. +moderator_manage_chat_settings = 'moderator:manage:chat_settings' # Manage a broadcaster’s chat room settings. +user_edit = 'user:edit' # Manage a user object. +user_edit_follows = 'user:edit:follows' # Deprecated. Was previously used for “Create User Follows” and “Delete User Follows.” See Deprecation of Create and Delete Follows API Endpoints. +user_manage_blocked_users = 'user:manage:blocked_users' # Manage the block list of a user. +user_read_blocked_users = 'user:read:blocked_users' # View the block list of a user. +user_read_broadcast = 'user:read:broadcast' # View a user’s broadcasting configuration, including Extension configurations. +user_read_email = 'user:read:email' # View a user’s email address. +user_read_follows = 'user:read:follows' # View the list of channels a user follows. +user_read_subscriptions = 'user:read:subscriptions' # View if an authorized user is subscribed to specific channels. +channel_moderate = 'channel:moderate' # Perform moderation actions in a channel. The user requesting the scope must be a moderator in the channel. +chat_edit = 'chat:edit' # Send live stream chat and rooms messages. +chat_read = 'chat:read' # View live stream chat and rooms messages. +whispers_read = 'whispers:read' # View your whisper messages. +whispers_edit = 'whispers:edit' # Send whisper messages. diff --git a/resources/lib/twitch/oauth/validation.py b/resources/lib/twitch/oauth/validation.py new file mode 100644 index 0000000..45b8be3 --- /dev/null +++ b/resources/lib/twitch/oauth/validation.py @@ -0,0 +1,19 @@ +# -*- encoding: utf-8 -*- +""" + + Copyright (C) 2022 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from ..queries import OAuthValidationQuery as Qry +from ..queries import query + + +@query +def validate(token=None): + q = Qry(token) + return q diff --git a/resources/lib/twitch/parser.py b/resources/lib/twitch/parser.py index 6147ec4..b41345c 100644 --- a/resources/lib/twitch/parser.py +++ b/resources/lib/twitch/parser.py @@ -1,31 +1,69 @@ # -*- encoding: utf-8 -*- +""" + + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + import re -from twitch import keys -from twitch.logging import log +from urllib.parse import urlencode + +from . import keys +from .log import log _m3u_pattern = re.compile( r'#EXT-X-MEDIA:TYPE=VIDEO.*' r'GROUP-ID="(?P[^"]*)",' r'NAME="(?P[^"]*)"[,=\w]*\n' r'#EXT-X-STREAM-INF:.*' - r'BANDWIDTH=(?P[0-9]+).*\n(' - r'?Phttp.*)') - -_clip_embed_pattern = re.compile(r'quality_options:\s*(?P\[[^\]]+?\])') + r'BANDWIDTH=(?P[0-9]+),' + r'(?:.*RESOLUTION="*(?P[0-9xX]+)"*,)?' + r'(?:.*FRAME-RATE=(?P[0-9.]+))?.*\n' + r'(?Phttp.*)') _error_pattern = re.compile(r'.*error(?P.+?).*', re.IGNORECASE) +def _find_frame_rate(group_id, group_name): + group_id = group_id.lower() + + if group_id == 'audio_only': + return None + elif group_id == 'chunked': + group_id = group_name.lower().replace('(source)', '').strip() + + info = group_id.split('p') + if len(info) > 1 and info[1]: + fps = float(info[1]) + else: + fps = 30.0 + + return fps + + def m3u8(f): def m3u8_wrapper(*args, **kwargs): results = f(*args, **kwargs) + try: + results = results.decode('utf-8') + except AttributeError: + pass if keys.ERROR in results: if isinstance(results, dict): return results else: error = re.search(_error_pattern, results) if error: - return {'error': 'Error', 'message': error.group('message'), 'status': 404} + return { + 'error': 'Error', + 'message': error.group('message'), + 'status': 404 + } return m3u8_to_list(results) return m3u8_wrapper @@ -43,13 +81,25 @@ def m3u8_to_dict(string): d = dict() matches = re.finditer(_m3u_pattern, string) for m in matches: - name = 'Audio Only' if m.group('group_name') == 'audio_only' else m.group('group_name') - name = 'Source' if m.group('group_id') == 'chunked' else name + if m.group('group_name') == 'audio_only': + name = 'Audio Only' + elif m.group('group_id') == 'chunked': + name = 'Source' + else: + name = m.group('group_name') + + if m.group('fps'): + fps = float(m.group('fps')) + else: + fps = _find_frame_rate(m.group('group_id'), m.group('group_name')) + d[m.group('group_id')] = { 'id': m.group('group_id'), 'name': name, 'url': m.group('url'), - 'bandwidth': int(m.group('bandwidth')) + 'bandwidth': int(m.group('bandwidth')), + 'fps': fps, + 'resolution': m.group('resolution') } log.debug('m3u8_to_dict result:\n{0}'.format(d)) return d @@ -60,31 +110,58 @@ def m3u8_to_list(string): l = list() matches = re.finditer(_m3u_pattern, string) for m in matches: - name = 'Audio Only' if m.group('group_name') == 'audio_only' else m.group('group_name') - name = 'Source' if m.group('group_id') == 'chunked' else name + if m.group('group_name') == 'audio_only': + name = 'Audio Only' + elif m.group('group_id') == 'chunked': + name = 'Source' + else: + name = m.group('group_name') + + if m.group('fps'): + fps = float(m.group('fps')) + else: + fps = _find_frame_rate(m.group('group_id'), m.group('group_name')) + l.append({ 'id': m.group('group_id'), 'name': name, 'url': m.group('url'), - 'bandwidth': int(m.group('bandwidth')) + 'bandwidth': int(m.group('bandwidth')), + 'fps': fps, + 'resolution': m.group('resolution') }) log.debug('m3u8_to_list result:\n{0}'.format(l)) return l -def clip_embed_to_list(string): - log.debug('clip_embed_to_list called for:\n{0}'.format(string)) - match = re.search(_clip_embed_pattern, string) +def clip_embed_to_list(response): + log.debug('clip_embed_to_list called for:\n{0}'.format(response)) + + clip_json = response.get('data', {}).get('clip', {}) + access_token = clip_json.get('playbackAccessToken', {}) + token = access_token.get('value', '') + signature = access_token.get('signature', '') + qualities = clip_json.get('videoQualities', []) + + params = urlencode({ + 'sig': signature, + 'token': token + }) + l = list() - if match: - match = eval(match.group('qualities')) + + if isinstance(response, dict): + clip = response.get('data', {}).get('clip', {}) + qualities = clip.get('videoQualities', list()) + + if qualities: l = [{ - 'id': item['quality'], - 'name': item['quality'], - 'url': item['source'], - 'bandwidth': -1 - } for item in match] + 'id': item['quality'], + 'name': item['quality'], + 'url': item['sourceURL'] + '?' + params, + 'bandwidth': -1 + } for item in qualities] if l: l.insert(0, { 'id': 'Source', diff --git a/resources/lib/twitch/queries.py b/resources/lib/twitch/queries.py index 3d8c278..63c299e 100644 --- a/resources/lib/twitch/queries.py +++ b/resources/lib/twitch/queries.py @@ -1,20 +1,33 @@ # -*- encoding: utf-8 -*- +""" -from six.moves.urllib.parse import urljoin + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch -from twitch import CLIENT_ID, OAUTH_TOKEN -from twitch.exceptions import ResourceUnavailableException -from twitch.logging import log -from twitch.scraper import download, get_json -from twitch import methods + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + +from copy import deepcopy +from urllib.parse import urljoin + +from . import CLIENT_ID, OAUTH_TOKEN, APP_TOKEN +from .exceptions import ResourceUnavailableException +from .log import log, prep_log_message +from .scraper import download, get_json, get_json_and_headers +from . import methods _kraken_baseurl = 'https://api.twitch.tv/kraken/' +_helix_baseurl = 'https://api.twitch.tv/helix/' _hidden_baseurl = 'https://api.twitch.tv/api/' _usher_baseurl = 'https://usher.ttvnw.net/' -_clips_baseurl = 'https://clips.twitch.tv/' +_clips_baseurl = 'https://gql.twitch.tv/gql' _uploads_baseurl = 'https://uploads.twitch.tv/' - -_v5_headers = {'ACCEPT': 'application/vnd.twitchtv.v5+json'} +_gql_baseurl = 'https://gql.twitch.tv/gql' +_oauth_baseurl = 'https://api.twitch.tv/kraken/oauth2/' +_oauthid_baseurl = 'https://id.twitch.tv/oauth2/' class _Query(object): @@ -89,7 +102,7 @@ def execute(self, f): try: return f(self.url, self.params, self.headers, self.data, self.method) except: - raise ResourceUnavailableException(str(self)) + raise ResourceUnavailableException(prep_log_message(str(self))) class DownloadQuery(_Query): @@ -104,54 +117,136 @@ def execute(self): return super(JsonQuery, self).execute(get_json) +class HelixJsonQuery(_Query): + def execute(self): + # TODO implement get_json completely here + return super(HelixJsonQuery, self).execute(get_json_and_headers) + + class ApiQuery(JsonQuery): def __init__(self, path, headers={}, data={}, use_token=True, method=methods.GET): - headers.setdefault('Client-ID', CLIENT_ID) + _headers = deepcopy(headers) + _headers.setdefault('Client-ID', CLIENT_ID) if use_token and OAUTH_TOKEN: - headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) - super(ApiQuery, self).__init__(_kraken_baseurl, headers, data, method) + _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) + super(ApiQuery, self).__init__(_kraken_baseurl, _headers, data, method) + self.add_path(path) + + +class HelixApiQuery(HelixJsonQuery): + def __init__(self, path, headers={}, data={}, use_app_token=False, method=methods.GET): + _headers = deepcopy(headers) + _headers.setdefault('Client-ID', CLIENT_ID) + if use_app_token and APP_TOKEN: + _headers.setdefault('Authorization', 'Bearer {access_token}'.format(access_token=APP_TOKEN)) + elif OAUTH_TOKEN: + _headers.setdefault('Authorization', 'Bearer {access_token}'.format(access_token=OAUTH_TOKEN)) + super(HelixApiQuery, self).__init__(_helix_baseurl, _headers, data, method) + self._params = list() self.add_path(path) + def add_param(self, key, value, default=None): + if value != default: + if isinstance(value, list): + _params = [] + for val in value: + _params += [(key, val)] + self._params += _params + elif (key, value) not in self._params: + self._params += [(key, value)] + return self + class HiddenApiQuery(JsonQuery): def __init__(self, path, headers={}, data={}, use_token=True, method=methods.GET): - headers.setdefault('Client-ID', CLIENT_ID) - if use_token and OAUTH_TOKEN: - headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) - super(HiddenApiQuery, self).__init__(_hidden_baseurl, headers, data, method) + _headers = deepcopy(headers) + if 'Client-ID' not in _headers: + _headers.setdefault('Client-ID', CLIENT_ID) + if 'Client-ID' in _headers and not _headers.get('Client-ID'): + del _headers['Client-ID'] + if 'Authorization' not in _headers: + if use_token and OAUTH_TOKEN: + _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) + if 'Authorization' in _headers and not _headers.get('Authorization'): + del _headers['Authorization'] + super(HiddenApiQuery, self).__init__(_hidden_baseurl, _headers, data, method) self.add_path(path) class UsherQuery(DownloadQuery): def __init__(self, path, headers={}, data={}, method=methods.GET): - headers.setdefault('Client-ID', CLIENT_ID) - if OAUTH_TOKEN: - headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) - super(UsherQuery, self).__init__(_usher_baseurl, headers, data, method) + _headers = deepcopy(headers) + if 'Client-ID' not in _headers: + _headers.setdefault('Client-ID', CLIENT_ID) + if 'Client-ID' in _headers and not _headers.get('Client-ID'): + del _headers['Client-ID'] + if 'Authorization' not in _headers: + if OAUTH_TOKEN: + _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) + if 'Authorization' in _headers and not _headers.get('Authorization'): + del _headers['Authorization'] + super(UsherQuery, self).__init__(_usher_baseurl, _headers, data, method) self.add_path(path) -class ClipsQuery(DownloadQuery): +class OAuthQuery(JsonQuery): def __init__(self, path, headers={}, data={}, method=methods.GET): - super(ClipsQuery, self).__init__(_clips_baseurl, headers, data, method) + _headers = deepcopy(headers) + super(JsonQuery, self).__init__(_oauthid_baseurl, _headers, data, method) self.add_path(path) +class OAuthValidationQuery(JsonQuery): + def __init__(self, token=None): + _headers = {} + if token: + _headers['Authorization'] = 'OAuth {access_token}'.format(access_token=token) + if 'Authorization' not in _headers: + _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) + super(JsonQuery, self).__init__(_oauthid_baseurl, _headers, {}, methods.GET) + self.add_path('validate') + + +class ClipsQuery(JsonQuery): + def __init__(self, path='', headers={}, data={}, method=methods.POST): + _headers = deepcopy(headers) + super(ClipsQuery, self).__init__(_clips_baseurl, _headers, data, method) + if path: + self.add_path(path) + + class UploadsQuery(DownloadQuery): def __init__(self, path, headers={}, data={}, method=methods.PUT): - super(UploadsQuery, self).__init__(_uploads_baseurl, headers, data, method) + _headers = deepcopy(headers) + super(UploadsQuery, self).__init__(_uploads_baseurl, _headers, data, method) self.add_path(path) class V5Query(ApiQuery): def __init__(self, path, use_token=True, method=methods.GET): - super(V5Query, self).__init__(path, _v5_headers, use_token=use_token, method=method) + headers = {'ACCEPT': 'application/vnd.twitchtv.v5+json'} + super(V5Query, self).__init__(path, headers, use_token=use_token, method=method) + + +class HelixQuery(HelixApiQuery): + def __init__(self, path, use_app_token=False, method=methods.GET): + super(HelixQuery, self).__init__(path, use_app_token=use_app_token, method=method) + + +class GQLQuery(JsonQuery): + def __init__(self, path, headers={}, data={}, use_token=True, method=methods.POST): + _headers = deepcopy(headers) + _headers.setdefault('Client-ID', CLIENT_ID) + if use_token and OAUTH_TOKEN: + _headers.setdefault('Authorization', 'OAuth {access_token}'.format(access_token=OAUTH_TOKEN)) + super(GQLQuery, self).__init__(_gql_baseurl, _headers, data, method) + self.add_path(path) def assert_new(d, k): if k in d: v = d.get(k) - raise ValueError("Key '{0}' already set to '{1}'".format(k, v)) + raise ValueError('Key |{0}| already set to |{1}|'.format(k, v)) # TODO maybe rename @@ -159,10 +254,9 @@ def query(f): def wrapper(*args, **kwargs): qry = f(*args, **kwargs) if not isinstance(qry, _Query): - raise ValueError('{0} did not return a Query, was: {1}'.format(f.__name__, repr(qry))) - log.debug('%s QUERY: url: %s, params: %s, data: %s, ' - 'headers: %r, target_func: %r', - qry.method, qry.url, qry.params, qry.data, qry.headers, f.__name__) + raise ValueError('|{0}| did not return a Query, was: |{1}|'.format(f.__name__, repr(qry))) + log.debug('{0} QUERY: url: |{1}|, params: |{2}|, data: |{3}|, headers: |{4}|, target_func: |{5}|' + .format(qry.method, qry.url, qry.params, qry.data, qry.headers, f.__name__)) return qry.execute() return wrapper diff --git a/resources/lib/twitch/scopes.py b/resources/lib/twitch/scopes.py deleted file mode 100644 index 1738d09..0000000 --- a/resources/lib/twitch/scopes.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- encoding: utf-8 -*- -""" - scopes - - string constants -""" - -user_read = 'user_read' -user_blocks_edit = 'user_blocks_edit' -user_blocks_read = 'user_blocks_read' -user_follows_edit = 'user_follows_edit' -channel_read = 'channel_read' -channel_editor = 'channel_editor' -channel_commercial = 'channel_commercial' -channel_stream = 'channel_stream' -channel_subscriptions = 'channel_subscriptions' -user_subscriptions = 'user_subscriptions' -channel_check_subscription = 'channel_check_subscription' -chat_login = 'chat_login' -channel_feed_read = 'channel_feed_read' -channel_feed_edit = 'channel_feed_edit' -collections_edit = 'collections_edit' -communities_edit = 'communities_edit' -communities_moderate = 'communities_moderate' -viewing_activity_read = 'viewing_activity_read' diff --git a/resources/lib/twitch/scraper.py b/resources/lib/twitch/scraper.py index b8232b6..433d95d 100644 --- a/resources/lib/twitch/scraper.py +++ b/resources/lib/twitch/scraper.py @@ -1,15 +1,26 @@ # -*- encoding: utf-8 -*- +""" + + Copyright (C) 2012-2016 python-twitch (https://github.com/ingwinlu/python-twitch) + Copyright (C) 2016-2018 script.module.python.twitch + + This file is part of script.module.python.twitch + + SPDX-License-Identifier: GPL-3.0-only + See LICENSES/GPL-3.0-only for more information. +""" + import sys +from urllib.error import URLError +from urllib.parse import quote_plus +from urllib.parse import urlencode + import requests -# import six -from six.moves.urllib.error import URLError -from six.moves.urllib.parse import quote_plus # NOQA -from six.moves.urllib.parse import urlencode -# from six.moves.urllib.request import Request, urlopen -from twitch.keys import USER_AGENT, USER_AGENT_STRING -from twitch.logging import log -import methods +from .keys import USER_AGENT, USER_AGENT_STRING +from .log import log +from .exceptions import ResourceUnavailableException +from . import methods try: import json @@ -26,7 +37,7 @@ def get_json(baseurl, parameters={}, headers={}, data={}, method=methods.GET): '''Download Data from an URL and returns it as JSON @param url Url to download from - @param parameters Parameter dict to be encoded with url + @param parameters Parameter dict to be encoded with url or list of tuple pairs @param headers Headers dict to pass with Request @param data Request body @param method Request method @@ -35,37 +46,78 @@ def get_json(baseurl, parameters={}, headers={}, data={}, method=methods.GET): method = methods.validate(method) jsonString = download(baseurl, parameters, headers, data, method) jsonDict = json.loads(jsonString) - log.debug(json.dumps(jsonDict, indent=4, sort_keys=True)) + log.debug('url: |{0}| parameters: |{1}|\n{2}'.format(baseurl, parameters, json.dumps(jsonDict, indent=4, sort_keys=True))) return jsonDict -def download(baseurl, parameters={}, headers={}, data={}, method=methods.GET): +def get_json_and_headers(baseurl, parameters={}, headers={}, data={}, method=methods.GET): + '''Download Data from an URL and returns it as JSON + @param url Url to download from + @param parameters Parameter dict to be encoded with url or list of tuple pairs + @param headers Headers dict to pass with Request + @param data Request body + @param method Request method + @returns JSON Object with data and headers from URL {'response': {}, 'headers': {}} + ''' + method = methods.validate(method) + content = download(baseurl, parameters, headers, data, method, response_headers=True) + content['response'] = json.loads(content['response']) + log.debug('url: |{0}| parameters: |{1}|\n{2}'.format(baseurl, parameters, json.dumps(content['response'], indent=4, sort_keys=True))) + return content + + +def download(baseurl, parameters={}, headers={}, data={}, method=methods.GET, response_headers=False): '''Download Data from an url and returns it as a String @param method Request method @param baseurl Url to download from (e.g. http://www.google.com) - @param parameters Parameter dict to be encoded with url + @param parameters Parameter dict to be encoded with url or list of tuple pairs @param headers Headers dict to pass with Request @param data Request body @param method Request method - @returns String of data from URL + @param response_headers Include response headers in response {'response': {}, 'headers': {}} + @returns String of data from URL or {'response': {}, 'headers': {}} if response_headers is True ''' method = methods.validate(method) - url = '?'.join([baseurl, urlencode(parameters)]) - log.debug('Downloading: ' + url) + + if not parameters: + url = baseurl + elif isinstance(parameters, dict): + url = '?'.join([baseurl, urlencode(parameters)]) + else: + _parameters = '' + for param in parameters: + _parameters += '{0}={1}&'.format(param[0], quote_plus(str(param[1]))) + _parameters = _parameters.rstrip('&') + url = '?'.join([baseurl, _parameters]) + + log.debug('Downloading: |{0}|'.format(url)) content = "" for _ in range(MAX_RETRIES): try: headers.update({USER_AGENT: USER_AGENT_STRING}) - response = requests.request(method=method, url=url, headers=headers, data=data, verify=SSL_VERIFICATION) + if isinstance(data, list): + json_body = data + data = None + else: + json_body = None + response = requests.request(method=method, url=url, headers=headers, + json=json_body, data=data, verify=SSL_VERIFICATION) content = response.content if not content: - content = '{"status": %d}' % response.status_code + content = '{{"status": {0}}}'.format(response.status_code) break except Exception as err: if not isinstance(err, URLError): - log.debug("Error %s during HTTP Request, abort", repr(err)) + log.debug('Error |{0}| during HTTP Request, abort'.format(repr(err))) raise # propagate non-URLError - log.debug("Error %s during HTTP Request, retrying", repr(err)) + log.debug('Error |{0}| during HTTP Request, retrying'.format(repr(err))) else: - raise - return content + raise ResourceUnavailableException('Max retries exceeded') + + if isinstance(content, bytes): + content = content.decode('utf-8') + + if not response_headers: + return content + else: + return {'response': content, 'headers': response.headers} diff --git a/icon.png b/resources/media/icon.png similarity index 100% rename from icon.png rename to resources/media/icon.png