mirror of
https://github.com/TeamNewPipe/NewPipeExtractor
synced 2025-08-28 21:07:57 +00:00
Compare commits
No commits in common. "dev" and "v0.19.4" have entirely different histories.
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1,2 +0,0 @@
|
||||
liberapay: TeamNewPipe
|
||||
custom: 'https://newpipe.net/donate/'
|
93
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
93
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,93 +0,0 @@
|
||||
name: Bug report
|
||||
description: Create a bug report to help us improve
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for helping to make the NewPipe Extractor better by reporting a bug. :hugs:
|
||||
|
||||
Please fill in as much information as possible about your bug so that we don't have to play "information ping-pong" and can help you immediately.
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: "Checklist"
|
||||
options:
|
||||
- label: "I am able to reproduce the bug with the latest version given here: [CLICK THIS LINK](https://github.com/TeamNewPipe/NewPipeExtractor/releases/latest)."
|
||||
required: true
|
||||
- label: "I am aware that this issue is being opened for the NewPipe Extractor, NOT the [app](https://github.com/TeamNewPipe/NewPipe), and my bug report will be dismissed otherwise."
|
||||
required: true
|
||||
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipeExtractor/issues) or [closed](https://github.com/TeamNewPipe/NewPipeExtractor/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||
required: true
|
||||
- label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise."
|
||||
required: true
|
||||
- label: "This issue contains only one bug."
|
||||
required: true
|
||||
- label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)."
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: extractor-version
|
||||
attributes:
|
||||
label: Affected version
|
||||
description: "In which NewPipe Extractor version did you encounter the bug?"
|
||||
placeholder: "x.xx.x"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce the bug
|
||||
description: |
|
||||
What did you do for the bug to show up?
|
||||
|
||||
If you can't cause the bug to show up again reliably (and hence don't have a proper set of steps to give us), please still try to give as many details as possible on how you think you encountered the bug.
|
||||
placeholder: |
|
||||
1. Init NewPipe with 'NewPipe.init(...)'
|
||||
2. Create a StreamExtractor for xyz: 'StreamExtractor e = YouTube.getStreamExtractor(xyz.com)'
|
||||
3. Get the description 'e.getDescription()'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: |
|
||||
Tell us what you expect to happen.
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual behavior
|
||||
description: |
|
||||
Tell us what happens with the steps given above.
|
||||
|
||||
- type: textarea
|
||||
id: screen-media
|
||||
attributes:
|
||||
label: Screenshots/Screen recordings
|
||||
description: |
|
||||
A picture or video is worth a thousand words.
|
||||
|
||||
If applicable, add screenshots or a screen recording to help explain your problem.
|
||||
GitHub supports uploading them directly in the text box.
|
||||
If your file is too big for Github to accept, try to compress it (ZIP-file) or feel free to paste a link to an image/video hoster here instead.
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs
|
||||
description: |
|
||||
If your bug includes a log you think we need to see, paste it here.
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: |
|
||||
Any other information you'd like to include, for instance that
|
||||
* your cat disabled your network connection
|
||||
* ...
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 Matrix
|
||||
url: https://matrix.to/#/#newpipe:matrix.newpipe-ev.de
|
||||
about: Chat with us via Matrix for quick Q/A
|
||||
- name: 💬 IRC
|
||||
url: https://web.libera.chat/#newpipe
|
||||
about: Chat with us via IRC for quick Q/A
|
50
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
50
.github/ISSUE_TEMPLATE/enhancement.yml
vendored
@ -1,50 +0,0 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thank you for helping to make the NewPipe Extractor better by suggesting a feature. :hugs:
|
||||
|
||||
Your ideas are highly welcome! The Extractor is made for you, the downstream developers, after all.
|
||||
|
||||
- type: checkboxes
|
||||
id: checklist
|
||||
attributes:
|
||||
label: "Checklist"
|
||||
options:
|
||||
- label: "I am aware that this issue is being opened for the NewPipe Extractor, NOT the [app](https://github.com/TeamNewPipe/NewPipe), and my feature request will be dismissed otherwise."
|
||||
required: true
|
||||
- label: "I made sure that there are *no existing issues* - [open](https://github.com/TeamNewPipe/NewPipeExtractor/issues) or [closed](https://github.com/TeamNewPipe/NewPipeExtractor/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to."
|
||||
required: true
|
||||
- label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise."
|
||||
required: true
|
||||
- label: "This issue contains only one feature request."
|
||||
required: true
|
||||
- label: "I have read and understood the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/dev/.github/CONTRIBUTING.md)."
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Feature description
|
||||
description: |
|
||||
Explain how you want the Extractor's behavior to change to suit your needs.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: why-is-the-feature-requested
|
||||
attributes:
|
||||
label: Why do you want this feature?
|
||||
description: |
|
||||
Describe any problem or limitation you come across while using the Extractor which would be solved by this feature.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Any other information you'd like to include, for instance sketches, mockups, pictures of cats, etc.
|
12
.github/dependabot.yml
vendored
12
.github/dependabot.yml
vendored
@ -1,12 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maintain dependencies for Gradle
|
||||
- package-ecosystem: "gradle"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
69
.github/workflows/ci.yml
vendored
69
.github/workflows/ci.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
downloaderType:
|
||||
description: 'Downloader Type'
|
||||
type: choice
|
||||
options:
|
||||
- MOCK
|
||||
- REAL
|
||||
schedule:
|
||||
# once per day
|
||||
- cron: 0 0 * * *
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache Gradle wrapper dists
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/wrapper/dists
|
||||
key: ${{ runner.os }}-gradle-wrapper-dists-${{ hashFiles('gradle/wrapper/**') }}
|
||||
restore-keys: ${{ runner.os }}-gradle-wrapper-dists
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
|
||||
# See gradle file for difference between downloaders
|
||||
- name: Build and run Tests
|
||||
run: |
|
||||
downloader_type="${{ github.event.inputs.downloaderType || 'MOCK' }}"
|
||||
if [[ "$GITHUB_EVENT_NAME" == 'schedule' ]]; then
|
||||
downloader_type="REAL"
|
||||
fi
|
||||
|
||||
echo "Running with $downloader_type downloader"
|
||||
./gradlew check \
|
||||
-Dorg.gradle.welcome=never \
|
||||
--stacktrace \
|
||||
-Ddownloader=$downloader_type
|
||||
|
||||
- name: Upload test reports when failure occurs
|
||||
uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: NewPipeExtractor-test-reports
|
||||
path: extractor/build/reports/tests/test/**
|
38
.github/workflows/docs.yml
vendored
38
.github/workflows/docs.yml
vendored
@ -1,38 +0,0 @@
|
||||
name: Build and deploy JavaDocs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
# The generated docs are written to the `gh-pages` branch.
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-deploy-docs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: set up JDK 11
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Cache Gradle dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
restore-keys: ${{ runner.os }}-gradle
|
||||
|
||||
- name: Build JavaDocs
|
||||
run: ./gradlew aggregatedJavadocs
|
||||
|
||||
- name: Deploy JavaDocs
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./build/docs
|
14
.travis.yml
Normal file
14
.travis.yml
Normal file
@ -0,0 +1,14 @@
|
||||
language: java
|
||||
jdk:
|
||||
- openjdk8
|
||||
|
||||
script:
|
||||
- ./gradlew check aggregatedJavadocs
|
||||
|
||||
deploy:
|
||||
provider: pages
|
||||
skip_cleanup: true
|
||||
github_token: $GITHUB_TOKEN # Set in travis-ci.org dashboard
|
||||
local_dir: build/docs
|
||||
on:
|
||||
branch: master
|
8
LICENSE
8
LICENSE
@ -1,7 +1,7 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
|
24
README.md
24
README.md
@ -1,6 +1,6 @@
|
||||
# NewPipe Extractor
|
||||
|
||||
[](https://github.com/TeamNewPipe/NewPipeExtractor/actions/workflows/ci.yml) [](https://jitpack.io/#teamnewpipe/NewPipeExtractor) [JDoc](https://teamnewpipe.github.io/NewPipeExtractor/javadoc/) • [Documentation](https://teamnewpipe.github.io/documentation/)
|
||||
[](https://travis-ci.org/TeamNewPipe/NewPipeExtractor) [](https://jitpack.io/#TeamNewPipe/NewPipeExtractor) [JDoc](https://teamnewpipe.github.io/NewPipeExtractor/javadoc/) • [Documentation](https://teamnewpipe.github.io/documentation/)
|
||||
|
||||
NewPipe Extractor is a library for extracting things from streaming sites. It is a core component of [NewPipe](https://github.com/TeamNewPipe/NewPipe), but could be used independently.
|
||||
|
||||
@ -11,16 +11,7 @@ NewPipe Extractor is available at JitPack's Maven repo.
|
||||
If you're using Gradle, you could add NewPipe Extractor as a dependency with the following steps:
|
||||
|
||||
1. Add `maven { url 'https://jitpack.io' }` to the `repositories` in your `build.gradle`.
|
||||
2. Add `implementation 'com.github.teamnewpipe:NewPipeExtractor:INSERT_VERSION_HERE'` to the `dependencies` in your `build.gradle`. Replace `INSERT_VERSION_HERE` with the [latest release](https://github.com/TeamNewPipe/NewPipeExtractor/releases/latest).
|
||||
3. If you are using tools to minimize your project, make sure to keep the files below, by e.g. adding the following lines to your proguard file:
|
||||
```
|
||||
## Rules for NewPipeExtractor
|
||||
-keep class org.mozilla.javascript.** { *; }
|
||||
-keep class org.mozilla.classfile.ClassFileWriter
|
||||
-dontwarn org.mozilla.javascript.tools.**
|
||||
```
|
||||
|
||||
**Note:** To use NewPipe Extractor in Android projects with a `minSdk` below 33, [core library desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) with the `desugar_jdk_libs_nio` artifact is required.
|
||||
2. Add `implementation 'com.github.TeamNewPipe:NewPipeExtractor:v0.19.0'`the `dependencies` in your `build.gradle`. Replace `v0.19.0` with the latest release.
|
||||
|
||||
### Testing changes
|
||||
|
||||
@ -29,7 +20,7 @@ To test changes quickly you can build the library locally. A good approach would
|
||||
```groovy
|
||||
includeBuild('../NewPipeExtractor') {
|
||||
dependencySubstitution {
|
||||
substitute module('com.github.teamnewpipe:NewPipeExtractor') with project(':extractor')
|
||||
substitute module('com.github.TeamNewPipe:NewPipeExtractor') with project(':extractor')
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -39,7 +30,7 @@ Another approach would be to use the local Maven repository, here's a gist of ho
|
||||
1. Add `mavenLocal()` in your project `repositories` list (usually as the first entry to give priority above the others).
|
||||
2. It's _recommended_ that you change the `version` of this library (e.g. `LOCAL_SNAPSHOT`).
|
||||
3. Run gradle's `ìnstall` task to deploy this library to your local repository (using the wrapper, present in the root of this project: `./gradlew install`)
|
||||
4. Change the dependency version used in your project to match the one you chose in step 2 (`implementation 'com.github.teamnewpipe:NewPipeExtractor:LOCAL_SNAPSHOT'`)
|
||||
4. Change the dependency version used in your project to match the one you chose in step 2 (`implementation 'com.github.TeamNewPipe:NewPipeExtractor:LOCAL_SNAPSHOT'`)
|
||||
|
||||
> Tip for Android Studio users: After you make changes and run the `install` task, use the menu option `File → "Sync with File System"` to refresh the library in your project.
|
||||
|
||||
@ -49,15 +40,14 @@ The following sites are currently supported:
|
||||
|
||||
- YouTube
|
||||
- SoundCloud
|
||||
- media.ccc.de
|
||||
- MediaCCC
|
||||
- PeerTube (no P2P)
|
||||
- Bandcamp
|
||||
|
||||
## License
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
NewPipe Extractor is Free Software: You can use, study share and improve it at your
|
||||
NewPipe is Free Software: You can use, study share and improve it at your
|
||||
will. Specifically you can redistribute and/or modify it under the terms of the
|
||||
[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as
|
||||
published by the Free Software Foundation, either version 3 of the License, or
|
||||
|
58
build.gradle
58
build.gradle
@ -1,59 +1,31 @@
|
||||
allprojects {
|
||||
apply plugin: 'java-library'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'maven'
|
||||
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
compileTestJava.options.encoding = 'UTF-8'
|
||||
sourceCompatibility = 1.7
|
||||
targetCompatibility = 1.7
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
|
||||
version 'v0.24.8'
|
||||
version 'v0.19.4'
|
||||
group 'com.github.TeamNewPipe'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
nanojsonVersion = "e9d656ddb49a412a5a0a5d5ef20ca7ef09549996"
|
||||
jsr305Version = "3.0.2"
|
||||
junitVersion = "5.13.4"
|
||||
checkstyleVersion = "10.4"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(':extractor')
|
||||
implementation project(':extractor')
|
||||
implementation project(':timeago-parser')
|
||||
}
|
||||
|
||||
subprojects {
|
||||
tasks.register('sourcesJar', Jar) {
|
||||
dependsOn classes
|
||||
archiveClassifier.set('sources')
|
||||
task sourcesJar(type: Jar, dependsOn: classes) {
|
||||
classifier = 'sources'
|
||||
from sourceSets.main.allSource
|
||||
}
|
||||
|
||||
// Protobuf files would uselessly end up in the JAR otherwise, see
|
||||
// https://github.com/google/protobuf-gradle-plugin/issues/390
|
||||
tasks.withType(Jar).configureEach {
|
||||
exclude '**/*.proto'
|
||||
includeEmptyDirs false
|
||||
}
|
||||
|
||||
tasks.withType(Test).configureEach {
|
||||
tasks.withType(Test) {
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
showStandardStreams = true
|
||||
@ -67,18 +39,12 @@ subprojects {
|
||||
}
|
||||
|
||||
// https://discuss.gradle.org/t/best-approach-gradle-multi-module-project-generate-just-one-global-javadoc/18657/21
|
||||
tasks.register('aggregatedJavadocs', Javadoc) {
|
||||
destinationDir = layout.buildDirectory.file("docs/javadoc").get().asFile
|
||||
task aggregatedJavadocs(type: Javadoc, group: 'Documentation') {
|
||||
destinationDir = file("$buildDir/docs/javadoc")
|
||||
title = "$project.name $version"
|
||||
// options.memberLevel = JavadocMemberLevel.PRIVATE
|
||||
options.links 'https://docs.oracle.com/javase/11/docs/api/'
|
||||
options.links 'https://docs.oracle.com/javase/7/docs/api/'
|
||||
options.encoding 'UTF-8'
|
||||
// Fixes unknown tag @implNote; the other two were added precautionary
|
||||
options.tags = [
|
||||
"apiNote:a:API Note:",
|
||||
"implSpec:a:Implementation Requirements:",
|
||||
"implNote:a:Implementation Note:"
|
||||
]
|
||||
|
||||
subprojects.each { project ->
|
||||
project.tasks.withType(Javadoc).each { javadocTask ->
|
||||
|
@ -1,195 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE module PUBLIC
|
||||
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||
<module name="Checker">
|
||||
<!--
|
||||
If you set the basedir property below, then all reported file
|
||||
names will be relative to the specified directory. See
|
||||
https://checkstyle.org/5.x/config.html#Checker
|
||||
|
||||
<property name="basedir" value="${basedir}"/>
|
||||
-->
|
||||
<property name="severity" value="error"/>
|
||||
|
||||
<property name="fileExtensions" value="java, properties, xml"/>
|
||||
|
||||
<!-- Excludes all 'module-info.java' files -->
|
||||
<!-- See https://checkstyle.org/config_filefilters.html -->
|
||||
<module name="BeforeExecutionExclusionFileFilter">
|
||||
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that a package-info.java file exists for each package. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html#JavadocPackage -->
|
||||
<!--<module name="JavadocPackage"/>-->
|
||||
|
||||
<!-- Checks whether files end with a new line. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html#NewlineAtEndOfFile -->
|
||||
<module name="NewlineAtEndOfFile"/>
|
||||
|
||||
<!-- Checks that property files contain the same keys. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html#Translation -->
|
||||
<module name="Translation"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See https://checkstyle.org/config_sizes.html -->
|
||||
<module name="FileLength"/>
|
||||
<module name="LineLength">
|
||||
<property name="max" value="100"/>
|
||||
<property name="fileExtensions" value="java"/>
|
||||
<!-- Also allow links in javadocs to be longer (the default would just cover imports) -->
|
||||
<property name="ignorePattern" value="^((package|import) .*)|( *\* (@see )?<a href ?\= ?".*">)$"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See https://checkstyle.org/config_whitespace.html -->
|
||||
<module name="FileTabCharacter"/>
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="\s+$"/>
|
||||
<property name="minimum" value="0"/>
|
||||
<property name="maximum" value="0"/>
|
||||
<property name="message" value="Line has trailing spaces."/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for Headers -->
|
||||
<!-- See https://checkstyle.org/config_header.html -->
|
||||
<!-- <module name="Header"> -->
|
||||
<!-- <property name="headerFile" value="${checkstyle.header.file}"/> -->
|
||||
<!-- <property name="fileExtensions" value="java"/> -->
|
||||
<!-- </module> -->
|
||||
|
||||
<module name="SuppressWarningsFilter" />
|
||||
|
||||
<module name="SuppressWithPlainTextCommentFilter"/>
|
||||
|
||||
<module name="TreeWalker">
|
||||
<!-- Checks for Javadoc comments. -->
|
||||
<!-- See https://checkstyle.org/config_javadoc.html -->
|
||||
<module name="InvalidJavadocPosition"/>
|
||||
<module name="JavadocMethod">
|
||||
<property name="allowMissingParamTags" value="true"/>
|
||||
<property name="allowMissingReturnTag" value="true"/>
|
||||
</module>
|
||||
<module name="JavadocType"/>
|
||||
<!--<module name="JavadocVariable"/>-->
|
||||
<module name="JavadocStyle">
|
||||
<property name="checkFirstSentence" value="false"/>
|
||||
</module>
|
||||
<!--<module name="MissingJavadocMethod"/>-->
|
||||
|
||||
<!-- Checks for Naming Conventions. -->
|
||||
<!-- See https://checkstyle.org/config_naming.html -->
|
||||
<module name="ConstantName"/>
|
||||
<module name="LocalFinalVariableName"/>
|
||||
<module name="LocalVariableName"/>
|
||||
<module name="MemberName">
|
||||
<property name="format" value="^(TAG|DEBUG|[a-z][a-zA-Z0-9]*)$"/>
|
||||
</module>
|
||||
<module name="MethodName"/>
|
||||
<module name="PackageName"/>
|
||||
<module name="ParameterName"/>
|
||||
<module name="StaticVariableName"/>
|
||||
<module name="TypeName"/>
|
||||
|
||||
<!-- Checks for imports -->
|
||||
<!-- See https://checkstyle.org/config_import.html -->
|
||||
<module name="AvoidStarImport"/>
|
||||
<module name="IllegalImport"> <!-- defaults to sun.* packages -->
|
||||
<property name="illegalClasses" value="
|
||||
org.jetbrains.annotations.Nullable,
|
||||
org.jetbrains.annotations.NotNull,
|
||||
androidx.annotation.Nullable,
|
||||
androidx.annotation.NonNull,
|
||||
io.reactivex.rxjava3.annotations.NonNull,
|
||||
io.reactivex.rxjava3.annotations.Nullable" />
|
||||
</module>
|
||||
<module name="RedundantImport"/>
|
||||
<module name="UnusedImports"/>
|
||||
|
||||
<!-- Checks for Size Violations. -->
|
||||
<!-- See https://checkstyle.org/config_sizes.html -->
|
||||
<module name="MethodLength">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
<module name="ParameterNumber">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for whitespace -->
|
||||
<!-- See https://checkstyle.org/config_whitespace.html -->
|
||||
<module name="EmptyForIteratorPad"/>
|
||||
<module name="GenericWhitespace"/>
|
||||
<module name="MethodParamPad"/>
|
||||
<module name="NoWhitespaceAfter"/>
|
||||
<module name="NoWhitespaceBefore"/>
|
||||
<module name="OperatorWrap"/>
|
||||
<module name="ParenPad"/>
|
||||
<module name="TypecastParenPad"/>
|
||||
<module name="WhitespaceAfter"/>
|
||||
<module name="WhitespaceAround"/>
|
||||
|
||||
<!-- Modifier Checks -->
|
||||
<!-- See https://checkstyle.org/config_modifiers.html -->
|
||||
<module name="ModifierOrder"/>
|
||||
<module name="RedundantModifier"/>
|
||||
|
||||
<!-- Checks for blocks. You know, those {}'s -->
|
||||
<!-- See https://checkstyle.org/config_blocks.html -->
|
||||
<module name="AvoidNestedBlocks"/>
|
||||
<module name="EmptyBlock"/>
|
||||
<module name="LeftCurly"/>
|
||||
<module name="NeedBraces"/>
|
||||
<module name="RightCurly"/>
|
||||
|
||||
<!-- Checks for common coding problems -->
|
||||
<!-- See https://checkstyle.org/config_coding.html -->
|
||||
<module name="EmptyStatement"/>
|
||||
<module name="EqualsHashCode">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>
|
||||
<module name="HiddenField">
|
||||
<property name="ignoreConstructorParameter" value="true"/>
|
||||
<property name="ignoreSetter" value="true"/>
|
||||
</module>
|
||||
<module name="IllegalInstantiation"/>
|
||||
<module name="InnerAssignment"/>
|
||||
<!--<module name="MagicNumber"/>-->
|
||||
<!--<module name="MissingSwitchDefault">
|
||||
<property name="severity" value="warning"/>
|
||||
</module>-->
|
||||
<module name="MultipleVariableDeclarations"/>
|
||||
<module name="SimplifyBooleanExpression"/>
|
||||
<module name="SimplifyBooleanReturn"/>
|
||||
<module name="FinalLocalVariable">
|
||||
<property name="tokens" value="VARIABLE_DEF,PARAMETER_DEF"/>
|
||||
<property name="validateEnhancedForLoopVariable" value="true"/>
|
||||
</module>
|
||||
|
||||
<!-- Checks for class design -->
|
||||
<!-- See https://checkstyle.org/config_design.html -->
|
||||
<!--<module name="DesignForExtension"/>-->
|
||||
<module name="FinalClass"/>
|
||||
<module name="HideUtilityClassConstructor"/>
|
||||
<module name="InterfaceIsType"/>
|
||||
<!--<module name="VisibilityModifier">
|
||||
<property name="ignoreAnnotationCanonicalNames" value="State,ColumnInfo"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>-->
|
||||
|
||||
<!-- Miscellaneous other checks. -->
|
||||
<!-- See https://checkstyle.org/config_misc.html -->
|
||||
<module name="ArrayTypeStyle"/>
|
||||
<module name="FinalParameters"/>
|
||||
<!--<module name="TodoComment">
|
||||
<property name="format" value="(TODO:|FIXME:)"/>
|
||||
<property name="severity" value="warning"/>
|
||||
</module>-->
|
||||
<module name="UpperEll"/>
|
||||
|
||||
<module name="SuppressWarningsHolder" />
|
||||
</module>
|
||||
</module>
|
15
copyright
Normal file
15
copyright
Normal file
@ -0,0 +1,15 @@
|
||||
Copyright: 2017 Christian Schabesberger <chris.schabesberger@mailbox.com>
|
||||
|
||||
License: GPL-3.0+
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
@ -1,71 +1,11 @@
|
||||
plugins {
|
||||
id "checkstyle"
|
||||
id "com.google.protobuf" version "0.9.5"
|
||||
}
|
||||
|
||||
test {
|
||||
// Pass on downloader type to tests for different CI jobs. See DownloaderFactory.java and ci.yml
|
||||
if (System.properties.containsKey('downloader')) {
|
||||
systemProperty('downloader', System.getProperty('downloader'))
|
||||
}
|
||||
useJUnitPlatform()
|
||||
dependsOn checkstyleMain // run checkstyle when testing
|
||||
}
|
||||
|
||||
checkstyle {
|
||||
getConfigDirectory().set(rootProject.file("checkstyle"))
|
||||
ignoreFailures false
|
||||
showViolations true
|
||||
toolVersion checkstyleVersion
|
||||
}
|
||||
|
||||
// Exclude Protobuf generated files from Checkstyle
|
||||
checkstyleMain.exclude("org/schabi/newpipe/extractor/services/youtube/protos")
|
||||
|
||||
checkstyleTest {
|
||||
enabled false // do not checkstyle test files
|
||||
}
|
||||
|
||||
ext {
|
||||
rhinoVersion = '1.8.0'
|
||||
protobufVersion = '4.32.0'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':timeago-parser')
|
||||
|
||||
implementation "com.github.TeamNewPipe:nanojson:$nanojsonVersion"
|
||||
implementation 'org.jsoup:jsoup:1.21.2'
|
||||
implementation "com.google.code.findbugs:jsr305:$jsr305Version"
|
||||
implementation "com.google.protobuf:protobuf-javalite:$protobufVersion"
|
||||
implementation 'com.github.TeamNewPipe:nanojson:1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751'
|
||||
implementation 'org.jsoup:jsoup:1.13.1'
|
||||
implementation 'org.mozilla:rhino:1.7.12'
|
||||
implementation 'com.github.spotbugs:spotbugs-annotations:4.0.2'
|
||||
implementation 'org.nibor.autolink:autolink:0.10.0'
|
||||
|
||||
implementation "org.mozilla:rhino:$rhinoVersion"
|
||||
implementation "org.mozilla:rhino-engine:$rhinoVersion"
|
||||
|
||||
checkstyle "com.puppycrawl.tools:checkstyle:$checkstyleVersion"
|
||||
|
||||
testImplementation platform("org.junit:junit-bom:$junitVersion")
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-params'
|
||||
|
||||
testImplementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
testImplementation 'com.google.code.gson:gson:2.13.1'
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = "com.google.protobuf:protoc:$protobufVersion"
|
||||
}
|
||||
|
||||
generateProtoTasks {
|
||||
all().configureEach { task ->
|
||||
task.builtins {
|
||||
java {
|
||||
option "lite"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
testImplementation 'junit:junit:4.13'
|
||||
}
|
||||
|
@ -10,15 +10,12 @@ import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class Extractor {
|
||||
/**
|
||||
* {@link StreamingService} currently related to this extractor.<br>
|
||||
* Useful for getting other things from a service (like the url handlers for
|
||||
* cleaning/accepting/get id from urls).
|
||||
* Useful for getting other things from a service (like the url handlers for cleaning/accepting/get id from urls).
|
||||
*/
|
||||
private final StreamingService service;
|
||||
private final LinkHandler linkHandler;
|
||||
@ -29,18 +26,19 @@ public abstract class Extractor {
|
||||
private ContentCountry forcedContentCountry = null;
|
||||
|
||||
private boolean pageFetched = false;
|
||||
// called like this to prevent checkstyle errors about "hiding a field"
|
||||
private final Downloader downloader;
|
||||
|
||||
protected Extractor(final StreamingService service, final LinkHandler linkHandler) {
|
||||
this.service = Objects.requireNonNull(service, "service is null");
|
||||
this.linkHandler = Objects.requireNonNull(linkHandler, "LinkHandler is null");
|
||||
this.downloader = Objects.requireNonNull(NewPipe.getDownloader(), "downloader is null");
|
||||
public Extractor(final StreamingService service, final LinkHandler linkHandler) {
|
||||
if (service == null) throw new NullPointerException("service is null");
|
||||
if (linkHandler == null) throw new NullPointerException("LinkHandler is null");
|
||||
this.service = service;
|
||||
this.linkHandler = linkHandler;
|
||||
this.downloader = NewPipe.getDownloader();
|
||||
if (downloader == null) throw new NullPointerException("downloader is null");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The {@link LinkHandler} of the current extractor object (e.g. a ChannelExtractor
|
||||
* should return a channel url handler).
|
||||
* @return The {@link LinkHandler} of the current extractor object (e.g. a ChannelExtractor should return a channel url handler).
|
||||
*/
|
||||
@Nonnull
|
||||
public LinkHandler getLinkHandler() {
|
||||
@ -54,17 +52,13 @@ public abstract class Extractor {
|
||||
* @throws ExtractionException if the pages content is not understood
|
||||
*/
|
||||
public void fetchPage() throws IOException, ExtractionException {
|
||||
if (pageFetched) {
|
||||
return;
|
||||
}
|
||||
if (pageFetched) return;
|
||||
onFetchPage(downloader);
|
||||
pageFetched = true;
|
||||
}
|
||||
|
||||
protected void assertPageFetched() {
|
||||
if (!pageFetched) {
|
||||
throw new IllegalStateException("Page is not fetched. Make sure you call fetchPage()");
|
||||
}
|
||||
if (!pageFetched) throw new IllegalStateException("Page is not fetched. Make sure you call fetchPage()");
|
||||
}
|
||||
|
||||
protected boolean isPageFetched() {
|
||||
@ -74,13 +68,11 @@ public abstract class Extractor {
|
||||
/**
|
||||
* Fetch the current page.
|
||||
*
|
||||
* @param downloader the downloader to use
|
||||
* @param downloader the download to use
|
||||
* @throws IOException if the page can not be loaded
|
||||
* @throws ExtractionException if the pages content is not understood
|
||||
*/
|
||||
@SuppressWarnings("HiddenField")
|
||||
public abstract void onFetchPage(@Nonnull Downloader downloader)
|
||||
throws IOException, ExtractionException;
|
||||
public abstract void onFetchPage(@Nonnull Downloader downloader) throws IOException, ExtractionException;
|
||||
|
||||
@Nonnull
|
||||
public String getId() throws ParsingException {
|
||||
@ -128,11 +120,11 @@ public abstract class Extractor {
|
||||
// Localization
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public void forceLocalization(final Localization localization) {
|
||||
public void forceLocalization(Localization localization) {
|
||||
this.forcedLocalization = localization;
|
||||
}
|
||||
|
||||
public void forceContentCountry(final ContentCountry contentCountry) {
|
||||
public void forceContentCountry(ContentCountry contentCountry) {
|
||||
this.forcedContentCountry = contentCountry;
|
||||
}
|
||||
|
||||
@ -143,8 +135,7 @@ public abstract class Extractor {
|
||||
|
||||
@Nonnull
|
||||
public ContentCountry getExtractorContentCountry() {
|
||||
return forcedContentCountry == null ? getService().getContentCountry()
|
||||
: forcedContentCountry;
|
||||
return forcedContentCountry == null ? getService().getContentCountry() : forcedContentCountry;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -1,211 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class representing images in the extractor.
|
||||
*
|
||||
* <p>
|
||||
* An image has four properties: its URL, its height, its width and its estimated quality level.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Depending of the services, the height, the width or both properties may be not known.
|
||||
* Implementations <b>must use</b> the relevant unknown constants in this case
|
||||
* ({@link #HEIGHT_UNKNOWN} and {@link #WIDTH_UNKNOWN}), to ensure properly the lack of knowledge
|
||||
* of one or both of these properties to extractor clients.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* They should also respect the ranges defined in the estimated image resolution levels as much as
|
||||
* possible, to ensure consistency to extractor clients.
|
||||
* </p>
|
||||
*/
|
||||
public final class Image implements Serializable {
|
||||
|
||||
/**
|
||||
* Constant representing that the height of an {@link Image} is unknown.
|
||||
*/
|
||||
public static final int HEIGHT_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* Constant representing that the width of an {@link Image} is unknown.
|
||||
*/
|
||||
public static final int WIDTH_UNKNOWN = -1;
|
||||
|
||||
@Nonnull
|
||||
private final String url;
|
||||
private final int height;
|
||||
private final int width;
|
||||
@Nonnull
|
||||
private final ResolutionLevel estimatedResolutionLevel;
|
||||
|
||||
/**
|
||||
* Construct an {@link Image} instance.
|
||||
*
|
||||
* @param url the URL to the image, which should be not null or empty
|
||||
* @param height the image's height
|
||||
* @param width the image's width
|
||||
* @param estimatedResolutionLevel the image's estimated resolution level, which must not be
|
||||
* null
|
||||
* @throws NullPointerException if {@code estimatedResolutionLevel} is null
|
||||
*/
|
||||
public Image(@Nonnull final String url,
|
||||
final int height,
|
||||
final int width,
|
||||
@Nonnull final ResolutionLevel estimatedResolutionLevel)
|
||||
throws NullPointerException {
|
||||
this.url = url;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.estimatedResolutionLevel = Objects.requireNonNull(
|
||||
estimatedResolutionLevel, "estimatedResolutionLevel is null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL of this {@link Image}.
|
||||
*
|
||||
* @return the {@link Image}'s URL.
|
||||
*/
|
||||
@Nonnull
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the height of this {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link #HEIGHT_UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link Image}'s height or {@link #HEIGHT_UNKNOWN}
|
||||
*/
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of this {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link #WIDTH_UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the {@link Image}'s width or {@link #WIDTH_UNKNOWN}
|
||||
*/
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated resolution level of this image.
|
||||
*
|
||||
* <p>
|
||||
* If it is unknown, {@link ResolutionLevel#UNKNOWN} is returned instead.
|
||||
* </p>
|
||||
*
|
||||
* @return the estimated resolution level, which is never {@code null}
|
||||
* @see ResolutionLevel
|
||||
*/
|
||||
@Nonnull
|
||||
public ResolutionLevel getEstimatedResolutionLevel() {
|
||||
return estimatedResolutionLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string representation of this {@link Image} instance.
|
||||
*
|
||||
* <p>
|
||||
* The representation will be in the following format, where {@code url}, {@code height},
|
||||
* {@code width} and {@code estimatedResolutionLevel} represent the corresponding properties:
|
||||
* <br>
|
||||
* <br>
|
||||
* {@code Image {url=url, height='height, width=width,
|
||||
* estimatedResolutionLevel=estimatedResolutionLevel}'}
|
||||
* </p>
|
||||
*
|
||||
* @return a string representation of this {@link Image} instance
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Image {" + "url=" + url + ", height=" + height + ", width=" + width
|
||||
+ ", estimatedResolutionLevel=" + estimatedResolutionLevel + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* The estimated resolution level of an {@link Image}.
|
||||
*
|
||||
* <p>
|
||||
* Some services don't return the size of their images, but we may know for a specific image
|
||||
* type that a service returns, according to real data, an approximation of the resolution
|
||||
* level.
|
||||
* </p>
|
||||
*/
|
||||
public enum ResolutionLevel {
|
||||
|
||||
/**
|
||||
* The high resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height greater than or equal to 720px.
|
||||
* </p>
|
||||
*/
|
||||
HIGH,
|
||||
|
||||
/**
|
||||
* The medium resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height between 175px inclusive and 720px exclusive.
|
||||
* </p>
|
||||
*/
|
||||
MEDIUM,
|
||||
|
||||
/**
|
||||
* The low resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This level applies to images with a height between 1px inclusive and 175px exclusive.
|
||||
* </p>
|
||||
*/
|
||||
LOW,
|
||||
|
||||
/**
|
||||
* The unknown resolution level.
|
||||
*
|
||||
* <p>
|
||||
* This value is returned when the extractor doesn't know what resolution level an image
|
||||
* could have, for example if the extractor loops in an array of images with different
|
||||
* resolution levels without knowing the height.
|
||||
* </p>
|
||||
*/
|
||||
UNKNOWN;
|
||||
|
||||
/**
|
||||
* Get a {@link ResolutionLevel} based from the given height.
|
||||
*
|
||||
* @param heightPx the height from which returning the good {@link ResolutionLevel}
|
||||
* @return the {@link ResolutionLevel} corresponding to the height provided. See the
|
||||
* {@link ResolutionLevel} values for details about what value is returned.
|
||||
*/
|
||||
public static ResolutionLevel fromHeight(final int heightPx) {
|
||||
if (heightPx <= 0) {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
if (heightPx < 175) {
|
||||
return LOW;
|
||||
}
|
||||
|
||||
if (heightPx < 720) {
|
||||
return MEDIUM;
|
||||
}
|
||||
|
||||
return HIGH;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -17,8 +16,7 @@ public abstract class Info implements Serializable {
|
||||
*/
|
||||
private final String id;
|
||||
/**
|
||||
* Different than the {@link #originalUrl} in the sense that it <i>may</i> be set as a cleaned
|
||||
* url.
|
||||
* Different than the {@link #originalUrl} in the sense that it <i>may</i> be set as a cleaned url.
|
||||
*
|
||||
* @see LinkHandler#getUrl()
|
||||
* @see Extractor#getOriginalUrl()
|
||||
@ -34,19 +32,15 @@ public abstract class Info implements Serializable {
|
||||
|
||||
private final List<Throwable> errors = new ArrayList<>();
|
||||
|
||||
public void addError(final Throwable throwable) {
|
||||
public void addError(Throwable throwable) {
|
||||
this.errors.add(throwable);
|
||||
}
|
||||
|
||||
public void addAllErrors(final Collection<Throwable> throwables) {
|
||||
this.errors.addAll(throwables);
|
||||
public void addAllErrors(Collection<Throwable> errors) {
|
||||
this.errors.addAll(errors);
|
||||
}
|
||||
|
||||
public Info(final int serviceId,
|
||||
final String id,
|
||||
final String url,
|
||||
final String originalUrl,
|
||||
final String name) {
|
||||
public Info(int serviceId, String id, String url, String originalUrl, String name) {
|
||||
this.serviceId = serviceId;
|
||||
this.id = id;
|
||||
this.url = url;
|
||||
@ -54,7 +48,7 @@ public abstract class Info implements Serializable {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Info(final int serviceId, final LinkHandler linkHandler, final String name) {
|
||||
public Info(int serviceId, LinkHandler linkHandler, String name) {
|
||||
this(serviceId,
|
||||
linkHandler.getId(),
|
||||
linkHandler.getUrl(),
|
||||
@ -64,31 +58,20 @@ public abstract class Info implements Serializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final String ifDifferentString
|
||||
= url.equals(originalUrl) ? "" : " (originalUrl=\"" + originalUrl + "\")";
|
||||
return getClass().getSimpleName() + "[url=\"" + url + "\"" + ifDifferentString
|
||||
+ ", name=\"" + name + "\"]";
|
||||
final String ifDifferentString = !url.equals(originalUrl) ? " (originalUrl=\"" + originalUrl + "\")" : "";
|
||||
return getClass().getSimpleName() + "[url=\"" + url + "\"" + ifDifferentString + ", name=\"" + name + "\"]";
|
||||
}
|
||||
|
||||
// if you use an api and want to handle the website url
|
||||
// overriding original url is essential
|
||||
public void setOriginalUrl(final String originalUrl) {
|
||||
this.originalUrl = originalUrl;
|
||||
public void setOriginalUrl(String url) {
|
||||
originalUrl = url;
|
||||
}
|
||||
|
||||
public int getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public StreamingService getService() {
|
||||
try {
|
||||
return NewPipe.getService(serviceId);
|
||||
} catch (final ExtractionException e) {
|
||||
// this should be unreachable, as serviceId certainly refers to a valid service
|
||||
throw new RuntimeException("Info object has invalid service id", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -1,41 +1,35 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 11.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItem.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItem.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class InfoItem implements Serializable {
|
||||
private final InfoType infoType;
|
||||
private final int serviceId;
|
||||
private final String url;
|
||||
private final String name;
|
||||
@Nonnull
|
||||
private List<Image> thumbnails = List.of();
|
||||
private String thumbnailUrl;
|
||||
|
||||
public InfoItem(final InfoType infoType,
|
||||
final int serviceId,
|
||||
final String url,
|
||||
final String name) {
|
||||
public InfoItem(InfoType infoType, int serviceId, String url, String name) {
|
||||
this.infoType = infoType;
|
||||
this.serviceId = serviceId;
|
||||
this.url = url;
|
||||
@ -58,13 +52,12 @@ public abstract class InfoItem implements Serializable {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||
this.thumbnails = thumbnails;
|
||||
public void setThumbnailUrl(String thumbnailUrl) {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() {
|
||||
return thumbnails;
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,12 +2,8 @@ package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public interface InfoItemExtractor {
|
||||
String getName() throws ParsingException;
|
||||
String getUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
List<Image> getThumbnails() throws ParsingException;
|
||||
String getThumbnailUrl() throws ParsingException;
|
||||
}
|
||||
|
@ -3,63 +3,46 @@ package org.schabi.newpipe.extractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsCollector.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsCollector.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemExtractor>
|
||||
implements Collector<I, E> {
|
||||
public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemExtractor> implements Collector<I, E> {
|
||||
|
||||
private final List<I> itemList = new ArrayList<>();
|
||||
private final List<Throwable> errors = new ArrayList<>();
|
||||
private final int serviceId;
|
||||
@Nullable
|
||||
private final Comparator<I> comparator;
|
||||
|
||||
/**
|
||||
* Create a new collector with no comparator / sorting function
|
||||
* @param serviceId the service id
|
||||
*/
|
||||
public InfoItemsCollector(final int serviceId) {
|
||||
this(serviceId, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new collector
|
||||
* @param serviceId the service id
|
||||
*/
|
||||
public InfoItemsCollector(final int serviceId, @Nullable final Comparator<I> comparator) {
|
||||
public InfoItemsCollector(int serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
this.comparator = comparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<I> getItems() {
|
||||
if (comparator != null) {
|
||||
itemList.sort(comparator);
|
||||
}
|
||||
return Collections.unmodifiableList(itemList);
|
||||
}
|
||||
|
||||
@ -78,7 +61,7 @@ public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemE
|
||||
* Add an error
|
||||
* @param error the error
|
||||
*/
|
||||
protected void addError(final Exception error) {
|
||||
protected void addError(Exception error) {
|
||||
errors.add(error);
|
||||
}
|
||||
|
||||
@ -86,7 +69,7 @@ public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemE
|
||||
* Add an item
|
||||
* @param item the item
|
||||
*/
|
||||
protected void addItem(final I item) {
|
||||
protected void addItem(I item) {
|
||||
itemList.add(item);
|
||||
}
|
||||
|
||||
@ -99,12 +82,12 @@ public abstract class InfoItemsCollector<I extends InfoItem, E extends InfoItemE
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(final E extractor) {
|
||||
public void commit(E extractor) {
|
||||
try {
|
||||
addItem(extract(extractor));
|
||||
} catch (final FoundAdException ae) {
|
||||
} catch (FoundAdException ae) {
|
||||
// found an ad. Maybe a debug line could be placed here
|
||||
} catch (final ParsingException e) {
|
||||
} catch (ParsingException e) {
|
||||
addError(e);
|
||||
}
|
||||
}
|
||||
|
@ -2,60 +2,73 @@ package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
/**
|
||||
* Base class to extractors that have a list (e.g. playlists, users).
|
||||
* @param <R> the info item type this list extractor provides
|
||||
*/
|
||||
public abstract class ListExtractor<R extends InfoItem> extends Extractor {
|
||||
|
||||
/**
|
||||
* Constant that should be returned whenever
|
||||
* a list has an unknown number of items.
|
||||
*/
|
||||
public static final long ITEM_COUNT_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* Constant that should be returned whenever a list has an
|
||||
* infinite number of items. For example a YouTube mix.
|
||||
*/
|
||||
public static final long ITEM_COUNT_INFINITE = -2;
|
||||
|
||||
/**
|
||||
* Constant that should be returned whenever a list
|
||||
* has an unknown number of items bigger than 100.
|
||||
*/
|
||||
public static final long ITEM_COUNT_MORE_THAN_100 = -3;
|
||||
|
||||
public ListExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
public ListExtractor(StreamingService service, ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link InfoItemsPage InfoItemsPage} corresponding to the initial page
|
||||
* where the items are from the initial request and the nextPage relative to it.
|
||||
* A {@link InfoItemsPage InfoItemsPage} corresponding to the initial page where the items are from the initial request and
|
||||
* the nextPageUrl relative to it.
|
||||
*
|
||||
* @return a {@link InfoItemsPage} corresponding to the initial page
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract InfoItemsPage<R> getInitialPage() throws IOException, ExtractionException;
|
||||
|
||||
/**
|
||||
* Returns an url that can be used to get the next page relative to the initial one.
|
||||
* <p>Usually, these links will only work in the implementation itself.</p>
|
||||
*
|
||||
* @return an url pointing to the next page relative to the initial page
|
||||
* @see #getPage(String)
|
||||
*/
|
||||
public abstract String getNextPageUrl() throws IOException, ExtractionException;
|
||||
|
||||
/**
|
||||
* Get a list of items corresponding to the specific requested page.
|
||||
*
|
||||
* @param page any page got from the exclusive implementation of the list extractor
|
||||
* @param pageUrl any page url got from the exclusive implementation of the list extractor
|
||||
* @return a {@link InfoItemsPage} corresponding to the requested page
|
||||
* @see InfoItemsPage#getNextPage()
|
||||
* @see #getNextPageUrl()
|
||||
* @see InfoItemsPage#getNextPageUrl()
|
||||
*/
|
||||
public abstract InfoItemsPage<R> getPage(Page page) throws IOException, ExtractionException;
|
||||
public abstract InfoItemsPage<R> getPage(final String pageUrl) throws IOException, ExtractionException;
|
||||
|
||||
public boolean hasNextPage() throws IOException, ExtractionException {
|
||||
return !isNullOrEmpty(getNextPageUrl());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ListLinkHandler getLinkHandler() {
|
||||
return (ListLinkHandler) super.getLinkHandler();
|
||||
@ -67,27 +80,23 @@ public abstract class ListExtractor<R extends InfoItem> extends Extractor {
|
||||
|
||||
/**
|
||||
* A class that is used to wrap a list of gathered items and eventual errors, it
|
||||
* also contains a field that points to the next available page ({@link #nextPage}).
|
||||
* @param <T> the info item type that this page is supposed to store and provide
|
||||
* also contains a field that points to the next available page ({@link #nextPageUrl}).
|
||||
*/
|
||||
public static class InfoItemsPage<T extends InfoItem> {
|
||||
private static final InfoItemsPage<InfoItem> EMPTY = new InfoItemsPage<>(
|
||||
Collections.emptyList(),
|
||||
null,
|
||||
Collections.emptyList()
|
||||
);
|
||||
private static final InfoItemsPage<InfoItem> EMPTY =
|
||||
new InfoItemsPage<>(Collections.<InfoItem>emptyList(), "", Collections.<Throwable>emptyList());
|
||||
|
||||
/**
|
||||
* A convenient method that returns a representation of an empty page.
|
||||
*
|
||||
* @return a type-safe page with the list of items and errors empty and the nextPage set to
|
||||
* {@code null}.
|
||||
* @return a type-safe page with the list of items and errors empty and the nextPageUrl set to an empty string.
|
||||
*/
|
||||
public static <T extends InfoItem> InfoItemsPage<T> emptyPage() {
|
||||
//noinspection unchecked
|
||||
return (InfoItemsPage<T>) EMPTY;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The current list of items of this page
|
||||
*/
|
||||
@ -96,48 +105,40 @@ public abstract class ListExtractor<R extends InfoItem> extends Extractor {
|
||||
/**
|
||||
* Url pointing to the next page relative to this one
|
||||
*
|
||||
* @see ListExtractor#getPage(Page)
|
||||
* @see Page
|
||||
* @see ListExtractor#getPage(String)
|
||||
*/
|
||||
@Nullable
|
||||
private final Page nextPage;
|
||||
private final String nextPageUrl;
|
||||
|
||||
/**
|
||||
* Errors that happened during the extraction
|
||||
*/
|
||||
private final List<Throwable> errors;
|
||||
|
||||
public InfoItemsPage(final InfoItemsCollector<T, ?> collector,
|
||||
@Nullable final Page nextPage) {
|
||||
this(collector.getItems(), nextPage, collector.getErrors());
|
||||
public InfoItemsPage(InfoItemsCollector<T, ?> collector, String nextPageUrl) {
|
||||
this(collector.getItems(), nextPageUrl, collector.getErrors());
|
||||
}
|
||||
|
||||
public InfoItemsPage(final List<T> itemsList,
|
||||
@Nullable final Page nextPage,
|
||||
final List<Throwable> errors) {
|
||||
public InfoItemsPage(List<T> itemsList, String nextPageUrl, List<Throwable> errors) {
|
||||
this.itemsList = itemsList;
|
||||
this.nextPage = nextPage;
|
||||
this.nextPageUrl = nextPageUrl;
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public boolean hasNextPage() {
|
||||
return Page.isValid(nextPage);
|
||||
return !isNullOrEmpty(nextPageUrl);
|
||||
}
|
||||
|
||||
public List<T> getItems() {
|
||||
return itemsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next page if available, or null otherwise
|
||||
*/
|
||||
@Nullable
|
||||
public Page getNextPage() {
|
||||
return nextPage;
|
||||
public String getNextPageUrl() {
|
||||
return nextPageUrl;
|
||||
}
|
||||
|
||||
public List<Throwable> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,27 +4,27 @@ import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public abstract class ListInfo<T extends InfoItem> extends Info {
|
||||
private List<T> relatedItems;
|
||||
private Page nextPage = null;
|
||||
private String nextPageUrl = null;
|
||||
private final List<String> contentFilters;
|
||||
private final String sortFilter;
|
||||
|
||||
public ListInfo(final int serviceId,
|
||||
final String id,
|
||||
final String url,
|
||||
final String originalUrl,
|
||||
final String name,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
public ListInfo(int serviceId,
|
||||
String id,
|
||||
String url,
|
||||
String originalUrl,
|
||||
String name,
|
||||
List<String> contentFilter,
|
||||
String sortFilter) {
|
||||
super(serviceId, id, url, originalUrl, name);
|
||||
this.contentFilters = contentFilter;
|
||||
this.sortFilter = sortFilter;
|
||||
}
|
||||
|
||||
public ListInfo(final int serviceId,
|
||||
final ListLinkHandler listUrlIdHandler,
|
||||
final String name) {
|
||||
public ListInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) {
|
||||
super(serviceId, listUrlIdHandler, name);
|
||||
this.contentFilters = listUrlIdHandler.getContentFilters();
|
||||
this.sortFilter = listUrlIdHandler.getSortFilter();
|
||||
@ -34,20 +34,20 @@ public abstract class ListInfo<T extends InfoItem> extends Info {
|
||||
return relatedItems;
|
||||
}
|
||||
|
||||
public void setRelatedItems(final List<T> relatedItems) {
|
||||
public void setRelatedItems(List<T> relatedItems) {
|
||||
this.relatedItems = relatedItems;
|
||||
}
|
||||
|
||||
public boolean hasNextPage() {
|
||||
return Page.isValid(nextPage);
|
||||
return !isNullOrEmpty(nextPageUrl);
|
||||
}
|
||||
|
||||
public Page getNextPage() {
|
||||
return nextPage;
|
||||
public String getNextPageUrl() {
|
||||
return nextPageUrl;
|
||||
}
|
||||
|
||||
public void setNextPage(final Page page) {
|
||||
this.nextPage = page;
|
||||
public void setNextPageUrl(String pageUrl) {
|
||||
this.nextPageUrl = pageUrl;
|
||||
}
|
||||
|
||||
public List<String> getContentFilters() {
|
||||
|
@ -3,41 +3,32 @@ package org.schabi.newpipe.extractor;
|
||||
/*
|
||||
* Created by Adam Howard on 08/11/15.
|
||||
*
|
||||
* Copyright (c) 2015 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* and Adam Howard <achdisposable1@gmail.com>
|
||||
* Copyright (c) Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* and Adam Howard <achdisposable1@gmail.com> 2015
|
||||
*
|
||||
* MediaFormat.java is part of NewPipe Extractor.
|
||||
* MediaFormat.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Static data about various media formats support by NewPipe, eg mime type, extension
|
||||
*/
|
||||
|
||||
@SuppressWarnings("MethodParamPad") // we want the media format table below to be aligned
|
||||
public enum MediaFormat {
|
||||
// @formatter:off
|
||||
//video and audio combined formats
|
||||
// id name suffix mimeType
|
||||
// id name suffix mime type
|
||||
MPEG_4 (0x0, "MPEG-4", "mp4", "video/mp4"),
|
||||
v3GPP (0x10, "3GPP", "3gp", "video/3gpp"),
|
||||
WEBM (0x20, "WebM", "webm", "video/webm"),
|
||||
@ -45,116 +36,82 @@ public enum MediaFormat {
|
||||
M4A (0x100, "m4a", "m4a", "audio/mp4"),
|
||||
WEBMA (0x200, "WebM", "webm", "audio/webm"),
|
||||
MP3 (0x300, "MP3", "mp3", "audio/mpeg"),
|
||||
MP2 (0x310, "MP2", "mp2", "audio/mpeg"),
|
||||
OPUS (0x400, "opus", "opus", "audio/opus"),
|
||||
OGG (0x500, "ogg", "ogg", "audio/ogg"),
|
||||
WEBMA_OPUS(0x200, "WebM Opus", "webm", "audio/webm"),
|
||||
AIFF (0x600, "AIFF", "aiff", "audio/aiff"),
|
||||
/**
|
||||
* Same as {@link MediaFormat#AIFF}, just with the shorter suffix/file extension
|
||||
*/
|
||||
AIF (0x600, "AIFF", "aif", "audio/aiff"),
|
||||
WAV (0x700, "WAV", "wav", "audio/wav"),
|
||||
FLAC (0x800, "FLAC", "flac", "audio/flac"),
|
||||
ALAC (0x900, "ALAC", "alac", "audio/alac"),
|
||||
WEBMA_OPUS (0x200, "WebM Opus", "webm", "audio/webm"),
|
||||
// subtitles formats
|
||||
VTT (0x1000, "WebVTT", "vtt", "text/vtt"),
|
||||
TTML (0x2000, "Timed Text Markup Language", "ttml", "application/ttml+xml"),
|
||||
TRANSCRIPT1(0x3000, "TranScript v1", "srv1", "text/xml"),
|
||||
TRANSCRIPT2(0x4000, "TranScript v2", "srv2", "text/xml"),
|
||||
TRANSCRIPT3(0x5000, "TranScript v3", "srv3", "text/xml"),
|
||||
TRANSCRIPT1 (0x3000, "TranScript v1", "srv1", "text/xml"),
|
||||
TRANSCRIPT2 (0x4000, "TranScript v2", "srv2", "text/xml"),
|
||||
TRANSCRIPT3 (0x5000, "TranScript v3", "srv3", "text/xml"),
|
||||
SRT (0x6000, "SubRip file format", "srt", "text/srt");
|
||||
// @formatter:on
|
||||
|
||||
public final int id;
|
||||
@Nonnull
|
||||
public final String name;
|
||||
@Nonnull
|
||||
public final String suffix;
|
||||
@Nonnull
|
||||
public final String mimeType;
|
||||
|
||||
MediaFormat(final int id, @Nonnull final String name,
|
||||
@Nonnull final String suffix, @Nonnull final String mimeType) {
|
||||
MediaFormat(int id, String name, String suffix, String mimeType) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.suffix = suffix;
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
private static <T> T getById(final int id,
|
||||
final Function<MediaFormat, T> field,
|
||||
final T orElse) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.id == id)
|
||||
.map(field)
|
||||
.findFirst()
|
||||
.orElse(orElse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the friendly name of the media format with the supplied id
|
||||
*
|
||||
* @param id the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the friendly name of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getNameById(final int id) {
|
||||
return getById(id, MediaFormat::getName, "");
|
||||
public static String getNameById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if (vf.id == ident) return vf.name;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file extension of the media format with the supplied id
|
||||
*
|
||||
* @param id the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the file extension of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getSuffixById(final int id) {
|
||||
return getById(id, MediaFormat::getSuffix, "");
|
||||
public static String getSuffixById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if (vf.id == ident) return vf.suffix;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the MIME type of the media format with the supplied id
|
||||
*
|
||||
* @param id the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @param ident the id of the media format. Currently an arbitrary, NewPipe-specific number.
|
||||
* @return the MIME type of the MediaFormat associated with this ids,
|
||||
* or an empty String if none match it.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getMimeById(final int id) {
|
||||
return getById(id, MediaFormat::getMimeType, null);
|
||||
public static String getMimeById(int ident) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if (vf.id == ident) return vf.mimeType;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first {@link MediaFormat} with the supplied mime type.
|
||||
* There might be more formats which have the same mime type.
|
||||
* To retrieve those, use {@link #getAllFromMimeType(String)}.
|
||||
* Return the MediaFormat with the supplied mime type
|
||||
*
|
||||
* @return MediaFormat associated with this mime type,
|
||||
* or null if none match it.
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFromMimeType(final String mimeType) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.mimeType.equals(mimeType))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
public static MediaFormat getFromMimeType(String mimeType) {
|
||||
for (MediaFormat vf : MediaFormat.values()) {
|
||||
if (vf.mimeType.equals(mimeType)) return vf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all media formats which have the given mime type.
|
||||
* @param mimeType the mime type to search for
|
||||
* @return a modifiable {@link List} which contains the {@link MediaFormat}s
|
||||
* that have the given mime type.
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<MediaFormat> getAllFromMimeType(final String mimeType) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.mimeType.equals(mimeType))
|
||||
.collect(Collectors.toList());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -163,21 +120,18 @@ public enum MediaFormat {
|
||||
* @param id the id
|
||||
* @return the id of the media format or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFormatById(final int id) {
|
||||
return getById(id, mediaFormat -> mediaFormat, null);
|
||||
public static MediaFormat getFormatById(int id) {
|
||||
for (MediaFormat vf : values()) {
|
||||
if (vf.id == id) return vf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first media format that has the given suffix/file extension.
|
||||
* @return the matching {@link MediaFormat} or {@code null} if no associated format is found
|
||||
*/
|
||||
@Nullable
|
||||
public static MediaFormat getFromSuffix(final String suffix) {
|
||||
return Arrays.stream(MediaFormat.values())
|
||||
.filter(mediaFormat -> mediaFormat.suffix.equals(suffix))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
public static MediaFormat getFromSuffix(String suffix) {
|
||||
for (MediaFormat vf : values()) {
|
||||
if (vf.suffix.equals(suffix)) return vf;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,7 +139,6 @@ public enum MediaFormat {
|
||||
*
|
||||
* @return the name of the format
|
||||
*/
|
||||
@Nonnull
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@ -195,7 +148,6 @@ public enum MediaFormat {
|
||||
*
|
||||
* @return the filename extension
|
||||
*/
|
||||
@Nonnull
|
||||
public String getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
@ -205,7 +157,6 @@ public enum MediaFormat {
|
||||
*
|
||||
* @return the mime type
|
||||
*/
|
||||
@Nonnull
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
@ -1,78 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class MetaInfo implements Serializable {
|
||||
|
||||
private String title = "";
|
||||
private Description content;
|
||||
private List<URL> urls = new ArrayList<>();
|
||||
private List<String> urlTexts = new ArrayList<>();
|
||||
|
||||
public MetaInfo(@Nonnull final String title,
|
||||
@Nonnull final Description content,
|
||||
@Nonnull final List<URL> urls,
|
||||
@Nonnull final List<String> urlTexts) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
this.urls = urls;
|
||||
this.urlTexts = urlTexts;
|
||||
}
|
||||
|
||||
public MetaInfo() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Title of the info. Can be empty.
|
||||
*/
|
||||
@Nonnull
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(@Nonnull final String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Description getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(@Nonnull final Description content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<URL> getUrls() {
|
||||
return urls;
|
||||
}
|
||||
|
||||
public void setUrls(@Nonnull final List<URL> urls) {
|
||||
this.urls = urls;
|
||||
}
|
||||
|
||||
public void addUrl(@Nonnull final URL url) {
|
||||
urls.add(url);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getUrlTexts() {
|
||||
return urlTexts;
|
||||
}
|
||||
|
||||
public void setUrlTexts(@Nonnull final List<String> urlTexts) {
|
||||
this.urlTexts = urlTexts;
|
||||
}
|
||||
|
||||
public void addUrlText(@Nonnull final String urlText) {
|
||||
urlTexts.add(urlText);
|
||||
}
|
||||
}
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 23.08.15.
|
||||
*
|
||||
* Copyright (C) 2015 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* NewPipe Extractor.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* NewPipe.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
@ -25,15 +25,14 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides access to streaming services supported by NewPipe.
|
||||
*/
|
||||
public final class NewPipe {
|
||||
public class NewPipe {
|
||||
private static Downloader downloader;
|
||||
private static Localization preferredLocalization;
|
||||
private static ContentCountry preferredContentCountry;
|
||||
@ -41,16 +40,19 @@ public final class NewPipe {
|
||||
private NewPipe() {
|
||||
}
|
||||
|
||||
public static void init(final Downloader d) {
|
||||
init(d, Localization.DEFAULT);
|
||||
public static void init(Downloader d) {
|
||||
downloader = d;
|
||||
preferredLocalization = Localization.DEFAULT;
|
||||
preferredContentCountry = ContentCountry.DEFAULT;
|
||||
}
|
||||
|
||||
public static void init(final Downloader d, final Localization l) {
|
||||
init(d, l, l.getCountryCode().isEmpty()
|
||||
? ContentCountry.DEFAULT : new ContentCountry(l.getCountryCode()));
|
||||
public static void init(Downloader d, Localization l) {
|
||||
downloader = d;
|
||||
preferredLocalization = l;
|
||||
preferredContentCountry = l.getCountryCode().isEmpty() ? ContentCountry.DEFAULT : new ContentCountry(l.getCountryCode());
|
||||
}
|
||||
|
||||
public static void init(final Downloader d, final Localization l, final ContentCountry c) {
|
||||
public static void init(Downloader d, Localization l, ContentCountry c) {
|
||||
downloader = d;
|
||||
preferredLocalization = l;
|
||||
preferredContentCountry = c;
|
||||
@ -68,24 +70,26 @@ public final class NewPipe {
|
||||
return ServiceList.all();
|
||||
}
|
||||
|
||||
public static StreamingService getService(final int serviceId) throws ExtractionException {
|
||||
return ServiceList.all().stream()
|
||||
.filter(service -> service.getServiceId() == serviceId)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ExtractionException(
|
||||
"There's no service with the id = \"" + serviceId + "\""));
|
||||
public static StreamingService getService(int serviceId) throws ExtractionException {
|
||||
for (StreamingService service : ServiceList.all()) {
|
||||
if (service.getServiceId() == serviceId) {
|
||||
return service;
|
||||
}
|
||||
}
|
||||
throw new ExtractionException("There's no service with the id = \"" + serviceId + "\"");
|
||||
}
|
||||
|
||||
public static StreamingService getService(final String serviceName) throws ExtractionException {
|
||||
return ServiceList.all().stream()
|
||||
.filter(service -> service.getServiceInfo().getName().equals(serviceName))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ExtractionException(
|
||||
"There's no service with the name = \"" + serviceName + "\""));
|
||||
public static StreamingService getService(String serviceName) throws ExtractionException {
|
||||
for (StreamingService service : ServiceList.all()) {
|
||||
if (service.getServiceInfo().getName().equals(serviceName)) {
|
||||
return service;
|
||||
}
|
||||
}
|
||||
throw new ExtractionException("There's no service with the name = \"" + serviceName + "\"");
|
||||
}
|
||||
|
||||
public static StreamingService getServiceByUrl(final String url) throws ExtractionException {
|
||||
for (final StreamingService service : ServiceList.all()) {
|
||||
public static StreamingService getServiceByUrl(String url) throws ExtractionException {
|
||||
for (StreamingService service : ServiceList.all()) {
|
||||
if (service.getLinkTypeByUrl(url) != StreamingService.LinkType.NONE) {
|
||||
return service;
|
||||
}
|
||||
@ -93,25 +97,43 @@ public final class NewPipe {
|
||||
throw new ExtractionException("No service can handle the url = \"" + url + "\"");
|
||||
}
|
||||
|
||||
public static int getIdOfService(String serviceName) {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
return getService(serviceName).getServiceId();
|
||||
} catch (ExtractionException ignored) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getNameOfService(int id) {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
return getService(id).getServiceInfo().getName();
|
||||
} catch (Exception e) {
|
||||
System.err.println("Service id not known");
|
||||
e.printStackTrace();
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Localization
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public static void setupLocalization(final Localization thePreferredLocalization) {
|
||||
setupLocalization(thePreferredLocalization, null);
|
||||
public static void setupLocalization(Localization preferredLocalization) {
|
||||
setupLocalization(preferredLocalization, null);
|
||||
}
|
||||
|
||||
public static void setupLocalization(
|
||||
final Localization thePreferredLocalization,
|
||||
@Nullable final ContentCountry thePreferredContentCountry) {
|
||||
NewPipe.preferredLocalization = thePreferredLocalization;
|
||||
public static void setupLocalization(Localization preferredLocalization, @Nullable ContentCountry preferredContentCountry) {
|
||||
NewPipe.preferredLocalization = preferredLocalization;
|
||||
|
||||
if (thePreferredContentCountry != null) {
|
||||
NewPipe.preferredContentCountry = thePreferredContentCountry;
|
||||
if (preferredContentCountry != null) {
|
||||
NewPipe.preferredContentCountry = preferredContentCountry;
|
||||
} else {
|
||||
NewPipe.preferredContentCountry = thePreferredLocalization.getCountryCode().isEmpty()
|
||||
NewPipe.preferredContentCountry = preferredLocalization.getCountryCode().isEmpty()
|
||||
? ContentCountry.DEFAULT
|
||||
: new ContentCountry(thePreferredLocalization.getCountryCode());
|
||||
: new ContentCountry(preferredLocalization.getCountryCode());
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +142,7 @@ public final class NewPipe {
|
||||
return preferredLocalization == null ? Localization.DEFAULT : preferredLocalization;
|
||||
}
|
||||
|
||||
public static void setPreferredLocalization(final Localization preferredLocalization) {
|
||||
public static void setPreferredLocalization(Localization preferredLocalization) {
|
||||
NewPipe.preferredLocalization = preferredLocalization;
|
||||
}
|
||||
|
||||
@ -129,7 +151,7 @@ public final class NewPipe {
|
||||
return preferredContentCountry == null ? ContentCountry.DEFAULT : preferredContentCountry;
|
||||
}
|
||||
|
||||
public static void setPreferredContentCountry(final ContentCountry preferredContentCountry) {
|
||||
public static void setPreferredContentCountry(ContentCountry preferredContentCountry) {
|
||||
NewPipe.preferredContentCountry = preferredContentCountry;
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
public class Page implements Serializable {
|
||||
private final String url;
|
||||
private final String id;
|
||||
private final List<String> ids;
|
||||
private final Map<String, String> cookies;
|
||||
|
||||
@Nullable
|
||||
private final byte[] body;
|
||||
|
||||
public Page(final String url,
|
||||
final String id,
|
||||
final List<String> ids,
|
||||
final Map<String, String> cookies,
|
||||
@Nullable final byte[] body) {
|
||||
this.url = url;
|
||||
this.id = id;
|
||||
this.ids = ids;
|
||||
this.cookies = cookies;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Page(final String url) {
|
||||
this(url, null, null, null, null);
|
||||
}
|
||||
|
||||
public Page(final String url, final String id) {
|
||||
this(url, id, null, null, null);
|
||||
}
|
||||
|
||||
public Page(final String url, final String id, final byte[] body) {
|
||||
this(url, id, null, null, body);
|
||||
}
|
||||
|
||||
public Page(final String url, final byte[] body) {
|
||||
this(url, null, null, null, body);
|
||||
}
|
||||
|
||||
public Page(final String url, final Map<String, String> cookies) {
|
||||
this(url, null, null, cookies, null);
|
||||
}
|
||||
|
||||
public Page(final List<String> ids) {
|
||||
this(null, null, ids, null, null);
|
||||
}
|
||||
|
||||
public Page(final List<String> ids, final Map<String, String> cookies) {
|
||||
this(null, null, ids, cookies, null);
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<String> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Map<String, String> getCookies() {
|
||||
return cookies;
|
||||
}
|
||||
|
||||
public static boolean isValid(final Page page) {
|
||||
return page != null && (!isNullOrEmpty(page.getUrl())
|
||||
|| !isNullOrEmpty(page.getIds()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public byte[] getBody() {
|
||||
return body;
|
||||
}
|
||||
}
|
@ -1,52 +1,56 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.BandcampService;
|
||||
import org.schabi.newpipe.extractor.services.media_ccc.MediaCCCService;
|
||||
import org.schabi.newpipe.extractor.services.peertube.PeertubeService;
|
||||
import org.schabi.newpipe.extractor.services.soundcloud.SoundcloudService;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeService;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ServiceList.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
||||
* ServiceList.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A list of supported services.
|
||||
*/
|
||||
@SuppressWarnings({"ConstantName", "InnerAssignment"}) // keep unusual names and inner assignments
|
||||
public final class ServiceList {
|
||||
private ServiceList() {
|
||||
// no instance
|
||||
//no instance
|
||||
}
|
||||
|
||||
public static final YoutubeService YouTube = new YoutubeService(0);
|
||||
public static final SoundcloudService SoundCloud = new SoundcloudService(1);
|
||||
public static final MediaCCCService MediaCCC = new MediaCCCService(2);
|
||||
public static final PeertubeService PeerTube = new PeertubeService(3);
|
||||
public static final BandcampService Bandcamp = new BandcampService(4);
|
||||
public static final YoutubeService YouTube;
|
||||
public static final SoundcloudService SoundCloud;
|
||||
public static final MediaCCCService MediaCCC;
|
||||
public static final PeertubeService PeerTube;
|
||||
|
||||
/**
|
||||
* When creating a new service, put this service in the end of this list,
|
||||
* and give it the next free id.
|
||||
*/
|
||||
private static final List<StreamingService> SERVICES = List.of(
|
||||
YouTube, SoundCloud, MediaCCC, PeerTube, Bandcamp);
|
||||
private static final List<StreamingService> SERVICES = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
YouTube = new YoutubeService(0),
|
||||
SoundCloud = new SoundcloudService(1),
|
||||
MediaCCC = new MediaCCCService(2),
|
||||
PeerTube = new PeertubeService(3)
|
||||
));
|
||||
|
||||
/**
|
||||
* Get all the supported services.
|
||||
|
@ -1,18 +1,12 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.feed.FeedExtractor;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.*;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import org.schabi.newpipe.extractor.localization.TimeAgoParser;
|
||||
@ -22,28 +16,27 @@ import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* StreamingService.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2018 <chris.schabesberger@mailbox.org>
|
||||
* StreamingService.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class StreamingService {
|
||||
@ -61,7 +54,7 @@ public abstract class StreamingService {
|
||||
* @param name the name of the service
|
||||
* @param mediaCapabilities the type of media this service can handle
|
||||
*/
|
||||
public ServiceInfo(final String name, final List<MediaCapability> mediaCapabilities) {
|
||||
public ServiceInfo(String name, List<MediaCapability> mediaCapabilities) {
|
||||
this.name = name;
|
||||
this.mediaCapabilities = Collections.unmodifiableList(mediaCapabilities);
|
||||
}
|
||||
@ -80,8 +73,8 @@ public abstract class StreamingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* LinkType will be used to determine which type of URL you are handling, and therefore which
|
||||
* part of NewPipe should handle a certain URL.
|
||||
* LinkType will be used to determine which type of URL you are handling, and therefore which part
|
||||
* of NewPipe should handle a certain URL.
|
||||
*/
|
||||
public enum LinkType {
|
||||
NONE,
|
||||
@ -96,15 +89,14 @@ public abstract class StreamingService {
|
||||
/**
|
||||
* Creates a new Streaming service.
|
||||
* If you Implement one do not set id within your implementation of this extractor, instead
|
||||
* set the id when you put the extractor into {@link ServiceList}
|
||||
* set the id when you put the extractor into
|
||||
* <a href="https://teamnewpipe.github.io/NewPipeExtractor/javadoc/org/schabi/newpipe/extractor/ServiceList.html">ServiceList</a>.
|
||||
* All other parameters can be set directly from the overriding constructor.
|
||||
* @param id the number of the service to identify him within the NewPipe frontend
|
||||
* @param name the name of the service
|
||||
* @param capabilities the type of media this service can handle
|
||||
*/
|
||||
public StreamingService(final int id,
|
||||
final String name,
|
||||
final List<ServiceInfo.MediaCapability> capabilities) {
|
||||
public StreamingService(int id, String name, List<ServiceInfo.MediaCapability> capabilities) {
|
||||
this.serviceId = id;
|
||||
this.serviceInfo = new ServiceInfo(name, capabilities);
|
||||
}
|
||||
@ -141,14 +133,6 @@ public abstract class StreamingService {
|
||||
*/
|
||||
public abstract ListLinkHandlerFactory getChannelLHFactory();
|
||||
|
||||
/**
|
||||
* Must return a new instance of an implementation of ListLinkHandlerFactory for channel tabs.
|
||||
* If support for channel tabs is not given null must be returned.
|
||||
*
|
||||
* @return an instance of a ListLinkHandlerFactory for channel tabs or null
|
||||
*/
|
||||
public abstract ListLinkHandlerFactory getChannelTabLHFactory();
|
||||
|
||||
/**
|
||||
* Must return a new instance of an implementation of ListLinkHandlerFactory for playlists.
|
||||
* If support for playlists is not given null must be returned.
|
||||
@ -187,21 +171,22 @@ public abstract class StreamingService {
|
||||
public abstract SubscriptionExtractor getSubscriptionExtractor();
|
||||
|
||||
/**
|
||||
* This method decides which strategy will be chosen to fetch the feed. In YouTube, for example,
|
||||
* a separate feed exists which is lightweight and made specifically to be used like this.
|
||||
* This method decides which strategy will be chosen to fetch the feed. In YouTube, for example, a separate feed
|
||||
* exists which is lightweight and made specifically to be used like this.
|
||||
* <p>
|
||||
* In services which there's no other way to retrieve them, null should be returned.
|
||||
*
|
||||
* @return a {@link FeedExtractor} instance or null.
|
||||
*/
|
||||
@Nullable
|
||||
public FeedExtractor getFeedExtractor(final String url) throws ExtractionException {
|
||||
public FeedExtractor getFeedExtractor(String url) throws ExtractionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Must create a new instance of a KioskList implementation.
|
||||
* @return a new KioskList instance
|
||||
* @throws ExtractionException
|
||||
*/
|
||||
public abstract KioskList getKioskList() throws ExtractionException;
|
||||
|
||||
@ -209,61 +194,49 @@ public abstract class StreamingService {
|
||||
* Must create a new instance of a ChannelExtractor implementation.
|
||||
* @param linkHandler is pointing to the channel which should be handled by this new instance.
|
||||
* @return a new ChannelExtractor
|
||||
* @throws ExtractionException
|
||||
*/
|
||||
public abstract ChannelExtractor getChannelExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
|
||||
/**
|
||||
* Must create a new instance of a ChannelTabExtractor implementation.
|
||||
*
|
||||
* @param linkHandler is pointing to the channel which should be handled by this new instance.
|
||||
* @return a new ChannelTabExtractor
|
||||
*/
|
||||
public abstract ChannelTabExtractor getChannelTabExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
public abstract ChannelExtractor getChannelExtractor(ListLinkHandler linkHandler) throws ExtractionException;
|
||||
|
||||
/**
|
||||
* Must crete a new instance of a PlaylistExtractor implementation.
|
||||
* @param linkHandler is pointing to the playlist which should be handled by this new instance.
|
||||
* @return a new PlaylistExtractor
|
||||
* @throws ExtractionException
|
||||
*/
|
||||
public abstract PlaylistExtractor getPlaylistExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
public abstract PlaylistExtractor getPlaylistExtractor(ListLinkHandler linkHandler) throws ExtractionException;
|
||||
|
||||
/**
|
||||
* Must create a new instance of a StreamExtractor implementation.
|
||||
* @param linkHandler is pointing to the stream which should be handled by this new instance.
|
||||
* @return a new StreamExtractor
|
||||
* @throws ExtractionException
|
||||
*/
|
||||
public abstract StreamExtractor getStreamExtractor(LinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
public abstract StreamExtractor getStreamExtractor(LinkHandler linkHandler) throws ExtractionException;
|
||||
|
||||
public abstract CommentsExtractor getCommentsExtractor(ListLinkHandler linkHandler)
|
||||
throws ExtractionException;
|
||||
public abstract CommentsExtractor getCommentsExtractor(ListLinkHandler linkHandler) throws ExtractionException;
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Extractors without link handler
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public SearchExtractor getSearchExtractor(final String query,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ExtractionException {
|
||||
public SearchExtractor getSearchExtractor(String query,
|
||||
List<String> contentFilter,
|
||||
String sortFilter) throws ExtractionException {
|
||||
return getSearchExtractor(getSearchQHFactory()
|
||||
.fromQuery(query, contentFilter, sortFilter));
|
||||
}
|
||||
|
||||
public ChannelExtractor getChannelExtractor(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
throws ExtractionException {
|
||||
public ChannelExtractor getChannelExtractor(String id,
|
||||
List<String> contentFilter,
|
||||
String sortFilter) throws ExtractionException {
|
||||
return getChannelExtractor(getChannelLHFactory()
|
||||
.fromQuery(id, contentFilter, sortFilter));
|
||||
}
|
||||
|
||||
public PlaylistExtractor getPlaylistExtractor(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter)
|
||||
throws ExtractionException {
|
||||
public PlaylistExtractor getPlaylistExtractor(String id,
|
||||
List<String> contentFilter,
|
||||
String sortFilter) throws ExtractionException {
|
||||
return getPlaylistExtractor(getPlaylistLHFactory()
|
||||
.fromQuery(id, contentFilter, sortFilter));
|
||||
}
|
||||
@ -272,42 +245,28 @@ public abstract class StreamingService {
|
||||
// Short extractors overloads
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public SearchExtractor getSearchExtractor(final String query) throws ExtractionException {
|
||||
public SearchExtractor getSearchExtractor(String query) throws ExtractionException {
|
||||
return getSearchExtractor(getSearchQHFactory().fromQuery(query));
|
||||
}
|
||||
|
||||
public ChannelExtractor getChannelExtractor(final String url) throws ExtractionException {
|
||||
public ChannelExtractor getChannelExtractor(String url) throws ExtractionException {
|
||||
return getChannelExtractor(getChannelLHFactory().fromUrl(url));
|
||||
}
|
||||
|
||||
public ChannelTabExtractor getChannelTabExtractorFromId(final String id, final String tab)
|
||||
throws ExtractionException {
|
||||
return getChannelTabExtractor(getChannelTabLHFactory().fromQuery(
|
||||
id, Collections.singletonList(tab), ""));
|
||||
}
|
||||
|
||||
public ChannelTabExtractor getChannelTabExtractorFromIdAndBaseUrl(final String id,
|
||||
final String tab,
|
||||
final String baseUrl)
|
||||
throws ExtractionException {
|
||||
return getChannelTabExtractor(getChannelTabLHFactory().fromQuery(
|
||||
id, Collections.singletonList(tab), "", baseUrl));
|
||||
}
|
||||
|
||||
public PlaylistExtractor getPlaylistExtractor(final String url) throws ExtractionException {
|
||||
public PlaylistExtractor getPlaylistExtractor(String url) throws ExtractionException {
|
||||
return getPlaylistExtractor(getPlaylistLHFactory().fromUrl(url));
|
||||
}
|
||||
|
||||
public StreamExtractor getStreamExtractor(final String url) throws ExtractionException {
|
||||
public StreamExtractor getStreamExtractor(String url) throws ExtractionException {
|
||||
return getStreamExtractor(getStreamLHFactory().fromUrl(url));
|
||||
}
|
||||
|
||||
public CommentsExtractor getCommentsExtractor(final String url) throws ExtractionException {
|
||||
final ListLinkHandlerFactory listLinkHandlerFactory = getCommentsLHFactory();
|
||||
if (listLinkHandlerFactory == null) {
|
||||
public CommentsExtractor getCommentsExtractor(String url) throws ExtractionException {
|
||||
ListLinkHandlerFactory llhf = getCommentsLHFactory();
|
||||
if (llhf == null) {
|
||||
return null;
|
||||
}
|
||||
return getCommentsExtractor(listLinkHandlerFactory.fromUrl(url));
|
||||
return getCommentsExtractor(llhf.fromUrl(url));
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
@ -318,19 +277,18 @@ public abstract class StreamingService {
|
||||
* Figures out where the link is pointing to (a channel, a video, a playlist, etc.)
|
||||
* @param url the url on which it should be decided of which link type it is
|
||||
* @return the link type of url
|
||||
* @throws ParsingException
|
||||
*/
|
||||
public final LinkType getLinkTypeByUrl(final String url) throws ParsingException {
|
||||
final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
|
||||
public final LinkType getLinkTypeByUrl(String url) throws ParsingException {
|
||||
LinkHandlerFactory sH = getStreamLHFactory();
|
||||
LinkHandlerFactory cH = getChannelLHFactory();
|
||||
LinkHandlerFactory pH = getPlaylistLHFactory();
|
||||
|
||||
final LinkHandlerFactory sH = getStreamLHFactory();
|
||||
final LinkHandlerFactory cH = getChannelLHFactory();
|
||||
final LinkHandlerFactory pH = getPlaylistLHFactory();
|
||||
|
||||
if (sH != null && sH.acceptUrl(polishedUrl)) {
|
||||
if (sH != null && sH.acceptUrl(url)) {
|
||||
return LinkType.STREAM;
|
||||
} else if (cH != null && cH.acceptUrl(polishedUrl)) {
|
||||
} else if (cH != null && cH.acceptUrl(url)) {
|
||||
return LinkType.CHANNEL;
|
||||
} else if (pH != null && pH.acceptUrl(polishedUrl)) {
|
||||
} else if (pH != null && pH.acceptUrl(url)) {
|
||||
return LinkType.PLAYLIST;
|
||||
} else {
|
||||
return LinkType.NONE;
|
||||
@ -360,8 +318,7 @@ public abstract class StreamingService {
|
||||
* the user prefer (using {@link NewPipe#getPreferredLocalization()}), then it will:
|
||||
* <ul>
|
||||
* <li>Check if the exactly localization is supported by this service.</li>
|
||||
* <li>If not, check if a less specific localization is available, using only the language
|
||||
* code.</li>
|
||||
* <li>If not, check if a less specific localization is available, using only the language code.</li>
|
||||
* <li>Fallback to the {@link Localization#DEFAULT default} localization.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@ -374,9 +331,8 @@ public abstract class StreamingService {
|
||||
}
|
||||
|
||||
// Fallback to the first supported language that matches the preferred language
|
||||
for (final Localization supportedLanguage : getSupportedLocalizations()) {
|
||||
if (supportedLanguage.getLanguageCode()
|
||||
.equals(preferredLocalization.getLanguageCode())) {
|
||||
for (Localization supportedLanguage : getSupportedLocalizations()) {
|
||||
if (supportedLanguage.getLanguageCode().equals(preferredLocalization.getLanguageCode())) {
|
||||
return supportedLanguage;
|
||||
}
|
||||
}
|
||||
@ -385,8 +341,8 @@ public abstract class StreamingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the country that should be used to fetch content in this service. It will get which
|
||||
* country the user prefer (using {@link NewPipe#getPreferredContentCountry()}), then it will:
|
||||
* Returns the country that should be used to fetch content in this service. It will get which country
|
||||
* the user prefer (using {@link NewPipe#getPreferredContentCountry()}), then it will:
|
||||
* <ul>
|
||||
* <li>Check if the country is supported by this service.</li>
|
||||
* <li>If not, fallback to the {@link ContentCountry#DEFAULT default} country.</li>
|
||||
@ -403,15 +359,14 @@ public abstract class StreamingService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the time ago parser using the patterns related to the passed localization.
|
||||
* <br><br>
|
||||
* Just like {@link #getLocalization()}, it will also try to fallback to a less specific
|
||||
* localization if the exact one is not available/supported.
|
||||
* Get an instance of the time ago parser using the patterns related to the passed localization.<br>
|
||||
* <br>
|
||||
* Just like {@link #getLocalization()}, it will also try to fallback to a less specific localization if
|
||||
* the exact one is not available/supported.
|
||||
*
|
||||
* @throws IllegalArgumentException if the localization is not supported (parsing patterns are
|
||||
* not present).
|
||||
* @throws IllegalArgumentException if the localization is not supported (parsing patterns are not present).
|
||||
*/
|
||||
public TimeAgoParser getTimeAgoParser(final Localization localization) {
|
||||
public TimeAgoParser getTimeAgoParser(Localization localization) {
|
||||
final TimeAgoParser targetParser = TimeAgoPatternsManager.getTimeAgoParserFor(localization);
|
||||
|
||||
if (targetParser != null) {
|
||||
@ -419,18 +374,15 @@ public abstract class StreamingService {
|
||||
}
|
||||
|
||||
if (!localization.getCountryCode().isEmpty()) {
|
||||
final Localization lessSpecificLocalization
|
||||
= new Localization(localization.getLanguageCode());
|
||||
final TimeAgoParser lessSpecificParser
|
||||
= TimeAgoPatternsManager.getTimeAgoParserFor(lessSpecificLocalization);
|
||||
final Localization lessSpecificLocalization = new Localization(localization.getLanguageCode());
|
||||
final TimeAgoParser lessSpecificParser = TimeAgoPatternsManager.getTimeAgoParserFor(lessSpecificLocalization);
|
||||
|
||||
if (lessSpecificParser != null) {
|
||||
return lessSpecificParser;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
"Localization is not supported (\"" + localization + "\")");
|
||||
throw new IllegalArgumentException("Localization is not supported (\"" + localization.toString() + "\")");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,58 +1,43 @@
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 25.07.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelExtractor.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelExtractor.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
public abstract class ChannelExtractor extends ListExtractor<StreamInfoItem> {
|
||||
|
||||
import org.schabi.newpipe.extractor.Extractor;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class ChannelExtractor extends Extractor {
|
||||
|
||||
public static final long UNKNOWN_SUBSCRIBER_COUNT = -1;
|
||||
|
||||
protected ChannelExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
public ChannelExtractor(StreamingService service, ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public abstract List<Image> getAvatars() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getBanners() throws ParsingException;
|
||||
public abstract String getAvatarUrl() throws ParsingException;
|
||||
public abstract String getBannerUrl() throws ParsingException;
|
||||
public abstract String getFeedUrl() throws ParsingException;
|
||||
public abstract long getSubscriberCount() throws ParsingException;
|
||||
public abstract String getDescription() throws ParsingException;
|
||||
public abstract String getParentChannelName() throws ParsingException;
|
||||
public abstract String getParentChannelUrl() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getParentChannelAvatars() throws ParsingException;
|
||||
public abstract boolean isVerified() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<ListLinkHandler> getTabs() throws ParsingException;
|
||||
@Nonnull
|
||||
public List<String> getTags() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
public abstract String getParentChannelAvatarUrl() throws ParsingException;
|
||||
}
|
||||
|
@ -1,60 +1,59 @@
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 31.07.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfo.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfo.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
public class ChannelInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
import org.schabi.newpipe.extractor.Info;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class ChannelInfo extends Info {
|
||||
|
||||
public ChannelInfo(final int serviceId,
|
||||
final String id,
|
||||
final String url,
|
||||
final String originalUrl,
|
||||
final String name) {
|
||||
super(serviceId, id, url, originalUrl, name);
|
||||
public ChannelInfo(int serviceId, String id, String url, String originalUrl, String name, ListLinkHandler listLinkHandler) {
|
||||
super(serviceId, id, url, originalUrl, name, listLinkHandler.getContentFilters(), listLinkHandler.getSortFilter());
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
public static ChannelInfo getInfo(String url) throws IOException, ExtractionException {
|
||||
return getInfo(NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(final StreamingService service, final String url)
|
||||
throws IOException, ExtractionException {
|
||||
final ChannelExtractor extractor = service.getChannelExtractor(url);
|
||||
public static ChannelInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
|
||||
ChannelExtractor extractor = service.getChannelExtractor(url);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(final ChannelExtractor extractor)
|
||||
throws IOException, ExtractionException {
|
||||
public static InfoItemsPage<StreamInfoItem> getMoreItems(StreamingService service,
|
||||
String url,
|
||||
String pageUrl) throws IOException, ExtractionException {
|
||||
return service.getChannelExtractor(url).getPage(pageUrl);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(ChannelExtractor extractor) throws IOException, ExtractionException {
|
||||
|
||||
final int serviceId = extractor.getServiceId();
|
||||
final String id = extractor.getId();
|
||||
@ -62,98 +61,75 @@ public class ChannelInfo extends Info {
|
||||
final String originalUrl = extractor.getOriginalUrl();
|
||||
final String name = extractor.getName();
|
||||
|
||||
final ChannelInfo info = new ChannelInfo(serviceId, id, url, originalUrl, name);
|
||||
final ChannelInfo info = new ChannelInfo(serviceId, id, url, originalUrl, name, extractor.getLinkHandler());
|
||||
|
||||
try {
|
||||
info.setAvatars(extractor.getAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.setAvatarUrl(extractor.getAvatarUrl());
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setBanners(extractor.getBanners());
|
||||
} catch (final Exception e) {
|
||||
info.setBannerUrl(extractor.getBannerUrl());
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setFeedUrl(extractor.getFeedUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage = ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(itemsPage.getItems());
|
||||
info.setNextPageUrl(itemsPage.getNextPageUrl());
|
||||
|
||||
try {
|
||||
info.setSubscriberCount(extractor.getSubscriberCount());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setParentChannelName(extractor.getParentChannelName());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setParentChannelUrl(extractor.getParentChannelUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setParentChannelAvatars(extractor.getParentChannelAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setVerified(extractor.isVerified());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setTabs(extractor.getTabs());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
info.setTags(extractor.getTags());
|
||||
} catch (final Exception e) {
|
||||
info.setParentChannelAvatarUrl(extractor.getParentChannelAvatarUrl());
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private String avatarUrl;
|
||||
private String parentChannelName;
|
||||
private String parentChannelUrl;
|
||||
private String parentChannelAvatarUrl;
|
||||
private String bannerUrl;
|
||||
private String feedUrl;
|
||||
private long subscriberCount = -1;
|
||||
private String description;
|
||||
private String[] donationLinks;
|
||||
@Nonnull
|
||||
private List<Image> avatars = List.of();
|
||||
@Nonnull
|
||||
private List<Image> banners = List.of();
|
||||
@Nonnull
|
||||
private List<Image> parentChannelAvatars = List.of();
|
||||
private boolean verified;
|
||||
private List<ListLinkHandler> tabs = List.of();
|
||||
private List<String> tags = List.of();
|
||||
|
||||
public String getParentChannelName() {
|
||||
return parentChannelName;
|
||||
}
|
||||
|
||||
public void setParentChannelName(final String parentChannelName) {
|
||||
public void setParentChannelName(String parentChannelName) {
|
||||
this.parentChannelName = parentChannelName;
|
||||
}
|
||||
|
||||
@ -161,42 +137,39 @@ public class ChannelInfo extends Info {
|
||||
return parentChannelUrl;
|
||||
}
|
||||
|
||||
public void setParentChannelUrl(final String parentChannelUrl) {
|
||||
public void setParentChannelUrl(String parentChannelUrl) {
|
||||
this.parentChannelUrl = parentChannelUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getParentChannelAvatars() {
|
||||
return parentChannelAvatars;
|
||||
public String getParentChannelAvatarUrl() {
|
||||
return parentChannelAvatarUrl;
|
||||
}
|
||||
|
||||
public void setParentChannelAvatars(@Nonnull final List<Image> parentChannelAvatars) {
|
||||
this.parentChannelAvatars = parentChannelAvatars;
|
||||
public void setParentChannelAvatarUrl(String parentChannelAvatarUrl) {
|
||||
this.parentChannelAvatarUrl = parentChannelAvatarUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getAvatars() {
|
||||
return avatars;
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatars(@Nonnull final List<Image> avatars) {
|
||||
this.avatars = avatars;
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getBanners() {
|
||||
return banners;
|
||||
public String getBannerUrl() {
|
||||
return bannerUrl;
|
||||
}
|
||||
|
||||
public void setBanners(@Nonnull final List<Image> banners) {
|
||||
this.banners = banners;
|
||||
public void setBannerUrl(String bannerUrl) {
|
||||
this.bannerUrl = bannerUrl;
|
||||
}
|
||||
|
||||
public String getFeedUrl() {
|
||||
return feedUrl;
|
||||
}
|
||||
|
||||
public void setFeedUrl(final String feedUrl) {
|
||||
public void setFeedUrl(String feedUrl) {
|
||||
this.feedUrl = feedUrl;
|
||||
}
|
||||
|
||||
@ -204,7 +177,7 @@ public class ChannelInfo extends Info {
|
||||
return subscriberCount;
|
||||
}
|
||||
|
||||
public void setSubscriberCount(final long subscriberCount) {
|
||||
public void setSubscriberCount(long subscriberCount) {
|
||||
this.subscriberCount = subscriberCount;
|
||||
}
|
||||
|
||||
@ -212,7 +185,7 @@ public class ChannelInfo extends Info {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final String description) {
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@ -220,33 +193,7 @@ public class ChannelInfo extends Info {
|
||||
return donationLinks;
|
||||
}
|
||||
|
||||
public void setDonationLinks(final String[] donationLinks) {
|
||||
public void setDonationLinks(String[] donationLinks) {
|
||||
this.donationLinks = donationLinks;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
public void setVerified(final boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<ListLinkHandler> getTabs() {
|
||||
return tabs;
|
||||
}
|
||||
|
||||
public void setTabs(@Nonnull final List<ListLinkHandler> tabs) {
|
||||
this.tabs = tabs;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
public void setTags(@Nonnull final List<String> tags) {
|
||||
this.tags = tags;
|
||||
}
|
||||
}
|
||||
|
@ -5,21 +5,21 @@ import org.schabi.newpipe.extractor.InfoItem;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 11.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItem.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItem.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ChannelInfoItem extends InfoItem {
|
||||
@ -27,9 +27,9 @@ public class ChannelInfoItem extends InfoItem {
|
||||
private String description;
|
||||
private long subscriberCount = -1;
|
||||
private long streamCount = -1;
|
||||
private boolean verified = false;
|
||||
|
||||
public ChannelInfoItem(final int serviceId, final String url, final String name) {
|
||||
|
||||
public ChannelInfoItem(int serviceId, String url, String name) {
|
||||
super(InfoType.CHANNEL, serviceId, url, name);
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ public class ChannelInfoItem extends InfoItem {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final String description) {
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
@ -45,23 +45,15 @@ public class ChannelInfoItem extends InfoItem {
|
||||
return subscriberCount;
|
||||
}
|
||||
|
||||
public void setSubscriberCount(final long subscriberCount) {
|
||||
this.subscriberCount = subscriberCount;
|
||||
public void setSubscriberCount(long subscriber_count) {
|
||||
this.subscriberCount = subscriber_count;
|
||||
}
|
||||
|
||||
public long getStreamCount() {
|
||||
return streamCount;
|
||||
}
|
||||
|
||||
public void setStreamCount(final long streamCount) {
|
||||
this.streamCount = streamCount;
|
||||
}
|
||||
|
||||
public boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
|
||||
public void setVerified(final boolean verified) {
|
||||
this.verified = verified;
|
||||
public void setStreamCount(long stream_count) {
|
||||
this.streamCount = stream_count;
|
||||
}
|
||||
}
|
||||
|
@ -6,29 +6,26 @@ import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemExtractor.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemExtractor.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public interface ChannelInfoItemExtractor extends InfoItemExtractor {
|
||||
String getDescription() throws ParsingException;
|
||||
|
||||
long getSubscriberCount() throws ParsingException;
|
||||
|
||||
long getStreamCount() throws ParsingException;
|
||||
|
||||
boolean isVerified() throws ParsingException;
|
||||
}
|
||||
|
@ -1,67 +1,64 @@
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemsCollector.java is part of NewPipe Extractor.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.extractor.channel;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
public final class ChannelInfoItemsCollector
|
||||
extends InfoItemsCollector<ChannelInfoItem, ChannelInfoItemExtractor> {
|
||||
public ChannelInfoItemsCollector(final int serviceId) {
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfoItemsCollector.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ChannelInfoItemsCollector extends InfoItemsCollector<ChannelInfoItem, ChannelInfoItemExtractor> {
|
||||
public ChannelInfoItemsCollector(int serviceId) {
|
||||
super(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelInfoItem extract(final ChannelInfoItemExtractor extractor)
|
||||
throws ParsingException {
|
||||
final ChannelInfoItem resultItem = new ChannelInfoItem(
|
||||
getServiceId(), extractor.getUrl(), extractor.getName());
|
||||
public ChannelInfoItem extract(ChannelInfoItemExtractor extractor) throws ParsingException {
|
||||
// important information
|
||||
int serviceId = getServiceId();
|
||||
String name = extractor.getName();
|
||||
String url = extractor.getUrl();
|
||||
|
||||
ChannelInfoItem resultItem = new ChannelInfoItem(serviceId, url, name);
|
||||
|
||||
|
||||
// optional information
|
||||
try {
|
||||
resultItem.setSubscriberCount(extractor.getSubscriberCount());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setStreamCount(extractor.getStreamCount());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setVerified(extractor.isVerified());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
return resultItem;
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* A {@link ListExtractor} of {@link InfoItem}s for tabs of channels.
|
||||
*/
|
||||
public abstract class ChannelTabExtractor extends ListExtractor<InfoItem> {
|
||||
|
||||
protected ChannelTabExtractor(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() {
|
||||
return getLinkHandler().getContentFilters().get(0);
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ChannelTabInfo extends ListInfo<InfoItem> {
|
||||
|
||||
public ChannelTabInfo(final int serviceId,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
super(serviceId, linkHandler, linkHandler.getContentFilters().get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link ChannelTabInfo} instance from the given service and tab handler.
|
||||
*
|
||||
* @param service streaming service
|
||||
* @param linkHandler Channel tab handler (from {@link ChannelInfo})
|
||||
* @return the extracted {@link ChannelTabInfo}
|
||||
*/
|
||||
@Nonnull
|
||||
public static ChannelTabInfo getInfo(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler)
|
||||
throws ExtractionException, IOException {
|
||||
final ChannelTabExtractor extractor = service.getChannelTabExtractor(linkHandler);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a {@link ChannelTabInfo} instance from a {@link ChannelTabExtractor}.
|
||||
*
|
||||
* @param extractor an extractor where {@code fetchPage()} was already got called on
|
||||
* @return the extracted {@link ChannelTabInfo}
|
||||
*/
|
||||
@Nonnull
|
||||
public static ChannelTabInfo getInfo(@Nonnull final ChannelTabExtractor extractor) {
|
||||
final ChannelTabInfo info =
|
||||
new ChannelTabInfo(extractor.getServiceId(), extractor.getLinkHandler());
|
||||
|
||||
try {
|
||||
info.setOriginalUrl(extractor.getOriginalUrl());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
final ListExtractor.InfoItemsPage<InfoItem> page
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(page.getItems());
|
||||
info.setNextPage(page.getNextPage());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static ListExtractor.InfoItemsPage<InfoItem> getMoreItems(
|
||||
@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler,
|
||||
@Nonnull final Page page) throws ExtractionException, IOException {
|
||||
return service.getChannelTabExtractor(linkHandler).getPage(page);
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.channel.tabs;
|
||||
|
||||
/**
|
||||
* Constants of channel tabs supported by the extractor.
|
||||
*/
|
||||
public final class ChannelTabs {
|
||||
public static final String VIDEOS = "videos";
|
||||
public static final String TRACKS = "tracks";
|
||||
public static final String SHORTS = "shorts";
|
||||
public static final String LIVESTREAMS = "livestreams";
|
||||
public static final String CHANNELS = "channels";
|
||||
public static final String PLAYLISTS = "playlists";
|
||||
public static final String ALBUMS = "albums";
|
||||
public static final String LIKES = "likes";
|
||||
|
||||
private ChannelTabs() {
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
|
||||
@ -10,23 +9,9 @@ import javax.annotation.Nonnull;
|
||||
|
||||
public abstract class CommentsExtractor extends ListExtractor<CommentsInfoItem> {
|
||||
|
||||
public CommentsExtractor(final StreamingService service, final ListLinkHandler uiHandler) {
|
||||
public CommentsExtractor(StreamingService service, ListLinkHandler uiHandler) {
|
||||
super(service, uiHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* @apiNote Warning: This method is experimental and may get removed in a future release.
|
||||
* @return <code>true</code> if the comments are disabled otherwise <code>false</code> (default)
|
||||
*/
|
||||
public boolean isCommentsDisabled() throws ExtractionException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total number of comments
|
||||
*/
|
||||
public int getCommentsCount() throws ExtractionException {
|
||||
return -1;
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -3,7 +3,6 @@ package org.schabi.newpipe.extractor.comments;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
@ -11,116 +10,62 @@ import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
||||
public class CommentsInfo extends ListInfo<CommentsInfoItem> {
|
||||
|
||||
private CommentsInfo(
|
||||
final int serviceId,
|
||||
final ListLinkHandler listUrlIdHandler,
|
||||
final String name) {
|
||||
private CommentsInfo(int serviceId, ListLinkHandler listUrlIdHandler, String name) {
|
||||
super(serviceId, listUrlIdHandler, name);
|
||||
}
|
||||
|
||||
public static CommentsInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
public static CommentsInfo getInfo(String url) throws IOException, ExtractionException {
|
||||
return getInfo(NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static CommentsInfo getInfo(final StreamingService service, final String url)
|
||||
throws ExtractionException, IOException {
|
||||
return getInfo(service.getCommentsExtractor(url));
|
||||
public static CommentsInfo getInfo(StreamingService serviceByUrl, String url) throws ExtractionException, IOException {
|
||||
return getInfo(serviceByUrl.getCommentsExtractor(url));
|
||||
}
|
||||
|
||||
public static CommentsInfo getInfo(final CommentsExtractor commentsExtractor)
|
||||
throws IOException, ExtractionException {
|
||||
private static CommentsInfo getInfo(CommentsExtractor commentsExtractor) throws IOException, ExtractionException {
|
||||
// for services which do not have a comments extractor
|
||||
if (commentsExtractor == null) {
|
||||
if (null == commentsExtractor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
commentsExtractor.fetchPage();
|
||||
|
||||
final String name = commentsExtractor.getName();
|
||||
final int serviceId = commentsExtractor.getServiceId();
|
||||
final ListLinkHandler listUrlIdHandler = commentsExtractor.getLinkHandler();
|
||||
|
||||
final CommentsInfo commentsInfo = new CommentsInfo(serviceId, listUrlIdHandler, name);
|
||||
String name = commentsExtractor.getName();
|
||||
int serviceId = commentsExtractor.getServiceId();
|
||||
ListLinkHandler listUrlIdHandler = commentsExtractor.getLinkHandler();
|
||||
CommentsInfo commentsInfo = new CommentsInfo(serviceId, listUrlIdHandler, name);
|
||||
commentsInfo.setCommentsExtractor(commentsExtractor);
|
||||
final InfoItemsPage<CommentsInfoItem> initialCommentsPage =
|
||||
ExtractorHelper.getItemsPageOrLogError(commentsInfo, commentsExtractor);
|
||||
commentsInfo.setCommentsDisabled(commentsExtractor.isCommentsDisabled());
|
||||
InfoItemsPage<CommentsInfoItem> initialCommentsPage = ExtractorHelper.getItemsPageOrLogError(commentsInfo,
|
||||
commentsExtractor);
|
||||
commentsInfo.setRelatedItems(initialCommentsPage.getItems());
|
||||
try {
|
||||
commentsInfo.setCommentsCount(commentsExtractor.getCommentsCount());
|
||||
} catch (final Exception e) {
|
||||
commentsInfo.addError(e);
|
||||
}
|
||||
commentsInfo.setNextPage(initialCommentsPage.getNextPage());
|
||||
commentsInfo.setNextPageUrl(initialCommentsPage.getNextPageUrl());
|
||||
|
||||
return commentsInfo;
|
||||
}
|
||||
|
||||
public static InfoItemsPage<CommentsInfoItem> getMoreItems(
|
||||
final CommentsInfo commentsInfo,
|
||||
final Page page) throws ExtractionException, IOException {
|
||||
return getMoreItems(NewPipe.getService(commentsInfo.getServiceId()), commentsInfo.getUrl(),
|
||||
page);
|
||||
public static InfoItemsPage<CommentsInfoItem> getMoreItems(CommentsInfo commentsInfo, String pageUrl)
|
||||
throws ExtractionException, IOException {
|
||||
return getMoreItems(NewPipe.getService(commentsInfo.getServiceId()), commentsInfo, pageUrl);
|
||||
}
|
||||
|
||||
public static InfoItemsPage<CommentsInfoItem> getMoreItems(
|
||||
final StreamingService service,
|
||||
final CommentsInfo commentsInfo,
|
||||
final Page page) throws IOException, ExtractionException {
|
||||
return getMoreItems(service, commentsInfo.getUrl(), page);
|
||||
public static InfoItemsPage<CommentsInfoItem> getMoreItems(StreamingService service, CommentsInfo commentsInfo,
|
||||
String pageUrl) throws IOException, ExtractionException {
|
||||
if (null == commentsInfo.getCommentsExtractor()) {
|
||||
commentsInfo.setCommentsExtractor(service.getCommentsExtractor(commentsInfo.getUrl()));
|
||||
commentsInfo.getCommentsExtractor().fetchPage();
|
||||
}
|
||||
|
||||
public static InfoItemsPage<CommentsInfoItem> getMoreItems(
|
||||
final StreamingService service,
|
||||
final String url,
|
||||
final Page page) throws IOException, ExtractionException {
|
||||
return service.getCommentsExtractor(url).getPage(page);
|
||||
return commentsInfo.getCommentsExtractor().getPage(pageUrl);
|
||||
}
|
||||
|
||||
private transient CommentsExtractor commentsExtractor;
|
||||
private boolean commentsDisabled = false;
|
||||
private int commentsCount;
|
||||
|
||||
public CommentsExtractor getCommentsExtractor() {
|
||||
return commentsExtractor;
|
||||
}
|
||||
|
||||
public void setCommentsExtractor(final CommentsExtractor commentsExtractor) {
|
||||
public void setCommentsExtractor(CommentsExtractor commentsExtractor) {
|
||||
this.commentsExtractor = commentsExtractor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if the comments are disabled otherwise {@code false} (default)
|
||||
* @see CommentsExtractor#isCommentsDisabled()
|
||||
*/
|
||||
public boolean isCommentsDisabled() {
|
||||
return commentsDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param commentsDisabled {@code true} if the comments are disabled otherwise {@code false}
|
||||
*/
|
||||
public void setCommentsDisabled(final boolean commentsDisabled) {
|
||||
this.commentsDisabled = commentsDisabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total number of comments.
|
||||
*
|
||||
* @return the total number of comments
|
||||
*/
|
||||
public int getCommentsCount() {
|
||||
return commentsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the total number of comments.
|
||||
*
|
||||
* @param commentsCount the commentsCount to set.
|
||||
*/
|
||||
public void setCommentsCount(final int commentsCount) {
|
||||
this.commentsCount = commentsCount;
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +1,23 @@
|
||||
package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public class CommentsInfoItem extends InfoItem {
|
||||
|
||||
private String commentId;
|
||||
@Nonnull
|
||||
private Description commentText = Description.EMPTY_DESCRIPTION;
|
||||
private String commentText;
|
||||
private String uploaderName;
|
||||
@Nonnull
|
||||
private List<Image> uploaderAvatars = List.of();
|
||||
private String uploaderAvatarUrl;
|
||||
private String uploaderUrl;
|
||||
private boolean uploaderVerified;
|
||||
private String textualUploadDate;
|
||||
@Nullable
|
||||
private DateWrapper uploadDate;
|
||||
private int likeCount;
|
||||
private String textualLikeCount;
|
||||
private boolean heartedByUploader;
|
||||
private boolean pinned;
|
||||
private int streamPosition;
|
||||
private int replyCount;
|
||||
@Nullable
|
||||
private Page replies;
|
||||
private boolean isChannelOwner;
|
||||
private boolean creatorReply;
|
||||
|
||||
public static final int NO_LIKE_COUNT = -1;
|
||||
public static final int NO_STREAM_POSITION = -1;
|
||||
|
||||
public static final int UNKNOWN_REPLY_COUNT = -1;
|
||||
|
||||
public CommentsInfoItem(final int serviceId, final String url, final String name) {
|
||||
public CommentsInfoItem(int serviceId, String url, String name) {
|
||||
super(InfoType.COMMENT, serviceId, url, name);
|
||||
}
|
||||
|
||||
@ -47,16 +25,15 @@ public class CommentsInfoItem extends InfoItem {
|
||||
return commentId;
|
||||
}
|
||||
|
||||
public void setCommentId(final String commentId) {
|
||||
public void setCommentId(String commentId) {
|
||||
this.commentId = commentId;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Description getCommentText() {
|
||||
public String getCommentText() {
|
||||
return commentText;
|
||||
}
|
||||
|
||||
public void setCommentText(@Nonnull final Description commentText) {
|
||||
public void setCommentText(String commentText) {
|
||||
this.commentText = commentText;
|
||||
}
|
||||
|
||||
@ -64,24 +41,23 @@ public class CommentsInfoItem extends InfoItem {
|
||||
return uploaderName;
|
||||
}
|
||||
|
||||
public void setUploaderName(final String uploaderName) {
|
||||
public void setUploaderName(String uploaderName) {
|
||||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return uploaderAvatars;
|
||||
public String getUploaderAvatarUrl() {
|
||||
return uploaderAvatarUrl;
|
||||
}
|
||||
|
||||
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||
this.uploaderAvatars = uploaderAvatars;
|
||||
public void setUploaderAvatarUrl(String uploaderAvatarUrl) {
|
||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
||||
}
|
||||
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
public void setUploaderUrl(final String uploaderUrl) {
|
||||
public void setUploaderUrl(String uploaderUrl) {
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
@ -89,7 +65,7 @@ public class CommentsInfoItem extends InfoItem {
|
||||
return textualUploadDate;
|
||||
}
|
||||
|
||||
public void setTextualUploadDate(final String textualUploadDate) {
|
||||
public void setTextualUploadDate(String textualUploadDate) {
|
||||
this.textualUploadDate = textualUploadDate;
|
||||
}
|
||||
|
||||
@ -98,100 +74,15 @@ public class CommentsInfoItem extends InfoItem {
|
||||
return uploadDate;
|
||||
}
|
||||
|
||||
public void setUploadDate(@Nullable final DateWrapper uploadDate) {
|
||||
public void setUploadDate(@Nullable DateWrapper uploadDate) {
|
||||
this.uploadDate = uploadDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the comment's like count or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is
|
||||
* unavailable
|
||||
*/
|
||||
public int getLikeCount() {
|
||||
return likeCount;
|
||||
}
|
||||
|
||||
public void setLikeCount(final int likeCount) {
|
||||
public void setLikeCount(int likeCount) {
|
||||
this.likeCount = likeCount;
|
||||
}
|
||||
|
||||
public String getTextualLikeCount() {
|
||||
return textualLikeCount;
|
||||
}
|
||||
|
||||
public void setTextualLikeCount(final String textualLikeCount) {
|
||||
this.textualLikeCount = textualLikeCount;
|
||||
}
|
||||
|
||||
public void setHeartedByUploader(final boolean isHeartedByUploader) {
|
||||
this.heartedByUploader = isHeartedByUploader;
|
||||
}
|
||||
|
||||
public boolean isHeartedByUploader() {
|
||||
return this.heartedByUploader;
|
||||
}
|
||||
|
||||
public boolean isPinned() {
|
||||
return pinned;
|
||||
}
|
||||
|
||||
public void setPinned(final boolean pinned) {
|
||||
this.pinned = pinned;
|
||||
}
|
||||
|
||||
public void setUploaderVerified(final boolean uploaderVerified) {
|
||||
this.uploaderVerified = uploaderVerified;
|
||||
}
|
||||
|
||||
public boolean isUploaderVerified() {
|
||||
return uploaderVerified;
|
||||
}
|
||||
|
||||
public void setStreamPosition(final int streamPosition) {
|
||||
this.streamPosition = streamPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playback position of the stream to which this comment belongs.
|
||||
* This is not supported by all services.
|
||||
*
|
||||
* @return the playback position in seconds or {@link #NO_STREAM_POSITION} if not available
|
||||
*/
|
||||
public int getStreamPosition() {
|
||||
return streamPosition;
|
||||
}
|
||||
|
||||
public void setReplyCount(final int replyCount) {
|
||||
this.replyCount = replyCount;
|
||||
}
|
||||
|
||||
public int getReplyCount() {
|
||||
return replyCount;
|
||||
}
|
||||
|
||||
public void setReplies(@Nullable final Page replies) {
|
||||
this.replies = replies;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Page getReplies() {
|
||||
return this.replies;
|
||||
}
|
||||
|
||||
public void setChannelOwner(final boolean channelOwner) {
|
||||
this.isChannelOwner = channelOwner;
|
||||
}
|
||||
|
||||
public boolean isChannelOwner() {
|
||||
return isChannelOwner;
|
||||
}
|
||||
|
||||
|
||||
public void setCreatorReply(final boolean creatorReply) {
|
||||
this.creatorReply = creatorReply;
|
||||
}
|
||||
|
||||
public boolean hasCreatorReply() {
|
||||
return creatorReply;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,152 +1,43 @@
|
||||
package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeCommentsInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.List;
|
||||
|
||||
public interface CommentsInfoItemExtractor extends InfoItemExtractor {
|
||||
|
||||
/**
|
||||
* Return the like count of the comment,
|
||||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable.
|
||||
*
|
||||
* <br>
|
||||
* <p>
|
||||
* NOTE: Currently only implemented for YT {@link
|
||||
* YoutubeCommentsInfoItemExtractor#getLikeCount()}
|
||||
* with limitations (only approximate like count is returned)
|
||||
*
|
||||
* @return the comment's like count
|
||||
* or {@link CommentsInfoItem#NO_LIKE_COUNT} if it is unavailable
|
||||
* Return the like count of the comment, or -1 if it's unavailable
|
||||
* @see StreamExtractor#getLikeCount()
|
||||
*/
|
||||
default int getLikeCount() throws ParsingException {
|
||||
return CommentsInfoItem.NO_LIKE_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unmodified like count given by the service
|
||||
* <br>
|
||||
* It may be language dependent
|
||||
*/
|
||||
default String getTextualLikeCount() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
int getLikeCount() throws ParsingException;
|
||||
|
||||
/**
|
||||
* The text of the comment
|
||||
*/
|
||||
@Nonnull
|
||||
default Description getCommentText() throws ParsingException {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
String getCommentText() throws ParsingException;
|
||||
|
||||
/**
|
||||
* The upload date given by the service, unmodified
|
||||
*
|
||||
* @see StreamExtractor#getTextualUploadDate()
|
||||
*/
|
||||
default String getTextualUploadDate() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
String getTextualUploadDate() throws ParsingException;
|
||||
|
||||
/**
|
||||
* The upload date wrapped with DateWrapper class
|
||||
*
|
||||
* @see StreamExtractor#getUploadDate()
|
||||
*/
|
||||
@Nullable
|
||||
default DateWrapper getUploadDate() throws ParsingException {
|
||||
return null;
|
||||
}
|
||||
DateWrapper getUploadDate() throws ParsingException;
|
||||
|
||||
default String getCommentId() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
String getCommentId() throws ParsingException;
|
||||
|
||||
default String getUploaderUrl() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
String getUploaderUrl() throws ParsingException;
|
||||
|
||||
default String getUploaderName() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
String getUploaderName() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
default List<Image> getUploaderAvatars() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment has been hearted by the uploader
|
||||
*/
|
||||
default boolean isHeartedByUploader() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment is pinned
|
||||
*/
|
||||
default boolean isPinned() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the uploader is verified by the service
|
||||
*/
|
||||
default boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The playback position of the stream to which this comment belongs.
|
||||
*
|
||||
* @see CommentsInfoItem#getStreamPosition()
|
||||
*/
|
||||
default int getStreamPosition() throws ParsingException {
|
||||
return CommentsInfoItem.NO_STREAM_POSITION;
|
||||
}
|
||||
|
||||
/**
|
||||
* The count of comment replies.
|
||||
*
|
||||
* @return the count of the replies
|
||||
* or {@link CommentsInfoItem#UNKNOWN_REPLY_COUNT} if replies are not supported
|
||||
*/
|
||||
default int getReplyCount() throws ParsingException {
|
||||
return CommentsInfoItem.UNKNOWN_REPLY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* The continuation page which is used to get comment replies from.
|
||||
*
|
||||
* @return the continuation Page for the replies, or null if replies are not supported
|
||||
*/
|
||||
@Nullable
|
||||
default Page getReplies() throws ParsingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment was made by the channel owner.
|
||||
*/
|
||||
default boolean isChannelOwner() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the comment was replied to by the creator.
|
||||
*/
|
||||
default boolean hasCreatorReply() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
String getUploaderAvatarUrl() throws ParsingException;
|
||||
}
|
||||
|
@ -1,133 +1,94 @@
|
||||
package org.schabi.newpipe.extractor.comments;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
public final class CommentsInfoItemsCollector
|
||||
extends InfoItemsCollector<CommentsInfoItem, CommentsInfoItemExtractor> {
|
||||
public class CommentsInfoItemsCollector extends InfoItemsCollector<CommentsInfoItem, CommentsInfoItemExtractor> {
|
||||
|
||||
public CommentsInfoItemsCollector(final int serviceId) {
|
||||
public CommentsInfoItemsCollector(int serviceId) {
|
||||
super(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentsInfoItem extract(final CommentsInfoItemExtractor extractor)
|
||||
throws ParsingException {
|
||||
final CommentsInfoItem resultItem = new CommentsInfoItem(
|
||||
getServiceId(), extractor.getUrl(), extractor.getName());
|
||||
public CommentsInfoItem extract(CommentsInfoItemExtractor extractor) throws ParsingException {
|
||||
|
||||
// important information
|
||||
int serviceId = getServiceId();
|
||||
String url = extractor.getUrl();
|
||||
String name = extractor.getName();
|
||||
|
||||
CommentsInfoItem resultItem = new CommentsInfoItem(serviceId, url, name);
|
||||
|
||||
// optional information
|
||||
try {
|
||||
resultItem.setCommentId(extractor.getCommentId());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setCommentText(extractor.getCommentText());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderName(extractor.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||
} catch (final Exception e) {
|
||||
resultItem.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderUrl(extractor.getUploaderUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setTextualUploadDate(extractor.getTextualUploadDate());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploadDate(extractor.getUploadDate());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setLikeCount(extractor.getLikeCount());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setTextualLikeCount(extractor.getTextualLikeCount());
|
||||
} catch (final Exception e) {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setHeartedByUploader(extractor.isHeartedByUploader());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setPinned(extractor.isPinned());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setStreamPosition(extractor.getStreamPosition());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setReplyCount(extractor.getReplyCount());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setReplies(extractor.getReplies());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
try {
|
||||
resultItem.setChannelOwner(extractor.isChannelOwner());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
resultItem.setCreatorReply(extractor.hasCreatorReply());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
|
||||
|
||||
return resultItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(final CommentsInfoItemExtractor extractor) {
|
||||
public void commit(CommentsInfoItemExtractor extractor) {
|
||||
try {
|
||||
addItem(extract(extractor));
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CommentsInfoItem> getCommentsInfoItemList() {
|
||||
return new ArrayList<>(super.getItems());
|
||||
List<CommentsInfoItem> siiList = new Vector<>();
|
||||
for (InfoItem ii : super.getItems()) {
|
||||
if (ii instanceof CommentsInfoItem) {
|
||||
siiList.add((CommentsInfoItem) ii);
|
||||
}
|
||||
}
|
||||
return siiList;
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,6 @@ import org.schabi.newpipe.extractor.localization.Localization;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@ -21,14 +19,13 @@ public abstract class Downloader {
|
||||
/**
|
||||
* Do a GET request to get the resource that the url is pointing to.<br>
|
||||
* <br>
|
||||
* This method calls {@link #get(String, Map, Localization)} with the default preferred
|
||||
* localization. It should only be used when the resource that will be fetched won't be affected
|
||||
* by the localization.
|
||||
* This method calls {@link #get(String, Map, Localization)} with the default preferred localization. It should only be
|
||||
* used when the resource that will be fetched won't be affected by the localization.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response get(final String url) throws IOException, ReCaptchaException {
|
||||
public Response get(String url) throws IOException, ReCaptchaException {
|
||||
return get(url, null, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
@ -41,8 +38,7 @@ public abstract class Downloader {
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response get(final String url, final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
public Response get(String url, @Nullable Localization localization) throws IOException, ReCaptchaException {
|
||||
return get(url, null, localization);
|
||||
}
|
||||
|
||||
@ -54,8 +50,7 @@ public abstract class Downloader {
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response get(final String url, @Nullable final Map<String, List<String>> headers)
|
||||
throws IOException, ReCaptchaException {
|
||||
public Response get(String url, @Nullable Map<String, List<String>> headers) throws IOException, ReCaptchaException {
|
||||
return get(url, headers, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
@ -70,9 +65,7 @@ public abstract class Downloader {
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response get(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
final Localization localization)
|
||||
public Response get(String url, @Nullable Map<String, List<String>> headers, @Nullable Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return execute(Request.newBuilder()
|
||||
.get(url)
|
||||
@ -87,7 +80,7 @@ public abstract class Downloader {
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @return the result of the HEAD request
|
||||
*/
|
||||
public Response head(final String url) throws IOException, ReCaptchaException {
|
||||
public Response head(String url) throws IOException, ReCaptchaException {
|
||||
return head(url, null);
|
||||
}
|
||||
|
||||
@ -99,7 +92,7 @@ public abstract class Downloader {
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @return the result of the HEAD request
|
||||
*/
|
||||
public Response head(final String url, @Nullable final Map<String, List<String>> headers)
|
||||
public Response head(String url, @Nullable Map<String, List<String>> headers)
|
||||
throws IOException, ReCaptchaException {
|
||||
return execute(Request.newBuilder()
|
||||
.head(url)
|
||||
@ -114,11 +107,9 @@ public abstract class Downloader {
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @return the result of the POST request
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response post(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend)
|
||||
public Response post(String url, @Nullable Map<String, List<String>> headers, @Nullable byte[] dataToSend)
|
||||
throws IOException, ReCaptchaException {
|
||||
return post(url, headers, dataToSend, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
@ -133,12 +124,9 @@ public abstract class Downloader {
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the POST request
|
||||
* @return the result of the GET request
|
||||
*/
|
||||
public Response post(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final Localization localization)
|
||||
public Response post(String url, @Nullable Map<String, List<String>> headers, @Nullable byte[] dataToSend, @Nullable Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return execute(Request.newBuilder()
|
||||
.post(url, dataToSend)
|
||||
@ -147,100 +135,10 @@ public abstract class Downloader {
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request using the specified value of the
|
||||
* {@code Content-Type} header with a given {@link Localization}.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @param contentType the mime type of the body sent, which will be set as the value of the
|
||||
* {@code Content-Type} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentType(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final Localization localization,
|
||||
final String contentType)
|
||||
throws IOException, ReCaptchaException {
|
||||
final Map<String, List<String>> actualHeaders = new HashMap<>();
|
||||
if (headers != null) {
|
||||
actualHeaders.putAll(headers);
|
||||
}
|
||||
actualHeaders.put("Content-Type", Collections.singletonList(contentType));
|
||||
return post(url, actualHeaders, dataToSend, localization);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request using the specified value of the
|
||||
* {@code Content-Type} header.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param contentType the mime type of the body sent, which will be set as the value of the
|
||||
* {@code Content-Type} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentType(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final String contentType)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentType(url, headers, dataToSend, NewPipe.getPreferredLocalization(),
|
||||
contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request the JSON mime type as the value of the
|
||||
* {@code Content-Type} header with a given {@link Localization}.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @param localization the source of the value of the {@code Accept-Language} header
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentTypeJson(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
final Localization localization)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentType(url, headers, dataToSend, localization, "application/json");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenient method to send a POST request the JSON mime type as the value of the
|
||||
* {@code Content-Type} header.
|
||||
*
|
||||
* @param url the URL that is pointing to the wanted resource
|
||||
* @param headers a list of headers that will be used in the request.
|
||||
* Any default headers <b>should</b> be overridden by these.
|
||||
* @param dataToSend byte array that will be sent when doing the request.
|
||||
* @return the result of the POST request
|
||||
* @see #post(String, Map, byte[], Localization)
|
||||
*/
|
||||
public Response postWithContentTypeJson(final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend)
|
||||
throws IOException, ReCaptchaException {
|
||||
return postWithContentTypeJson(url, headers, dataToSend,
|
||||
NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
/**
|
||||
* Do a request using the specified {@link Request} object.
|
||||
*
|
||||
* @return the result of the request
|
||||
*/
|
||||
public abstract Response execute(@Nonnull Request request)
|
||||
throws IOException, ReCaptchaException;
|
||||
public abstract Response execute(@Nonnull Request request) throws IOException, ReCaptchaException;
|
||||
}
|
||||
|
@ -2,53 +2,42 @@ package org.schabi.newpipe.extractor.downloader;
|
||||
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* An object that holds request information used when {@link Downloader#execute(Request) executing}
|
||||
* a request.
|
||||
* An object that holds request information used when {@link Downloader#execute(Request) executing} a request.
|
||||
*/
|
||||
public class Request {
|
||||
private final String httpMethod;
|
||||
private final String url;
|
||||
private final Map<String, List<String>> headers;
|
||||
@Nullable
|
||||
private final byte[] dataToSend;
|
||||
@Nullable
|
||||
private final Localization localization;
|
||||
@Nullable private final byte[] dataToSend;
|
||||
@Nullable private final Localization localization;
|
||||
|
||||
public Request(final String httpMethod,
|
||||
final String url,
|
||||
@Nullable final Map<String, List<String>> headers,
|
||||
@Nullable final byte[] dataToSend,
|
||||
@Nullable final Localization localization,
|
||||
final boolean automaticLocalizationHeader) {
|
||||
this.httpMethod = Objects.requireNonNull(httpMethod, "Request's httpMethod is null");
|
||||
this.url = Objects.requireNonNull(url, "Request's url is null");
|
||||
public Request(String httpMethod, String url, Map<String, List<String>> headers, @Nullable byte[] dataToSend,
|
||||
@Nullable Localization localization, boolean automaticLocalizationHeader) {
|
||||
if (httpMethod == null) throw new IllegalArgumentException("Request's httpMethod is null");
|
||||
if (url == null) throw new IllegalArgumentException("Request's url is null");
|
||||
|
||||
this.httpMethod = httpMethod;
|
||||
this.url = url;
|
||||
this.dataToSend = dataToSend;
|
||||
this.localization = localization;
|
||||
|
||||
final Map<String, List<String>> actualHeaders = new LinkedHashMap<>();
|
||||
if (headers != null) {
|
||||
actualHeaders.putAll(headers);
|
||||
}
|
||||
Map<String, List<String>> headersToSet = null;
|
||||
if (headers == null) headers = Collections.emptyMap();
|
||||
|
||||
if (automaticLocalizationHeader && localization != null) {
|
||||
actualHeaders.putAll(getHeadersFromLocalization(localization));
|
||||
headersToSet = new LinkedHashMap<>(headersFromLocalization(localization));
|
||||
headersToSet.putAll(headers);
|
||||
}
|
||||
|
||||
this.headers = Collections.unmodifiableMap(actualHeaders);
|
||||
this.headers = Collections.unmodifiableMap(headersToSet == null ? headers : headersToSet);
|
||||
}
|
||||
|
||||
private Request(final Builder builder) {
|
||||
private Request(Builder builder) {
|
||||
this(builder.httpMethod, builder.url, builder.headers, builder.dataToSend,
|
||||
builder.localization, builder.automaticLocalizationHeader);
|
||||
}
|
||||
@ -91,7 +80,7 @@ public class Request {
|
||||
* A localization object that should be used when executing a request.<br>
|
||||
* <br>
|
||||
* Usually the {@code Accept-Language} will be set to this value (a helper
|
||||
* method to do this easily: {@link Request#getHeadersFromLocalization(Localization)}).
|
||||
* method to do this easily: {@link Request#headersFromLocalization(Localization)}).
|
||||
*/
|
||||
@Nullable
|
||||
public Localization localization() {
|
||||
@ -105,7 +94,7 @@ public class Request {
|
||||
public static final class Builder {
|
||||
private String httpMethod;
|
||||
private String url;
|
||||
private final Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
private Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
private byte[] dataToSend;
|
||||
private Localization localization;
|
||||
private boolean automaticLocalizationHeader = true;
|
||||
@ -116,29 +105,30 @@ public class Request {
|
||||
/**
|
||||
* A http method (i.e. {@code GET, POST, HEAD}).
|
||||
*/
|
||||
public Builder httpMethod(final String httpMethodToSet) {
|
||||
this.httpMethod = httpMethodToSet;
|
||||
public Builder httpMethod(String httpMethod) {
|
||||
this.httpMethod = httpMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL that is pointing to the wanted resource.
|
||||
*/
|
||||
public Builder url(final String urlToSet) {
|
||||
this.url = urlToSet;
|
||||
public Builder url(String url) {
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of headers that will be used in the request.<br>
|
||||
* Any default headers that the implementation may have, <b>should</b> be overridden by
|
||||
* these.
|
||||
* Any default headers that the implementation may have, <b>should</b> be overridden by these.
|
||||
*/
|
||||
public Builder headers(@Nullable final Map<String, List<String>> headersToSet) {
|
||||
public Builder headers(@Nullable Map<String, List<String>> headers) {
|
||||
if (headers == null) {
|
||||
this.headers.clear();
|
||||
if (headersToSet != null) {
|
||||
this.headers.putAll(headersToSet);
|
||||
return this;
|
||||
}
|
||||
this.headers.clear();
|
||||
this.headers.putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -149,8 +139,8 @@ public class Request {
|
||||
* The implementation should make note of some recommended headers
|
||||
* (for example, {@code Content-Length} in a post request).
|
||||
*/
|
||||
public Builder dataToSend(final byte[] dataToSendToSet) {
|
||||
this.dataToSend = dataToSendToSet;
|
||||
public Builder dataToSend(byte[] dataToSend) {
|
||||
this.dataToSend = dataToSend;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -158,18 +148,18 @@ public class Request {
|
||||
* A localization object that should be used when executing a request.<br>
|
||||
* <br>
|
||||
* Usually the {@code Accept-Language} will be set to this value (a helper
|
||||
* method to do this easily: {@link Request#getHeadersFromLocalization(Localization)}).
|
||||
* method to do this easily: {@link Request#headersFromLocalization(Localization)}).
|
||||
*/
|
||||
public Builder localization(final Localization localizationToSet) {
|
||||
this.localization = localizationToSet;
|
||||
public Builder localization(Localization localization) {
|
||||
this.localization = localization;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If localization headers should automatically be included in the request.
|
||||
*/
|
||||
public Builder automaticLocalizationHeader(final boolean automaticLocalizationHeaderToSet) {
|
||||
this.automaticLocalizationHeader = automaticLocalizationHeaderToSet;
|
||||
public Builder automaticLocalizationHeader(boolean automaticLocalizationHeader) {
|
||||
this.automaticLocalizationHeader = automaticLocalizationHeader;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -182,22 +172,22 @@ public class Request {
|
||||
// Http Methods Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public Builder get(final String urlToSet) {
|
||||
public Builder get(String url) {
|
||||
this.httpMethod = "GET";
|
||||
this.url = urlToSet;
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder head(final String urlToSet) {
|
||||
public Builder head(String url) {
|
||||
this.httpMethod = "HEAD";
|
||||
this.url = urlToSet;
|
||||
this.url = url;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder post(final String urlToSet, @Nullable final byte[] dataToSendToSet) {
|
||||
public Builder post(String url, @Nullable byte[] dataToSend) {
|
||||
this.httpMethod = "POST";
|
||||
this.url = urlToSet;
|
||||
this.dataToSend = dataToSendToSet;
|
||||
this.url = url;
|
||||
this.dataToSend = dataToSend;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -205,13 +195,13 @@ public class Request {
|
||||
// Additional Headers Utils
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
public Builder setHeaders(final String headerName, final List<String> headerValueList) {
|
||||
public Builder setHeaders(String headerName, List<String> headerValueList) {
|
||||
this.headers.remove(headerName);
|
||||
this.headers.put(headerName, headerValueList);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder addHeaders(final String headerName, final List<String> headerValueList) {
|
||||
public Builder addHeaders(String headerName, List<String> headerValueList) {
|
||||
@Nullable List<String> currentHeaderValueList = this.headers.get(headerName);
|
||||
if (currentHeaderValueList == null) {
|
||||
currentHeaderValueList = new ArrayList<>();
|
||||
@ -222,11 +212,11 @@ public class Request {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHeader(final String headerName, final String headerValue) {
|
||||
public Builder setHeader(String headerName, String headerValue) {
|
||||
return setHeaders(headerName, Collections.singletonList(headerValue));
|
||||
}
|
||||
|
||||
public Builder addHeader(final String headerName, final String headerValue) {
|
||||
public Builder addHeader(String headerName, String headerValue) {
|
||||
return addHeaders(headerName, Collections.singletonList(headerValue));
|
||||
}
|
||||
|
||||
@ -238,43 +228,17 @@ public class Request {
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Nonnull
|
||||
public static Map<String, List<String>> getHeadersFromLocalization(
|
||||
@Nullable final Localization localization) {
|
||||
if (localization == null) {
|
||||
return Collections.emptyMap();
|
||||
public static Map<String, List<String>> headersFromLocalization(@Nullable Localization localization) {
|
||||
if (localization == null) return Collections.emptyMap();
|
||||
|
||||
final Map<String, List<String>> headers = new LinkedHashMap<>();
|
||||
if (!localization.getCountryCode().isEmpty()) {
|
||||
headers.put("Accept-Language", Collections.singletonList(localization.getLocalizationCode() +
|
||||
", " + localization.getLanguageCode() + ";q=0.9"));
|
||||
} else {
|
||||
headers.put("Accept-Language", Collections.singletonList(localization.getLanguageCode()));
|
||||
}
|
||||
|
||||
final String languageCode = localization.getLanguageCode();
|
||||
final List<String> languageCodeList = Collections.singletonList(
|
||||
localization.getCountryCode().isEmpty() ? languageCode
|
||||
: localization.getLocalizationCode() + ", " + languageCode + ";q=0.9");
|
||||
return Collections.singletonMap("Accept-Language", languageCodeList);
|
||||
}
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Generated
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final Request request = (Request) o;
|
||||
return httpMethod.equals(request.httpMethod)
|
||||
&& url.equals(request.url)
|
||||
&& headers.equals(request.headers)
|
||||
&& Arrays.equals(dataToSend, request.dataToSend)
|
||||
&& Objects.equals(localization, request.localization);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(httpMethod, url, headers, localization);
|
||||
result = 31 * result + Arrays.hashCode(dataToSend);
|
||||
return result;
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,11 @@ public class Response {
|
||||
|
||||
private final String latestUrl;
|
||||
|
||||
public Response(final int responseCode,
|
||||
final String responseMessage,
|
||||
@Nullable final Map<String, List<String>> responseHeaders,
|
||||
@Nullable final String responseBody,
|
||||
@Nullable final String latestUrl) {
|
||||
public Response(int responseCode, String responseMessage, Map<String, List<String>> responseHeaders,
|
||||
@Nullable String responseBody, @Nullable String latestUrl) {
|
||||
this.responseCode = responseCode;
|
||||
this.responseMessage = responseMessage;
|
||||
this.responseHeaders = responseHeaders == null ? Collections.emptyMap() : responseHeaders;
|
||||
this.responseHeaders = responseHeaders != null ? responseHeaders : Collections.<String, List<String>>emptyMap();
|
||||
|
||||
this.responseBody = responseBody == null ? "" : responseBody;
|
||||
this.latestUrl = latestUrl;
|
||||
@ -63,20 +60,21 @@ public class Response {
|
||||
|
||||
/**
|
||||
* For easy access to some header value that (usually) don't repeat itself.
|
||||
* <p>For getting all the values associated to the header, use {@link #responseHeaders()} (e.g.
|
||||
* {@code Set-Cookie}).
|
||||
* <p>For getting all the values associated to the header, use {@link #responseHeaders()} (e.g. {@code Set-Cookie}).
|
||||
*
|
||||
* @param name the name of the header
|
||||
* @return the first value assigned to this header
|
||||
*/
|
||||
@Nullable
|
||||
public String getHeader(final String name) {
|
||||
for (final Map.Entry<String, List<String>> headerEntry : responseHeaders.entrySet()) {
|
||||
public String getHeader(String name) {
|
||||
for (Map.Entry<String, List<String>> headerEntry : responseHeaders.entrySet()) {
|
||||
final String key = headerEntry.getKey();
|
||||
if (key != null && key.equalsIgnoreCase(name) && !headerEntry.getValue().isEmpty()) {
|
||||
if (key != null && key.equalsIgnoreCase(name)) {
|
||||
if (headerEntry.getValue().size() > 0) {
|
||||
return headerEntry.getValue().get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class AccountTerminatedException extends ContentNotAvailableException {
|
||||
|
||||
private Reason reason = Reason.UNKNOWN;
|
||||
|
||||
public AccountTerminatedException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AccountTerminatedException(final String message, final Reason reason) {
|
||||
super(message);
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public AccountTerminatedException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* The reason for the violation. There should also be more info in the exception's message.
|
||||
*/
|
||||
public Reason getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public enum Reason {
|
||||
UNKNOWN,
|
||||
VIOLATION
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class AgeRestrictedContentException extends ContentNotAvailableException {
|
||||
public AgeRestrictedContentException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AgeRestrictedContentException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class ContentNotAvailableException extends ParsingException {
|
||||
public ContentNotAvailableException(final String message) {
|
||||
public ContentNotAvailableException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ContentNotAvailableException(final String message, final Throwable cause) {
|
||||
public ContentNotAvailableException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class ContentNotSupportedException extends ParsingException {
|
||||
public ContentNotSupportedException(final String message) {
|
||||
public ContentNotSupportedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ContentNotSupportedException(final String message, final Throwable cause) {
|
||||
public ContentNotSupportedException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
@ -3,33 +3,33 @@ package org.schabi.newpipe.extractor.exceptions;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 30.01.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ExtractionException.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ExtractionException.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ExtractionException extends Exception {
|
||||
public ExtractionException(final String message) {
|
||||
public ExtractionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ExtractionException(final Throwable cause) {
|
||||
public ExtractionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public ExtractionException(final String message, final Throwable cause) {
|
||||
public ExtractionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -3,29 +3,29 @@ package org.schabi.newpipe.extractor.exceptions;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.09.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* FoundAdException.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* FoundAdException.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class FoundAdException extends ParsingException {
|
||||
public FoundAdException(final String message) {
|
||||
public FoundAdException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FoundAdException(final String message, final Throwable cause) {
|
||||
public FoundAdException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class GeographicRestrictionException extends ContentNotAvailableException {
|
||||
public GeographicRestrictionException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public GeographicRestrictionException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class PaidContentException extends ContentNotAvailableException {
|
||||
public PaidContentException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PaidContentException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -3,30 +3,30 @@ package org.schabi.newpipe.extractor.exceptions;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 31.01.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ParsingException.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ParsingException.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
public class ParsingException extends ExtractionException {
|
||||
public ParsingException(final String message) {
|
||||
public ParsingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ParsingException(final String message, final Throwable cause) {
|
||||
public ParsingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class PrivateContentException extends ContentNotAvailableException {
|
||||
public PrivateContentException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PrivateContentException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -3,27 +3,27 @@ package org.schabi.newpipe.extractor.exceptions;
|
||||
/*
|
||||
* Created by beneth <bmauduit@beneth.fr> on 07.12.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* ReCaptchaException.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ReCaptchaException.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class ReCaptchaException extends ExtractionException {
|
||||
private final String url;
|
||||
private String url;
|
||||
|
||||
public ReCaptchaException(final String message, final String url) {
|
||||
public ReCaptchaException(String message, String url) {
|
||||
super(message);
|
||||
this.url = url;
|
||||
}
|
||||
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
/**
|
||||
* Content can't be extracted because the service requires logging in to confirm the user is not a
|
||||
* bot. Can usually only be solvable by changing IP (e.g. in the case of YouTube).
|
||||
*/
|
||||
public class SignInConfirmNotBotException extends ParsingException {
|
||||
public SignInConfirmNotBotException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class SoundCloudGoPlusContentException extends ContentNotAvailableException {
|
||||
public SoundCloudGoPlusContentException() {
|
||||
super("This track is a SoundCloud Go+ track");
|
||||
}
|
||||
|
||||
public SoundCloudGoPlusContentException(final Throwable cause) {
|
||||
super("This track is a SoundCloud Go+ track", cause);
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
/**
|
||||
* Exception for contents not supported in a country.
|
||||
*
|
||||
* <p>
|
||||
* Unsupported content means content is not intentionally geographically restricted such as for
|
||||
* distribution rights, for which {@link GeographicRestrictionException} should be used instead.
|
||||
* </p>
|
||||
*/
|
||||
public class UnsupportedContentInCountryException extends ContentNotAvailableException {
|
||||
|
||||
public UnsupportedContentInCountryException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnsupportedContentInCountryException(final String message, final Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public final class UnsupportedTabException extends UnsupportedOperationException {
|
||||
public UnsupportedTabException(final String unsupportedTab) {
|
||||
super("Unsupported tab " + unsupportedTab);
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.exceptions;
|
||||
|
||||
public class YoutubeMusicPremiumContentException extends ContentNotAvailableException {
|
||||
public YoutubeMusicPremiumContentException() {
|
||||
super("This video is a YouTube Music Premium video");
|
||||
}
|
||||
|
||||
public YoutubeMusicPremiumContentException(final Throwable cause) {
|
||||
super("This video is a YouTube Music Premium video", cause);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
* YouTube is an example of a service that has this alternative available.
|
||||
*/
|
||||
public abstract class FeedExtractor extends ListExtractor<StreamInfoItem> {
|
||||
public FeedExtractor(final StreamingService service, final ListLinkHandler listLinkHandler) {
|
||||
public FeedExtractor(StreamingService service, ListLinkHandler listLinkHandler) {
|
||||
super(service, listLinkHandler);
|
||||
}
|
||||
}
|
||||
|
@ -13,35 +13,26 @@ import java.util.List;
|
||||
|
||||
public class FeedInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
public FeedInfo(final int serviceId,
|
||||
final String id,
|
||||
final String url,
|
||||
final String originalUrl,
|
||||
final String name,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) {
|
||||
public FeedInfo(int serviceId, String id, String url, String originalUrl, String name, List<String> contentFilter, String sortFilter) {
|
||||
super(serviceId, id, url, originalUrl, name, contentFilter, sortFilter);
|
||||
}
|
||||
|
||||
public static FeedInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
public static FeedInfo getInfo(String url) throws IOException, ExtractionException {
|
||||
return getInfo(NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static FeedInfo getInfo(final StreamingService service, final String url)
|
||||
throws IOException, ExtractionException {
|
||||
public static FeedInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
|
||||
final FeedExtractor extractor = service.getFeedExtractor(url);
|
||||
|
||||
if (extractor == null) {
|
||||
throw new IllegalArgumentException("Service \"" + service.getServiceInfo().getName()
|
||||
+ "\" doesn't support FeedExtractor.");
|
||||
throw new IllegalArgumentException("Service \"" + service.getServiceInfo().getName() + "\" doesn't support FeedExtractor.");
|
||||
}
|
||||
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
public static FeedInfo getInfo(final FeedExtractor extractor)
|
||||
throws IOException, ExtractionException {
|
||||
public static FeedInfo getInfo(FeedExtractor extractor) throws IOException, ExtractionException {
|
||||
extractor.fetchPage();
|
||||
|
||||
final int serviceId = extractor.getServiceId();
|
||||
@ -52,10 +43,9 @@ public class FeedInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
final FeedInfo info = new FeedInfo(serviceId, id, url, originalUrl, name, null, null);
|
||||
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage = ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(itemsPage.getItems());
|
||||
info.setNextPage(itemsPage.getNextPage());
|
||||
info.setNextPageUrl(itemsPage.getNextPageUrl());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@ -3,21 +3,21 @@ package org.schabi.newpipe.extractor.kiosk;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.08.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* KioskExtractor.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* KioskExtractor.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
@ -31,9 +31,9 @@ import javax.annotation.Nonnull;
|
||||
public abstract class KioskExtractor<T extends InfoItem> extends ListExtractor<T> {
|
||||
private final String id;
|
||||
|
||||
public KioskExtractor(final StreamingService streamingService,
|
||||
final ListLinkHandler linkHandler,
|
||||
final String kioskId) {
|
||||
public KioskExtractor(StreamingService streamingService,
|
||||
ListLinkHandler linkHandler,
|
||||
String kioskId) {
|
||||
super(streamingService, linkHandler);
|
||||
this.id = kioskId;
|
||||
}
|
||||
@ -45,11 +45,12 @@ public abstract class KioskExtractor<T extends InfoItem> extends ListExtractor<T
|
||||
}
|
||||
|
||||
/**
|
||||
* Id should be the name of the kiosk, tho Id is used for identifying it in the frontend,
|
||||
* Id should be the name of the kiosk, tho Id is used for identifing it in the frontend,
|
||||
* so id should be kept in english.
|
||||
* In order to get the name of the kiosk in the desired language we have to
|
||||
* crawl if from the website.
|
||||
* @return the translated version of id
|
||||
* @return the tranlsated version of id
|
||||
* @throws ParsingException
|
||||
*/
|
||||
@Nonnull
|
||||
@Override
|
||||
|
@ -3,53 +3,57 @@ package org.schabi.newpipe.extractor.kiosk;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.08.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* KioskInfo.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* KioskInfo.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public final class KioskInfo extends ListInfo<StreamInfoItem> {
|
||||
private KioskInfo(final int serviceId, final ListLinkHandler linkHandler, final String name) {
|
||||
public class KioskInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
private KioskInfo(int serviceId, ListLinkHandler linkHandler, String name) throws ParsingException {
|
||||
super(serviceId, linkHandler, name);
|
||||
}
|
||||
|
||||
public static ListExtractor.InfoItemsPage<StreamInfoItem> getMoreItems(
|
||||
final StreamingService service, final String url, final Page page)
|
||||
public static ListExtractor.InfoItemsPage<StreamInfoItem> getMoreItems(StreamingService service,
|
||||
String url,
|
||||
String pageUrl)
|
||||
throws IOException, ExtractionException {
|
||||
return service.getKioskList().getExtractorByUrl(url, page).getPage(page);
|
||||
KioskList kl = service.getKioskList();
|
||||
KioskExtractor extractor = kl.getExtractorByUrl(url, pageUrl);
|
||||
return extractor.getPage(pageUrl);
|
||||
}
|
||||
|
||||
public static KioskInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
public static KioskInfo getInfo(String url) throws IOException, ExtractionException {
|
||||
return getInfo(NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static KioskInfo getInfo(final StreamingService service, final String url)
|
||||
throws IOException, ExtractionException {
|
||||
final KioskExtractor extractor = service.getKioskList().getExtractorByUrl(url, null);
|
||||
public static KioskInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
|
||||
KioskList kl = service.getKioskList();
|
||||
KioskExtractor extractor = kl.getExtractorByUrl(url, null);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
@ -59,16 +63,15 @@ public final class KioskInfo extends ListInfo<StreamInfoItem> {
|
||||
*
|
||||
* @param extractor an extractor where fetchPage() was already got called on.
|
||||
*/
|
||||
public static KioskInfo getInfo(final KioskExtractor extractor) throws ExtractionException {
|
||||
public static KioskInfo getInfo(KioskExtractor extractor) throws ExtractionException {
|
||||
|
||||
final KioskInfo info = new KioskInfo(extractor.getServiceId(),
|
||||
extractor.getLinkHandler(),
|
||||
extractor.getName());
|
||||
|
||||
final ListExtractor.InfoItemsPage<StreamInfoItem> itemsPage
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
final ListExtractor.InfoItemsPage<StreamInfoItem> itemsPage = ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(itemsPage.getItems());
|
||||
info.setNextPage(itemsPage.getNextPage());
|
||||
info.setNextPageUrl(itemsPage.getNextPageUrl());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@ -1,28 +1,24 @@
|
||||
package org.schabi.newpipe.extractor.kiosk;
|
||||
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.localization.ContentCountry;
|
||||
import org.schabi.newpipe.extractor.localization.Localization;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class KioskList {
|
||||
|
||||
public interface KioskExtractorFactory {
|
||||
KioskExtractor createNewKiosk(StreamingService streamingService,
|
||||
String url,
|
||||
String kioskId)
|
||||
KioskExtractor createNewKiosk(final StreamingService streamingService,
|
||||
final String url,
|
||||
final String kioskId)
|
||||
throws ExtractionException, IOException;
|
||||
}
|
||||
|
||||
@ -35,8 +31,8 @@ public class KioskList {
|
||||
@Nullable
|
||||
private ContentCountry forcedContentCountry;
|
||||
|
||||
private static class KioskEntry {
|
||||
KioskEntry(final KioskExtractorFactory ef, final ListLinkHandlerFactory h) {
|
||||
private class KioskEntry {
|
||||
public KioskEntry(KioskExtractorFactory ef, ListLinkHandlerFactory h) {
|
||||
extractorFactory = ef;
|
||||
handlerFactory = h;
|
||||
}
|
||||
@ -45,13 +41,11 @@ public class KioskList {
|
||||
final ListLinkHandlerFactory handlerFactory;
|
||||
}
|
||||
|
||||
public KioskList(final StreamingService service) {
|
||||
public KioskList(StreamingService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public void addKioskEntry(final KioskExtractorFactory extractorFactory,
|
||||
final ListLinkHandlerFactory handlerFactory,
|
||||
final String id)
|
||||
public void addKioskEntry(KioskExtractorFactory extractorFactory, ListLinkHandlerFactory handlerFactory, String id)
|
||||
throws Exception {
|
||||
if (kioskList.get(id) != null) {
|
||||
throw new Exception("Kiosk with type " + id + " already exists.");
|
||||
@ -59,30 +53,29 @@ public class KioskList {
|
||||
kioskList.put(id, new KioskEntry(extractorFactory, handlerFactory));
|
||||
}
|
||||
|
||||
public void setDefaultKiosk(final String kioskType) {
|
||||
public void setDefaultKiosk(String kioskType) {
|
||||
defaultKiosk = kioskType;
|
||||
}
|
||||
|
||||
public KioskExtractor getDefaultKioskExtractor()
|
||||
throws ExtractionException, IOException {
|
||||
return getDefaultKioskExtractor(null);
|
||||
return getDefaultKioskExtractor("");
|
||||
}
|
||||
|
||||
public KioskExtractor getDefaultKioskExtractor(final Page nextPage)
|
||||
public KioskExtractor getDefaultKioskExtractor(String nextPageUrl)
|
||||
throws ExtractionException, IOException {
|
||||
return getDefaultKioskExtractor(nextPage, NewPipe.getPreferredLocalization());
|
||||
return getDefaultKioskExtractor(nextPageUrl, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
public KioskExtractor getDefaultKioskExtractor(final Page nextPage,
|
||||
final Localization localization)
|
||||
public KioskExtractor getDefaultKioskExtractor(String nextPageUrl, Localization localization)
|
||||
throws ExtractionException, IOException {
|
||||
if (!isNullOrEmpty(defaultKiosk)) {
|
||||
return getExtractorById(defaultKiosk, nextPage, localization);
|
||||
if (defaultKiosk != null && !defaultKiosk.equals("")) {
|
||||
return getExtractorById(defaultKiosk, nextPageUrl, localization);
|
||||
} else {
|
||||
final String first = kioskList.keySet().stream().findAny().orElse(null);
|
||||
if (first != null) {
|
||||
if (!kioskList.isEmpty()) {
|
||||
// if not set get any entry
|
||||
return getExtractorById(first, nextPage, localization);
|
||||
Object[] keySet = kioskList.keySet().toArray();
|
||||
return getExtractorById(keySet[0].toString(), nextPageUrl, localization);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@ -93,28 +86,22 @@ public class KioskList {
|
||||
return defaultKiosk;
|
||||
}
|
||||
|
||||
public KioskExtractor getExtractorById(final String kioskId, final Page nextPage)
|
||||
public KioskExtractor getExtractorById(String kioskId, String nextPageUrl)
|
||||
throws ExtractionException, IOException {
|
||||
return getExtractorById(kioskId, nextPage, NewPipe.getPreferredLocalization());
|
||||
return getExtractorById(kioskId, nextPageUrl, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
public KioskExtractor getExtractorById(final String kioskId,
|
||||
final Page nextPage,
|
||||
final Localization localization)
|
||||
public KioskExtractor getExtractorById(String kioskId, String nextPageUrl, Localization localization)
|
||||
throws ExtractionException, IOException {
|
||||
final KioskEntry ke = kioskList.get(kioskId);
|
||||
KioskEntry ke = kioskList.get(kioskId);
|
||||
if (ke == null) {
|
||||
throw new ExtractionException("No kiosk found with the type: " + kioskId);
|
||||
} else {
|
||||
final KioskExtractor kioskExtractor = ke.extractorFactory.createNewKiosk(service,
|
||||
ke.handlerFactory.fromId(kioskId).getUrl(), kioskId);
|
||||
|
||||
if (forcedLocalization != null) {
|
||||
kioskExtractor.forceLocalization(forcedLocalization);
|
||||
}
|
||||
if (forcedContentCountry != null) {
|
||||
kioskExtractor.forceContentCountry(forcedContentCountry);
|
||||
}
|
||||
if (forcedLocalization != null) kioskExtractor.forceLocalization(forcedLocalization);
|
||||
if (forcedContentCountry != null) kioskExtractor.forceContentCountry(forcedContentCountry);
|
||||
|
||||
return kioskExtractor;
|
||||
}
|
||||
@ -124,33 +111,31 @@ public class KioskList {
|
||||
return kioskList.keySet();
|
||||
}
|
||||
|
||||
public KioskExtractor getExtractorByUrl(final String url, final Page nextPage)
|
||||
public KioskExtractor getExtractorByUrl(String url, String nextPageUrl)
|
||||
throws ExtractionException, IOException {
|
||||
return getExtractorByUrl(url, nextPage, NewPipe.getPreferredLocalization());
|
||||
return getExtractorByUrl(url, nextPageUrl, NewPipe.getPreferredLocalization());
|
||||
}
|
||||
|
||||
public KioskExtractor getExtractorByUrl(final String url,
|
||||
final Page nextPage,
|
||||
final Localization localization)
|
||||
public KioskExtractor getExtractorByUrl(String url, String nextPageUrl, Localization localization)
|
||||
throws ExtractionException, IOException {
|
||||
for (final Map.Entry<String, KioskEntry> e : kioskList.entrySet()) {
|
||||
final KioskEntry ke = e.getValue();
|
||||
for (Map.Entry<String, KioskEntry> e : kioskList.entrySet()) {
|
||||
KioskEntry ke = e.getValue();
|
||||
if (ke.handlerFactory.acceptUrl(url)) {
|
||||
return getExtractorById(ke.handlerFactory.getId(url), nextPage, localization);
|
||||
return getExtractorById(ke.handlerFactory.getId(url), nextPageUrl, localization);
|
||||
}
|
||||
}
|
||||
throw new ExtractionException("Could not find a kiosk that fits to the url: " + url);
|
||||
}
|
||||
|
||||
public ListLinkHandlerFactory getListLinkHandlerFactoryByType(final String type) {
|
||||
public ListLinkHandlerFactory getListLinkHandlerFactoryByType(String type) {
|
||||
return kioskList.get(type).handlerFactory;
|
||||
}
|
||||
|
||||
public void forceLocalization(@Nullable final Localization localization) {
|
||||
public void forceLocalization(@Nullable Localization localization) {
|
||||
this.forcedLocalization = localization;
|
||||
}
|
||||
|
||||
public void forceContentCountry(@Nullable final ContentCountry contentCountry) {
|
||||
public void forceContentCountry(@Nullable ContentCountry contentCountry) {
|
||||
this.forcedContentCountry = contentCountry;
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ public class LinkHandler implements Serializable {
|
||||
protected final String url;
|
||||
protected final String id;
|
||||
|
||||
public LinkHandler(final String originalUrl, final String url, final String id) {
|
||||
public LinkHandler(String originalUrl, String url, String id) {
|
||||
this.originalUrl = originalUrl;
|
||||
this.url = url;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public LinkHandler(final LinkHandler handler) {
|
||||
public LinkHandler(LinkHandler handler) {
|
||||
this(handler.originalUrl, handler.url, handler.id);
|
||||
}
|
||||
|
||||
|
@ -1,28 +1,27 @@
|
||||
package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.FoundAdException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/*
|
||||
* Created by Christian Schabesberger on 26.07.16.
|
||||
*
|
||||
* Copyright (C) 2016 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* LinkHandlerFactory.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* LinkHandlerFactory.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public abstract class LinkHandlerFactory {
|
||||
@ -31,14 +30,11 @@ public abstract class LinkHandlerFactory {
|
||||
// To Override
|
||||
///////////////////////////////////
|
||||
|
||||
public abstract String getId(String url) throws ParsingException, UnsupportedOperationException;
|
||||
public abstract String getId(String url) throws ParsingException;
|
||||
public abstract String getUrl(String id) throws ParsingException;
|
||||
public abstract boolean onAcceptUrl(final String url) throws ParsingException;
|
||||
|
||||
public abstract String getUrl(String id) throws ParsingException, UnsupportedOperationException;
|
||||
|
||||
public abstract boolean onAcceptUrl(String url) throws ParsingException;
|
||||
|
||||
public String getUrl(final String id, final String baseUrl)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
public String getUrl(String id, String baseUrl) throws ParsingException {
|
||||
return getUrl(id);
|
||||
}
|
||||
|
||||
@ -46,52 +42,30 @@ public abstract class LinkHandlerFactory {
|
||||
// Logic
|
||||
///////////////////////////////////
|
||||
|
||||
/**
|
||||
* Builds a {@link LinkHandler} from a url.<br>
|
||||
* Be sure to call {@link Utils#followGoogleRedirectIfNeeded(String)} on the url if overriding
|
||||
* this function.
|
||||
*
|
||||
* @param url the url to extract path and id from
|
||||
* @return a {@link LinkHandler} complete with information
|
||||
*/
|
||||
public LinkHandler fromUrl(final String url) throws ParsingException {
|
||||
if (Utils.isNullOrEmpty(url)) {
|
||||
throw new IllegalArgumentException("The url is null or empty");
|
||||
}
|
||||
final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
|
||||
final String baseUrl = Utils.getBaseUrl(polishedUrl);
|
||||
return fromUrl(polishedUrl, baseUrl);
|
||||
public LinkHandler fromUrl(String url) throws ParsingException {
|
||||
if (url == null) throw new IllegalArgumentException("url can not be null");
|
||||
final String baseUrl = Utils.getBaseUrl(url);
|
||||
return fromUrl(url, baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link LinkHandler} from an URL and a base URL. The URL is expected to be already
|
||||
* polished from Google search redirects (otherwise how could {@code baseUrl} have been
|
||||
* extracted?).<br>
|
||||
* So do not call {@link Utils#followGoogleRedirectIfNeeded(String)} on the URL if overriding
|
||||
* this function, since that should be done in {@link #fromUrl(String)}.
|
||||
*
|
||||
* @param url the URL without Google search redirects to extract id from
|
||||
* @param baseUrl the base URL
|
||||
* @return a {@link LinkHandler} complete with information
|
||||
*/
|
||||
public LinkHandler fromUrl(final String url, final String baseUrl) throws ParsingException {
|
||||
Objects.requireNonNull(url, "URL cannot be null");
|
||||
public LinkHandler fromUrl(String url, String baseUrl) throws ParsingException {
|
||||
if (url == null) throw new IllegalArgumentException("url can not be null");
|
||||
if (!acceptUrl(url)) {
|
||||
throw new ParsingException("URL not accepted: " + url);
|
||||
throw new ParsingException("Malformed unacceptable url: " + url);
|
||||
}
|
||||
|
||||
final String id = getId(url);
|
||||
return new LinkHandler(url, getUrl(id, baseUrl), id);
|
||||
}
|
||||
|
||||
public LinkHandler fromId(final String id) throws ParsingException {
|
||||
Objects.requireNonNull(id, "ID cannot be null");
|
||||
public LinkHandler fromId(String id) throws ParsingException {
|
||||
if (id == null) throw new IllegalArgumentException("id can not be null");
|
||||
final String url = getUrl(id);
|
||||
return new LinkHandler(url, url, id);
|
||||
}
|
||||
|
||||
public LinkHandler fromId(final String id, final String baseUrl) throws ParsingException {
|
||||
Objects.requireNonNull(id, "ID cannot be null");
|
||||
public LinkHandler fromId(String id, String baseUrl) throws ParsingException {
|
||||
if (id == null) throw new IllegalArgumentException("id can not be null");
|
||||
final String url = getUrl(id, baseUrl);
|
||||
return new LinkHandler(url, url, id);
|
||||
}
|
||||
@ -102,6 +76,11 @@ public abstract class LinkHandlerFactory {
|
||||
* Return false if this service shall not allow to be called through ACTIONs.
|
||||
*/
|
||||
public boolean acceptUrl(final String url) throws ParsingException {
|
||||
try {
|
||||
return onAcceptUrl(url);
|
||||
} catch (FoundAdException fe) {
|
||||
throw fe;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,17 +7,17 @@ public class ListLinkHandler extends LinkHandler {
|
||||
protected final List<String> contentFilters;
|
||||
protected final String sortFilter;
|
||||
|
||||
public ListLinkHandler(final String originalUrl,
|
||||
final String url,
|
||||
final String id,
|
||||
final List<String> contentFilters,
|
||||
final String sortFilter) {
|
||||
public ListLinkHandler(String originalUrl,
|
||||
String url,
|
||||
String id,
|
||||
List<String> contentFilters,
|
||||
String sortFilter) {
|
||||
super(originalUrl, url, id);
|
||||
this.contentFilters = Collections.unmodifiableList(contentFilters);
|
||||
this.sortFilter = sortFilter;
|
||||
}
|
||||
|
||||
public ListLinkHandler(final ListLinkHandler handler) {
|
||||
public ListLinkHandler(ListLinkHandler handler) {
|
||||
this(handler.originalUrl,
|
||||
handler.url,
|
||||
handler.id,
|
||||
@ -25,12 +25,14 @@ public class ListLinkHandler extends LinkHandler {
|
||||
handler.sortFilter);
|
||||
}
|
||||
|
||||
public ListLinkHandler(final LinkHandler handler) {
|
||||
public ListLinkHandler(LinkHandler handler,
|
||||
List<String> contentFilters,
|
||||
String sortFilter) {
|
||||
this(handler.originalUrl,
|
||||
handler.url,
|
||||
handler.id,
|
||||
Collections.emptyList(),
|
||||
"");
|
||||
contentFilters,
|
||||
sortFilter);
|
||||
}
|
||||
|
||||
public List<String> getContentFilters() {
|
||||
|
@ -5,7 +5,6 @@ import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||
|
||||
@ -13,14 +12,17 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||
// To Override
|
||||
///////////////////////////////////
|
||||
|
||||
public abstract String getUrl(String id, List<String> contentFilter, String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException;
|
||||
public List<String> getContentFilter(String url) throws ParsingException {
|
||||
return new ArrayList<>(0);
|
||||
}
|
||||
|
||||
public String getUrl(final String id,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter,
|
||||
final String baseUrl)
|
||||
throws ParsingException, UnsupportedOperationException {
|
||||
public String getSortFilter(String url) throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
|
||||
public abstract String getUrl(String id, List<String> contentFilter, String sortFilter) throws ParsingException;
|
||||
|
||||
public String getUrl(String id, List<String> contentFilter, String sortFilter, String baseUrl) throws ParsingException {
|
||||
return getUrl(id, contentFilter, sortFilter);
|
||||
}
|
||||
|
||||
@ -29,62 +31,61 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||
///////////////////////////////////
|
||||
|
||||
@Override
|
||||
public ListLinkHandler fromUrl(final String url) throws ParsingException {
|
||||
final String polishedUrl = Utils.followGoogleRedirectIfNeeded(url);
|
||||
final String baseUrl = Utils.getBaseUrl(polishedUrl);
|
||||
return fromUrl(polishedUrl, baseUrl);
|
||||
public ListLinkHandler fromUrl(String url) throws ParsingException {
|
||||
String baseUrl = Utils.getBaseUrl(url);
|
||||
return fromUrl(url, baseUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandler fromUrl(final String url, final String baseUrl) throws ParsingException {
|
||||
Objects.requireNonNull(url, "URL may not be null");
|
||||
return new ListLinkHandler(super.fromUrl(url, baseUrl));
|
||||
public ListLinkHandler fromUrl(String url, String baseUrl) throws ParsingException {
|
||||
if (url == null) throw new IllegalArgumentException("url may not be null");
|
||||
|
||||
return new ListLinkHandler(super.fromUrl(url, baseUrl), getContentFilter(url), getSortFilter(url));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandler fromId(final String id) throws ParsingException {
|
||||
return new ListLinkHandler(super.fromId(id));
|
||||
public ListLinkHandler fromId(String id) throws ParsingException {
|
||||
return new ListLinkHandler(super.fromId(id), new ArrayList<String>(0), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandler fromId(final String id, final String baseUrl) throws ParsingException {
|
||||
return new ListLinkHandler(super.fromId(id, baseUrl));
|
||||
public ListLinkHandler fromId(String id, String baseUrl) throws ParsingException {
|
||||
return new ListLinkHandler(super.fromId(id, baseUrl), new ArrayList<String>(0), "");
|
||||
}
|
||||
|
||||
public ListLinkHandler fromQuery(final String id,
|
||||
final List<String> contentFilters,
|
||||
final String sortFilter) throws ParsingException {
|
||||
public ListLinkHandler fromQuery(String id,
|
||||
List<String> contentFilters,
|
||||
String sortFilter) throws ParsingException {
|
||||
final String url = getUrl(id, contentFilters, sortFilter);
|
||||
return new ListLinkHandler(url, url, id, contentFilters, sortFilter);
|
||||
}
|
||||
|
||||
public ListLinkHandler fromQuery(final String id,
|
||||
final List<String> contentFilters,
|
||||
final String sortFilter,
|
||||
final String baseUrl) throws ParsingException {
|
||||
public ListLinkHandler fromQuery(String id,
|
||||
List<String> contentFilters,
|
||||
String sortFilter, String baseUrl) throws ParsingException {
|
||||
final String url = getUrl(id, contentFilters, sortFilter, baseUrl);
|
||||
return new ListLinkHandler(url, url, id, contentFilters, sortFilter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* For making ListLinkHandlerFactory compatible with LinkHandlerFactory we need to override
|
||||
* this, however it should not be overridden by the actual implementation.
|
||||
* For making ListLinkHandlerFactory compatible with LinkHandlerFactory we need to override this,
|
||||
* however it should not be overridden by the actual implementation.
|
||||
*
|
||||
* @return the url corresponding to id without any filters applied
|
||||
* @param id
|
||||
* @return the url coresponding to id without any filters applied
|
||||
*/
|
||||
public String getUrl(final String id) throws ParsingException, UnsupportedOperationException {
|
||||
return getUrl(id, new ArrayList<>(0), "");
|
||||
public String getUrl(String id) throws ParsingException {
|
||||
return getUrl(id, new ArrayList<String>(0), "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl(final String id, final String baseUrl) throws ParsingException {
|
||||
return getUrl(id, new ArrayList<>(0), "", baseUrl);
|
||||
public String getUrl(String id, String baseUrl) throws ParsingException {
|
||||
return getUrl(id, new ArrayList<String>(0), "", baseUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Will returns content filter the corresponding extractor can handle like "channels", "videos",
|
||||
* "music", etc.
|
||||
* Will returns content filter the corresponding extractor can handle like "channels", "videos", "music", etc.
|
||||
*
|
||||
* @return filter that can be applied when building a query for getting a list
|
||||
*/
|
||||
@ -93,8 +94,7 @@ public abstract class ListLinkHandlerFactory extends LinkHandlerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Will returns sort filter the corresponding extractor can handle like "A-Z", "oldest first",
|
||||
* "size", etc.
|
||||
* Will returns sort filter the corresponding extractor can handle like "A-Z", "oldest first", "size", etc.
|
||||
*
|
||||
* @return filter that can be applied when building a query for getting a list
|
||||
*/
|
||||
|
@ -1,54 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@link ListLinkHandler} which can be used to be returned from {@link
|
||||
* org.schabi.newpipe.extractor.channel.ChannelInfo#getTabs() ChannelInfo#getTabs()} when a
|
||||
* specific tab's data has already been fetched.
|
||||
*
|
||||
* <p>
|
||||
* This class allows passing a builder for a {@link ChannelTabExtractor} that can hold references
|
||||
* to variables.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note: a service that wishes to use this class in one of its {@link
|
||||
* org.schabi.newpipe.extractor.channel.ChannelExtractor ChannelExtractor}s must also add the
|
||||
* following snippet of code in the service's
|
||||
* {@link StreamingService#getChannelTabExtractor(ListLinkHandler)}:
|
||||
* <pre>
|
||||
* if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
|
||||
* return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class ReadyChannelTabListLinkHandler extends ListLinkHandler {
|
||||
|
||||
public interface ChannelTabExtractorBuilder extends Serializable {
|
||||
@Nonnull
|
||||
ChannelTabExtractor build(@Nonnull StreamingService service,
|
||||
@Nonnull ListLinkHandler linkHandler);
|
||||
}
|
||||
|
||||
private final ChannelTabExtractorBuilder extractorBuilder;
|
||||
|
||||
public ReadyChannelTabListLinkHandler(
|
||||
final String url,
|
||||
final String channelId,
|
||||
@Nonnull final String channelTab,
|
||||
@Nonnull final ChannelTabExtractorBuilder extractorBuilder) {
|
||||
super(url, url, channelId, List.of(channelTab), "");
|
||||
this.extractorBuilder = extractorBuilder;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public ChannelTabExtractor getChannelTabExtractor(@Nonnull final StreamingService service) {
|
||||
return extractorBuilder.build(service, new ListLinkHandler(this));
|
||||
}
|
||||
}
|
@ -4,15 +4,15 @@ import java.util.List;
|
||||
|
||||
public class SearchQueryHandler extends ListLinkHandler {
|
||||
|
||||
public SearchQueryHandler(final String originalUrl,
|
||||
final String url,
|
||||
final String searchString,
|
||||
final List<String> contentFilters,
|
||||
final String sortFilter) {
|
||||
public SearchQueryHandler(String originalUrl,
|
||||
String url,
|
||||
String searchString,
|
||||
List<String> contentFilters,
|
||||
String sortFilter) {
|
||||
super(originalUrl, url, searchString, contentFilters, sortFilter);
|
||||
}
|
||||
|
||||
public SearchQueryHandler(final ListLinkHandler handler) {
|
||||
public SearchQueryHandler(ListLinkHandler handler) {
|
||||
this(handler.originalUrl,
|
||||
handler.url,
|
||||
handler.id,
|
||||
|
@ -2,7 +2,7 @@ package org.schabi.newpipe.extractor.linkhandler;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
||||
@ -12,11 +12,9 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
||||
///////////////////////////////////
|
||||
|
||||
@Override
|
||||
public abstract String getUrl(String query, List<String> contentFilter, String sortFilter)
|
||||
throws ParsingException, UnsupportedOperationException;
|
||||
public abstract String getUrl(String querry, List<String> contentFilter, String sortFilter) throws ParsingException;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public String getSearchString(final String url) {
|
||||
public String getSearchString(String url) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@ -25,26 +23,29 @@ public abstract class SearchQueryHandlerFactory extends ListLinkHandlerFactory {
|
||||
///////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getId(final String url) throws ParsingException, UnsupportedOperationException {
|
||||
public String getId(String url) {
|
||||
return getSearchString(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchQueryHandler fromQuery(final String query,
|
||||
final List<String> contentFilter,
|
||||
final String sortFilter) throws ParsingException {
|
||||
return new SearchQueryHandler(super.fromQuery(query, contentFilter, sortFilter));
|
||||
public SearchQueryHandler fromQuery(String querry,
|
||||
List<String> contentFilter,
|
||||
String sortFilter) throws ParsingException {
|
||||
return new SearchQueryHandler(super.fromQuery(querry, contentFilter, sortFilter));
|
||||
}
|
||||
|
||||
public SearchQueryHandler fromQuery(final String query) throws ParsingException {
|
||||
return fromQuery(query, Collections.emptyList(), "");
|
||||
public SearchQueryHandler fromQuery(String querry) throws ParsingException {
|
||||
return fromQuery(querry, new ArrayList<String>(0), "");
|
||||
}
|
||||
|
||||
/**
|
||||
* It's not mandatory for NewPipe to handle the Url
|
||||
*
|
||||
* @param url
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean onAcceptUrl(final String url) {
|
||||
public boolean onAcceptUrl(String url) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.schabi.newpipe.extractor.localization;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -10,26 +9,23 @@ import java.util.List;
|
||||
/**
|
||||
* Represents a country that should be used when fetching content.
|
||||
* <p>
|
||||
* YouTube, for example, give different results in their feed depending on which country is
|
||||
* selected.
|
||||
* YouTube, for example, give different results in their feed depending on which country is selected.
|
||||
* </p>
|
||||
*/
|
||||
public class ContentCountry implements Serializable {
|
||||
public static final ContentCountry DEFAULT =
|
||||
new ContentCountry(Localization.DEFAULT.getCountryCode());
|
||||
public static final ContentCountry DEFAULT = new ContentCountry(Localization.DEFAULT.getCountryCode());
|
||||
|
||||
@Nonnull
|
||||
private final String countryCode;
|
||||
@Nonnull private final String countryCode;
|
||||
|
||||
public static List<ContentCountry> listFrom(final String... countryCodeList) {
|
||||
public static List<ContentCountry> listFrom(String... countryCodeList) {
|
||||
final List<ContentCountry> toReturn = new ArrayList<>();
|
||||
for (final String countryCode : countryCodeList) {
|
||||
for (String countryCode : countryCodeList) {
|
||||
toReturn.add(new ContentCountry(countryCode));
|
||||
}
|
||||
return Collections.unmodifiableList(toReturn);
|
||||
}
|
||||
|
||||
public ContentCountry(@Nonnull final String countryCode) {
|
||||
public ContentCountry(@Nonnull String countryCode) {
|
||||
this.countryCode = countryCode;
|
||||
}
|
||||
|
||||
@ -44,15 +40,11 @@ public class ContentCountry implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof ContentCountry)) {
|
||||
return false;
|
||||
}
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ContentCountry)) return false;
|
||||
|
||||
final ContentCountry that = (ContentCountry) o;
|
||||
ContentCountry that = (ContentCountry) o;
|
||||
|
||||
return countryCode.equals(that.countryCode);
|
||||
}
|
||||
|
@ -1,70 +1,37 @@
|
||||
package org.schabi.newpipe.extractor.localization;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.Serializable;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
/**
|
||||
* A wrapper class that provides a field to describe if the date/time is precise or just an
|
||||
* approximation.
|
||||
* A wrapper class that provides a field to describe if the date is precise or just an approximation.
|
||||
*/
|
||||
public class DateWrapper implements Serializable {
|
||||
@Nonnull
|
||||
private final OffsetDateTime offsetDateTime;
|
||||
@NonNull private final Calendar date;
|
||||
private final boolean isApproximation;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #DateWrapper(OffsetDateTime)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public DateWrapper(@Nonnull final Calendar calendar) {
|
||||
//noinspection deprecation
|
||||
this(calendar, false);
|
||||
public DateWrapper(@NonNull Calendar date) {
|
||||
this(date, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #DateWrapper(OffsetDateTime, boolean)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public DateWrapper(@Nonnull final Calendar calendar, final boolean isApproximation) {
|
||||
this(OffsetDateTime.ofInstant(calendar.toInstant(), ZoneOffset.UTC), isApproximation);
|
||||
}
|
||||
|
||||
public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime) {
|
||||
this(offsetDateTime, false);
|
||||
}
|
||||
|
||||
public DateWrapper(@Nonnull final OffsetDateTime offsetDateTime,
|
||||
final boolean isApproximation) {
|
||||
this.offsetDateTime = offsetDateTime.withOffsetSameInstant(ZoneOffset.UTC);
|
||||
public DateWrapper(@NonNull Calendar date, boolean isApproximation) {
|
||||
this.date = date;
|
||||
this.isApproximation = isApproximation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the wrapped date/time as a {@link Calendar}.
|
||||
* @deprecated use {@link #offsetDateTime()} instead.
|
||||
* @return the wrapped date.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nonnull
|
||||
@NonNull
|
||||
public Calendar date() {
|
||||
return GregorianCalendar.from(offsetDateTime.toZonedDateTime());
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the wrapped date/time.
|
||||
*/
|
||||
@Nonnull
|
||||
public OffsetDateTime offsetDateTime() {
|
||||
return offsetDateTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the date is considered is precise or just an approximation (e.g. service only
|
||||
* returns an approximation like 2 weeks ago instead of a precise date).
|
||||
* @return if the date is considered is precise or just an approximation (e.g. service only returns an approximation
|
||||
* like 2 weeks ago instead of a precise date).
|
||||
*/
|
||||
public boolean isApproximation() {
|
||||
return isApproximation;
|
||||
|
@ -1,67 +1,57 @@
|
||||
package org.schabi.newpipe.extractor.localization;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.utils.LocaleCompat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Localization implements Serializable {
|
||||
public static final Localization DEFAULT = new Localization("en", "GB");
|
||||
|
||||
@Nonnull
|
||||
private final String languageCode;
|
||||
@Nullable
|
||||
private final String countryCode;
|
||||
@Nonnull private final String languageCode;
|
||||
@Nullable private final String countryCode;
|
||||
|
||||
/**
|
||||
* @param localizationCodeList a list of localization code, formatted like {@link
|
||||
* #getLocalizationCode()}
|
||||
* @throws IllegalArgumentException If any of the localizationCodeList is formatted incorrectly
|
||||
* @return list of Localization objects
|
||||
* @param localizationCodeList a list of localization code, formatted like {@link #getLocalizationCode()}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Localization> listFrom(final String... localizationCodeList) {
|
||||
public static List<Localization> listFrom(String... localizationCodeList) {
|
||||
final List<Localization> toReturn = new ArrayList<>();
|
||||
for (final String localizationCode : localizationCodeList) {
|
||||
toReturn.add(fromLocalizationCode(localizationCode)
|
||||
.orElseThrow(() -> new IllegalArgumentException(
|
||||
"Not a localization code: " + localizationCode
|
||||
)));
|
||||
for (String localizationCode : localizationCodeList) {
|
||||
toReturn.add(fromLocalizationCode(localizationCode));
|
||||
}
|
||||
return Collections.unmodifiableList(toReturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param localizationCode a localization code, formatted like {@link #getLocalizationCode()}
|
||||
* @return A Localization, if the code was valid.
|
||||
*/
|
||||
@Nonnull
|
||||
public static Optional<Localization> fromLocalizationCode(final String localizationCode) {
|
||||
return LocaleCompat.forLanguageTag(localizationCode).map(Localization::fromLocale);
|
||||
public static Localization fromLocalizationCode(String localizationCode) {
|
||||
final int indexSeparator = localizationCode.indexOf("-");
|
||||
|
||||
final String languageCode, countryCode;
|
||||
if (indexSeparator != -1) {
|
||||
languageCode = localizationCode.substring(0, indexSeparator);
|
||||
countryCode = localizationCode.substring(indexSeparator + 1);
|
||||
} else {
|
||||
languageCode = localizationCode;
|
||||
countryCode = null;
|
||||
}
|
||||
|
||||
public Localization(@Nonnull final String languageCode, @Nullable final String countryCode) {
|
||||
return new Localization(languageCode, countryCode);
|
||||
}
|
||||
|
||||
public Localization(@Nonnull String languageCode, @Nullable String countryCode) {
|
||||
this.languageCode = languageCode;
|
||||
this.countryCode = countryCode;
|
||||
}
|
||||
|
||||
public Localization(@Nonnull final String languageCode) {
|
||||
public Localization(@Nonnull String languageCode) {
|
||||
this(languageCode, null);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getLanguageCode() {
|
||||
return languageCode;
|
||||
}
|
||||
@ -71,15 +61,17 @@ public class Localization implements Serializable {
|
||||
return countryCode == null ? "" : countryCode;
|
||||
}
|
||||
|
||||
public static Localization fromLocale(@Nonnull final Locale locale) {
|
||||
public Locale asLocale() {
|
||||
return new Locale(getLanguageCode(), getCountryCode());
|
||||
}
|
||||
|
||||
public static Localization fromLocale(@Nonnull Locale locale) {
|
||||
return new Localization(locale.getLanguage(), locale.getCountry());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a formatted string in the form of: {@code language-Country}, or
|
||||
* just {@code language} if country is {@code null}.
|
||||
*
|
||||
* @return A correctly formatted localizationCode for this localization.
|
||||
*/
|
||||
public String getLocalizationCode() {
|
||||
return languageCode + (countryCode == null ? "" : "-" + countryCode);
|
||||
@ -91,47 +83,20 @@ public class Localization implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof Localization)) {
|
||||
return false;
|
||||
}
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof Localization)) return false;
|
||||
|
||||
final Localization that = (Localization) o;
|
||||
Localization that = (Localization) o;
|
||||
|
||||
return languageCode.equals(that.languageCode)
|
||||
&& Objects.equals(countryCode, that.countryCode);
|
||||
if (!languageCode.equals(that.languageCode)) return false;
|
||||
return countryCode != null ? countryCode.equals(that.countryCode) : that.countryCode == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = languageCode.hashCode();
|
||||
result = 31 * result + Objects.hashCode(countryCode);
|
||||
result = 31 * result + (countryCode != null ? countryCode.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a three letter language code (ISO 639-2/T) to a Locale
|
||||
* because limits of Java Locale class.
|
||||
*
|
||||
* @param code a three letter language code
|
||||
* @return the Locale corresponding
|
||||
*/
|
||||
public static Locale getLocaleFromThreeLetterCode(@Nonnull final String code)
|
||||
throws ParsingException {
|
||||
final String[] languages = Locale.getISOLanguages();
|
||||
final Map<String, Locale> localeMap = new HashMap<>(languages.length);
|
||||
for (final String language : languages) {
|
||||
final Locale locale = new Locale(language);
|
||||
localeMap.put(locale.getISO3Language(), locale);
|
||||
}
|
||||
if (localeMap.containsKey(code)) {
|
||||
return localeMap.get(code);
|
||||
} else {
|
||||
throw new ParsingException(
|
||||
"Could not get Locale from this three letter language code" + code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,21 @@ package org.schabi.newpipe.extractor.localization;
|
||||
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.timeago.PatternsHolder;
|
||||
import org.schabi.newpipe.extractor.timeago.TimeAgoUnit;
|
||||
import org.schabi.newpipe.extractor.utils.Parser;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A helper class that is meant to be used by services that need to parse durations such as
|
||||
* {@code 23 seconds} and/or upload dates in the format {@code 2 days ago} or similar.
|
||||
* A helper class that is meant to be used by services that need to parse upload dates in the
|
||||
* format '2 days ago' or similar.
|
||||
*/
|
||||
public class TimeAgoParser {
|
||||
private final PatternsHolder patternsHolder;
|
||||
private final OffsetDateTime now;
|
||||
private final Calendar consistentNow;
|
||||
|
||||
/**
|
||||
* Creates a helper to parse upload dates in the format '2 days ago'.
|
||||
@ -24,31 +24,16 @@ public class TimeAgoParser {
|
||||
* Instantiate a new {@link TimeAgoParser} every time you extract a new batch of items.
|
||||
* </p>
|
||||
*
|
||||
* @param patternsHolder An object that holds the "time ago" patterns, special cases, and the
|
||||
* language word separator.
|
||||
* @param patternsHolder An object that holds the "time ago" patterns, special cases, and the language word separator.
|
||||
*/
|
||||
public TimeAgoParser(final PatternsHolder patternsHolder) {
|
||||
this(patternsHolder, OffsetDateTime.now(ZoneOffset.UTC));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a helper to parse upload dates in the format '2 days ago'.
|
||||
* <p>
|
||||
* Instantiate a new {@link TimeAgoParser} every time you extract a new batch of items.
|
||||
* </p>
|
||||
*
|
||||
* @param patternsHolder An object that holds the "time ago" patterns, special cases, and the
|
||||
* language word separator.
|
||||
* @param now The current time
|
||||
*/
|
||||
public TimeAgoParser(final PatternsHolder patternsHolder, final OffsetDateTime now) {
|
||||
public TimeAgoParser(PatternsHolder patternsHolder) {
|
||||
this.patternsHolder = patternsHolder;
|
||||
this.now = now;
|
||||
consistentNow = Calendar.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a textual date in the format '2 days ago' into a Calendar representation which is then
|
||||
* wrapped in a {@link DateWrapper} object.
|
||||
* Parses a textual date in the format '2 days ago' into a Calendar representation which is then wrapped in a
|
||||
* {@link DateWrapper} object.
|
||||
* <p>
|
||||
* Beginning with days ago, the date is considered as an approximation.
|
||||
*
|
||||
@ -56,58 +41,67 @@ public class TimeAgoParser {
|
||||
* @return The parsed time (can be approximated)
|
||||
* @throws ParsingException if the time unit could not be recognized
|
||||
*/
|
||||
public DateWrapper parse(final String textualDate) throws ParsingException {
|
||||
for (final var caseUnitEntry : patternsHolder.specialCases().entrySet()) {
|
||||
final ChronoUnit chronoUnit = caseUnitEntry.getKey();
|
||||
for (final var caseMapToAmountEntry : caseUnitEntry.getValue().entrySet()) {
|
||||
public DateWrapper parse(String textualDate) throws ParsingException {
|
||||
for (Map.Entry<TimeAgoUnit, Map<String, Integer>> caseUnitEntry : patternsHolder.specialCases().entrySet()) {
|
||||
final TimeAgoUnit timeAgoUnit = caseUnitEntry.getKey();
|
||||
for (Map.Entry<String, Integer> caseMapToAmountEntry : caseUnitEntry.getValue().entrySet()) {
|
||||
final String caseText = caseMapToAmountEntry.getKey();
|
||||
final int caseAmount = caseMapToAmountEntry.getValue();
|
||||
final Integer caseAmount = caseMapToAmountEntry.getValue();
|
||||
|
||||
if (textualDateMatches(textualDate, caseText)) {
|
||||
return getResultFor(caseAmount, chronoUnit);
|
||||
return getResultFor(caseAmount, timeAgoUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getResultFor(parseTimeAgoAmount(textualDate), parseChronoUnit(textualDate));
|
||||
}
|
||||
|
||||
private int parseTimeAgoAmount(final String textualDate) {
|
||||
int timeAgoAmount;
|
||||
try {
|
||||
return Integer.parseInt(textualDate.replaceAll("\\D+", ""));
|
||||
} catch (final NumberFormatException ignored) {
|
||||
timeAgoAmount = parseTimeAgoAmount(textualDate);
|
||||
} catch (NumberFormatException e) {
|
||||
// If there is no valid number in the textual date,
|
||||
// assume it is 1 (as in 'a second ago').
|
||||
return 1;
|
||||
timeAgoAmount = 1;
|
||||
}
|
||||
|
||||
final TimeAgoUnit timeAgoUnit = parseTimeAgoUnit(textualDate);
|
||||
return getResultFor(timeAgoAmount, timeAgoUnit);
|
||||
}
|
||||
|
||||
private int parseTimeAgoAmount(String textualDate) throws NumberFormatException {
|
||||
String timeValueStr = textualDate.replaceAll("\\D+", "");
|
||||
return Integer.parseInt(timeValueStr);
|
||||
}
|
||||
|
||||
private TimeAgoUnit parseTimeAgoUnit(String textualDate) throws ParsingException {
|
||||
for (Map.Entry<TimeAgoUnit, Collection<String>> entry : patternsHolder.asMap().entrySet()) {
|
||||
final TimeAgoUnit timeAgoUnit = entry.getKey();
|
||||
|
||||
for (String agoPhrase : entry.getValue()) {
|
||||
if (textualDateMatches(textualDate, agoPhrase)) {
|
||||
return timeAgoUnit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ChronoUnit parseChronoUnit(final String textualDate) throws ParsingException {
|
||||
return patternsHolder.asMap().entrySet().stream()
|
||||
.filter(e -> e.getValue().stream()
|
||||
.anyMatch(agoPhrase -> textualDateMatches(textualDate, agoPhrase)))
|
||||
.map(Map.Entry::getKey)
|
||||
.findFirst()
|
||||
.orElseThrow(() ->
|
||||
new ParsingException("Unable to parse the date: " + textualDate));
|
||||
throw new ParsingException("Unable to parse the date: " + textualDate);
|
||||
}
|
||||
|
||||
private boolean textualDateMatches(final String textualDate, final String agoPhrase) {
|
||||
private boolean textualDateMatches(String textualDate, String agoPhrase) {
|
||||
if (textualDate.equals(agoPhrase)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (patternsHolder.wordSeparator().isEmpty()) {
|
||||
return textualDate.toLowerCase().contains(agoPhrase.toLowerCase());
|
||||
}
|
||||
|
||||
} else {
|
||||
final String escapedPhrase = Pattern.quote(agoPhrase.toLowerCase());
|
||||
final String escapedSeparator = patternsHolder.wordSeparator().equals(" ")
|
||||
// From JDK8 → \h - Treat horizontal spaces as a normal one
|
||||
// (non-breaking space, thin space, etc.)
|
||||
// Also split the string on numbers to be able to parse strings like "2wk"
|
||||
? "[ \\t\\xA0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000\\d]"
|
||||
: Pattern.quote(patternsHolder.wordSeparator());
|
||||
final String escapedSeparator;
|
||||
if (patternsHolder.wordSeparator().equals(" ")) {
|
||||
// From JDK8 → \h - Treat horizontal spaces as a normal one (non-breaking space, thin space, etc.)
|
||||
escapedSeparator = "[ \\t\\xA0\\u1680\\u180e\\u2000-\\u200a\\u202f\\u205f\\u3000]";
|
||||
} else {
|
||||
escapedSeparator = Pattern.quote(patternsHolder.wordSeparator());
|
||||
}
|
||||
|
||||
// (^|separator)pattern($|separator)
|
||||
// Check if the pattern is surrounded by separators or start/end of the string.
|
||||
@ -116,36 +110,67 @@ public class TimeAgoParser {
|
||||
|
||||
return Parser.isMatch(pattern, textualDate.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
private DateWrapper getResultFor(final int timeAgoAmount, final ChronoUnit chronoUnit) {
|
||||
OffsetDateTime offsetDateTime = now;
|
||||
private DateWrapper getResultFor(int timeAgoAmount, TimeAgoUnit timeAgoUnit) {
|
||||
final Calendar calendarTime = getNow();
|
||||
boolean isApproximation = false;
|
||||
|
||||
switch (chronoUnit) {
|
||||
switch (timeAgoUnit) {
|
||||
case SECONDS:
|
||||
calendarTime.add(Calendar.SECOND, -timeAgoAmount);
|
||||
break;
|
||||
|
||||
case MINUTES:
|
||||
calendarTime.add(Calendar.MINUTE, -timeAgoAmount);
|
||||
break;
|
||||
|
||||
case HOURS:
|
||||
offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit);
|
||||
calendarTime.add(Calendar.HOUR_OF_DAY, -timeAgoAmount);
|
||||
break;
|
||||
|
||||
case DAYS:
|
||||
calendarTime.add(Calendar.DAY_OF_MONTH, -timeAgoAmount);
|
||||
isApproximation = true;
|
||||
break;
|
||||
|
||||
case WEEKS:
|
||||
calendarTime.add(Calendar.WEEK_OF_YEAR, -timeAgoAmount);
|
||||
isApproximation = true;
|
||||
break;
|
||||
|
||||
case MONTHS:
|
||||
offsetDateTime = offsetDateTime.minus(timeAgoAmount, chronoUnit);
|
||||
calendarTime.add(Calendar.MONTH, -timeAgoAmount);
|
||||
isApproximation = true;
|
||||
break;
|
||||
|
||||
case YEARS:
|
||||
// minusDays is needed to prevent `PrettyTime` from showing '12 months ago'.
|
||||
offsetDateTime = offsetDateTime.minusYears(timeAgoAmount).minusDays(1);
|
||||
calendarTime.add(Calendar.YEAR, -timeAgoAmount);
|
||||
// Prevent `PrettyTime` from showing '12 months ago'.
|
||||
calendarTime.add(Calendar.DAY_OF_MONTH, -1);
|
||||
isApproximation = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isApproximation) {
|
||||
offsetDateTime = offsetDateTime.truncatedTo(ChronoUnit.HOURS);
|
||||
markApproximatedTime(calendarTime);
|
||||
}
|
||||
|
||||
return new DateWrapper(offsetDateTime, isApproximation);
|
||||
return new DateWrapper(calendarTime, isApproximation);
|
||||
}
|
||||
|
||||
private Calendar getNow() {
|
||||
return (Calendar) consistentNow.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the time as approximated by setting minutes, seconds and milliseconds to 0.
|
||||
*
|
||||
* @param calendarTime Time to be marked as approximated
|
||||
*/
|
||||
private void markApproximatedTime(Calendar calendarTime) {
|
||||
calendarTime.set(Calendar.MINUTE, 0);
|
||||
calendarTime.set(Calendar.SECOND, 0);
|
||||
calendarTime.set(Calendar.MILLISECOND, 0);
|
||||
}
|
||||
}
|
||||
|
@ -3,23 +3,17 @@ package org.schabi.newpipe.extractor.localization;
|
||||
import org.schabi.newpipe.extractor.timeago.PatternsHolder;
|
||||
import org.schabi.newpipe.extractor.timeago.PatternsManager;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class TimeAgoPatternsManager {
|
||||
private TimeAgoPatternsManager() {
|
||||
public class TimeAgoPatternsManager {
|
||||
@Nullable
|
||||
private static PatternsHolder getPatternsFor(@Nonnull Localization localization) {
|
||||
return PatternsManager.getPatterns(localization.getLanguageCode(), localization.getCountryCode());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PatternsHolder getPatternsFor(@Nonnull final Localization localization) {
|
||||
return PatternsManager.getPatterns(localization.getLanguageCode(),
|
||||
localization.getCountryCode());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TimeAgoParser getTimeAgoParserFor(@Nonnull final Localization localization) {
|
||||
public static TimeAgoParser getTimeAgoParserFor(@Nonnull Localization localization) {
|
||||
final PatternsHolder holder = getPatternsFor(localization);
|
||||
|
||||
if (holder == null) {
|
||||
@ -28,17 +22,4 @@ public final class TimeAgoPatternsManager {
|
||||
|
||||
return new TimeAgoParser(holder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static TimeAgoParser getTimeAgoParserFor(
|
||||
@Nonnull final Localization localization,
|
||||
@Nonnull final OffsetDateTime now) {
|
||||
final PatternsHolder holder = getPatternsFor(localization);
|
||||
|
||||
if (holder == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TimeAgoParser(holder, now);
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +1,30 @@
|
||||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class PlaylistExtractor extends ListExtractor<StreamInfoItem> {
|
||||
|
||||
public PlaylistExtractor(final StreamingService service, final ListLinkHandler linkHandler) {
|
||||
public PlaylistExtractor(StreamingService service, ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
public abstract String getThumbnailUrl() throws ParsingException;
|
||||
public abstract String getBannerUrl() throws ParsingException;
|
||||
|
||||
public abstract String getUploaderUrl() throws ParsingException;
|
||||
public abstract String getUploaderName() throws ParsingException;
|
||||
@Nonnull
|
||||
public abstract List<Image> getUploaderAvatars() throws ParsingException;
|
||||
public abstract boolean isUploaderVerified() throws ParsingException;
|
||||
public abstract String getUploaderAvatarUrl() throws ParsingException;
|
||||
|
||||
public abstract long getStreamCount() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
public abstract Description getDescription() throws ParsingException;
|
||||
@Nonnull public abstract String getSubChannelName() throws ParsingException;
|
||||
@Nonnull public abstract String getSubChannelUrl() throws ParsingException;
|
||||
@Nonnull public abstract String getSubChannelAvatarUrl() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getBanners() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSubChannelName() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getSubChannelUrl() throws ParsingException {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getSubChannelAvatars() throws ParsingException {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
public PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
|
||||
return PlaylistInfo.PlaylistType.NORMAL;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,12 @@
|
||||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
@ -17,71 +14,26 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
public class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
|
||||
/**
|
||||
* Mixes are handled as particular playlists in NewPipeExtractor. {@link PlaylistType#NORMAL} is
|
||||
* for non-mixes, while other values are for the different types of mixes. The type of a mix
|
||||
* depends on how its contents are autogenerated.
|
||||
*/
|
||||
public enum PlaylistType {
|
||||
/**
|
||||
* A normal playlist (not a mix)
|
||||
*/
|
||||
NORMAL,
|
||||
|
||||
/**
|
||||
* A mix made only of streams related to a particular stream, for example YouTube mixes
|
||||
*/
|
||||
MIX_STREAM,
|
||||
|
||||
/**
|
||||
* A mix made only of music streams related to a particular stream, for example YouTube
|
||||
* music mixes
|
||||
*/
|
||||
MIX_MUSIC,
|
||||
|
||||
/**
|
||||
* A mix made only of streams from (or related to) the same channel, for example YouTube
|
||||
* channel mixes
|
||||
*
|
||||
* @deprecated There is currently no service that implements this.
|
||||
* YouTube removed all playlists with this type around 2024-06
|
||||
*/
|
||||
@Deprecated
|
||||
MIX_CHANNEL,
|
||||
|
||||
/**
|
||||
* A mix made only of streams related to a particular (musical) genre, for example YouTube
|
||||
* genre mixes
|
||||
*/
|
||||
MIX_GENRE,
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
private PlaylistInfo(final int serviceId, final ListLinkHandler linkHandler, final String name)
|
||||
throws ParsingException {
|
||||
private PlaylistInfo(int serviceId, ListLinkHandler linkHandler, String name) throws ParsingException {
|
||||
super(serviceId, linkHandler, name);
|
||||
}
|
||||
|
||||
public static PlaylistInfo getInfo(final String url) throws IOException, ExtractionException {
|
||||
public static PlaylistInfo getInfo(String url) throws IOException, ExtractionException {
|
||||
return getInfo(NewPipe.getServiceByUrl(url), url);
|
||||
}
|
||||
|
||||
public static PlaylistInfo getInfo(final StreamingService service, final String url)
|
||||
throws IOException, ExtractionException {
|
||||
final PlaylistExtractor extractor = service.getPlaylistExtractor(url);
|
||||
public static PlaylistInfo getInfo(StreamingService service, String url) throws IOException, ExtractionException {
|
||||
PlaylistExtractor extractor = service.getPlaylistExtractor(url);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
public static InfoItemsPage<StreamInfoItem> getMoreItems(final StreamingService service,
|
||||
final String url,
|
||||
final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
return service.getPlaylistExtractor(url).getPage(page);
|
||||
public static InfoItemsPage<StreamInfoItem> getMoreItems(StreamingService service,
|
||||
String url,
|
||||
String pageUrl) throws IOException, ExtractionException {
|
||||
return service.getPlaylistExtractor(url).getPage(pageUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,8 +41,7 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
*
|
||||
* @param extractor an extractor where fetchPage() was already got called on.
|
||||
*/
|
||||
public static PlaylistInfo getInfo(final PlaylistExtractor extractor)
|
||||
throws ExtractionException {
|
||||
public static PlaylistInfo getInfo(PlaylistExtractor extractor) throws ExtractionException {
|
||||
|
||||
final PlaylistInfo info = new PlaylistInfo(
|
||||
extractor.getServiceId(),
|
||||
@ -98,122 +49,105 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
extractor.getName());
|
||||
// collect uploader extraction failures until we are sure this is not
|
||||
// just a playlist without an uploader
|
||||
final List<Throwable> uploaderParsingErrors = new ArrayList<>();
|
||||
List<Throwable> uploaderParsingErrors = new ArrayList<Throwable>(3);
|
||||
|
||||
try {
|
||||
info.setOriginalUrl(extractor.getOriginalUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setStreamCount(extractor.getStreamCount());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
info.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderUrl(extractor.getUploaderUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.setUploaderUrl("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderName(extractor.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.setUploaderName("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setUploaderAvatars(extractor.getUploaderAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.setUploaderAvatarUrl(extractor.getUploaderAvatarUrl());
|
||||
} catch (Exception e) {
|
||||
info.setUploaderAvatarUrl("");
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setSubChannelUrl(extractor.getSubChannelUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setSubChannelName(extractor.getSubChannelName());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setSubChannelAvatars(extractor.getSubChannelAvatars());
|
||||
} catch (final Exception e) {
|
||||
info.setSubChannelAvatarUrl(extractor.getSubChannelAvatarUrl());
|
||||
} catch (Exception e) {
|
||||
uploaderParsingErrors.add(e);
|
||||
}
|
||||
try {
|
||||
info.setBanners(extractor.getBanners());
|
||||
} catch (final Exception e) {
|
||||
info.setBannerUrl(extractor.getBannerUrl());
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setPlaylistType(extractor.getPlaylistType());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
// do not fail if everything but the uploader infos could be collected (TODO better comment)
|
||||
if (!uploaderParsingErrors.isEmpty()
|
||||
&& (!info.getErrors().isEmpty() || uploaderParsingErrors.size() < 3)) {
|
||||
// do not fail if everything but the uploader infos could be collected
|
||||
if (uploaderParsingErrors.size() > 0 &&
|
||||
(!info.getErrors().isEmpty() || uploaderParsingErrors.size() < 3)) {
|
||||
info.addAllErrors(uploaderParsingErrors);
|
||||
}
|
||||
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
final InfoItemsPage<StreamInfoItem> itemsPage = ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(itemsPage.getItems());
|
||||
info.setNextPage(itemsPage.getNextPage());
|
||||
info.setNextPageUrl(itemsPage.getNextPageUrl());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private String uploaderUrl = "";
|
||||
private String uploaderName = "";
|
||||
private String thumbnailUrl;
|
||||
private String bannerUrl;
|
||||
private String uploaderUrl;
|
||||
private String uploaderName;
|
||||
private String uploaderAvatarUrl;
|
||||
private String subChannelUrl;
|
||||
private String subChannelName;
|
||||
private Description description;
|
||||
@Nonnull
|
||||
private List<Image> banners = List.of();
|
||||
@Nonnull
|
||||
private List<Image> subChannelAvatars = List.of();
|
||||
@Nonnull
|
||||
private List<Image> thumbnails = List.of();
|
||||
@Nonnull
|
||||
private List<Image> uploaderAvatars = List.of();
|
||||
private long streamCount;
|
||||
private PlaylistType playlistType;
|
||||
private String subChannelAvatarUrl;
|
||||
private long streamCount = 0;
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getThumbnails() {
|
||||
return thumbnails;
|
||||
public String getThumbnailUrl() {
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
public void setThumbnails(@Nonnull final List<Image> thumbnails) {
|
||||
this.thumbnails = thumbnails;
|
||||
public void setThumbnailUrl(String thumbnailUrl) {
|
||||
this.thumbnailUrl = thumbnailUrl;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getBanners() {
|
||||
return banners;
|
||||
public String getBannerUrl() {
|
||||
return bannerUrl;
|
||||
}
|
||||
|
||||
public void setBanners(@Nonnull final List<Image> banners) {
|
||||
this.banners = banners;
|
||||
public void setBannerUrl(String bannerUrl) {
|
||||
this.bannerUrl = bannerUrl;
|
||||
}
|
||||
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
public void setUploaderUrl(final String uploaderUrl) {
|
||||
public void setUploaderUrl(String uploaderUrl) {
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
@ -221,24 +155,23 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
return uploaderName;
|
||||
}
|
||||
|
||||
public void setUploaderName(final String uploaderName) {
|
||||
public void setUploaderName(String uploaderName) {
|
||||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return uploaderAvatars;
|
||||
public String getUploaderAvatarUrl() {
|
||||
return uploaderAvatarUrl;
|
||||
}
|
||||
|
||||
public void setUploaderAvatars(@Nonnull final List<Image> uploaderAvatars) {
|
||||
this.uploaderAvatars = uploaderAvatars;
|
||||
public void setUploaderAvatarUrl(String uploaderAvatarUrl) {
|
||||
this.uploaderAvatarUrl = uploaderAvatarUrl;
|
||||
}
|
||||
|
||||
public String getSubChannelUrl() {
|
||||
return subChannelUrl;
|
||||
}
|
||||
|
||||
public void setSubChannelUrl(final String subChannelUrl) {
|
||||
public void setSubChannelUrl(String subChannelUrl) {
|
||||
this.subChannelUrl = subChannelUrl;
|
||||
}
|
||||
|
||||
@ -246,40 +179,23 @@ public final class PlaylistInfo extends ListInfo<StreamInfoItem> {
|
||||
return subChannelName;
|
||||
}
|
||||
|
||||
public void setSubChannelName(final String subChannelName) {
|
||||
public void setSubChannelName(String subChannelName) {
|
||||
this.subChannelName = subChannelName;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Image> getSubChannelAvatars() {
|
||||
return subChannelAvatars;
|
||||
public String getSubChannelAvatarUrl() {
|
||||
return subChannelAvatarUrl;
|
||||
}
|
||||
|
||||
public void setSubChannelAvatars(@Nonnull final List<Image> subChannelAvatars) {
|
||||
this.subChannelAvatars = subChannelAvatars;
|
||||
public void setSubChannelAvatarUrl(String subChannelAvatarUrl) {
|
||||
this.subChannelAvatarUrl = subChannelAvatarUrl;
|
||||
}
|
||||
|
||||
public long getStreamCount() {
|
||||
return streamCount;
|
||||
}
|
||||
|
||||
public void setStreamCount(final long streamCount) {
|
||||
public void setStreamCount(long streamCount) {
|
||||
this.streamCount = streamCount;
|
||||
}
|
||||
|
||||
public Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final Description description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public PlaylistType getPlaylistType() {
|
||||
return playlistType;
|
||||
}
|
||||
|
||||
public void setPlaylistType(final PlaylistType playlistType) {
|
||||
this.playlistType = playlistType;
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,16 @@
|
||||
package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class PlaylistInfoItem extends InfoItem {
|
||||
|
||||
private String uploaderName;
|
||||
private String uploaderUrl;
|
||||
private boolean uploaderVerified;
|
||||
/**
|
||||
* How many streams this playlist have
|
||||
*/
|
||||
private long streamCount = 0;
|
||||
private Description description;
|
||||
private PlaylistInfo.PlaylistType playlistType;
|
||||
|
||||
public PlaylistInfoItem(final int serviceId, final String url, final String name) {
|
||||
public PlaylistInfoItem(int serviceId, String url, String name) {
|
||||
super(InfoType.PLAYLIST, serviceId, url, name);
|
||||
}
|
||||
|
||||
@ -25,48 +18,15 @@ public class PlaylistInfoItem extends InfoItem {
|
||||
return uploaderName;
|
||||
}
|
||||
|
||||
public void setUploaderName(final String uploaderName) {
|
||||
this.uploaderName = uploaderName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
public void setUploaderUrl(@Nullable final String uploaderUrl) {
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
public boolean isUploaderVerified() {
|
||||
return uploaderVerified;
|
||||
}
|
||||
|
||||
public void setUploaderVerified(final boolean uploaderVerified) {
|
||||
this.uploaderVerified = uploaderVerified;
|
||||
public void setUploaderName(String uploader_name) {
|
||||
this.uploaderName = uploader_name;
|
||||
}
|
||||
|
||||
public long getStreamCount() {
|
||||
return streamCount;
|
||||
}
|
||||
|
||||
public void setStreamCount(final long streamCount) {
|
||||
this.streamCount = streamCount;
|
||||
}
|
||||
|
||||
public Description getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(final Description description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public PlaylistInfo.PlaylistType getPlaylistType() {
|
||||
return playlistType;
|
||||
}
|
||||
|
||||
public void setPlaylistType(final PlaylistInfo.PlaylistType playlistType) {
|
||||
this.playlistType = playlistType;
|
||||
public void setStreamCount(long stream_count) {
|
||||
this.streamCount = stream_count;
|
||||
}
|
||||
}
|
||||
|
@ -2,52 +2,20 @@ package org.schabi.newpipe.extractor.playlist;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public interface PlaylistInfoItemExtractor extends InfoItemExtractor {
|
||||
|
||||
/**
|
||||
* Get the uploader name
|
||||
* @return the uploader name
|
||||
* @throws ParsingException
|
||||
*/
|
||||
String getUploaderName() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the uploader url
|
||||
* @return the uploader url
|
||||
*/
|
||||
String getUploaderUrl() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get whether the uploader is verified
|
||||
* @return whether the uploader is verified
|
||||
*/
|
||||
boolean isUploaderVerified() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the number of streams
|
||||
* @return the number of streams
|
||||
* @throws ParsingException
|
||||
*/
|
||||
long getStreamCount() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Get the description of the playlist if there is any.
|
||||
* Otherwise, an {@link Description#EMPTY_DESCRIPTION EMPTY_DESCRIPTION} is returned.
|
||||
* @return the playlist's description
|
||||
*/
|
||||
@Nonnull
|
||||
default Description getDescription() throws ParsingException {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type of this playlist, see {@link PlaylistInfo.PlaylistType} for a description
|
||||
* of types. If not overridden always returns {@link PlaylistInfo.PlaylistType#NORMAL}.
|
||||
*/
|
||||
@Nonnull
|
||||
default PlaylistInfo.PlaylistType getPlaylistType() throws ParsingException {
|
||||
return PlaylistInfo.PlaylistType.NORMAL;
|
||||
}
|
||||
}
|
||||
|
@ -3,52 +3,34 @@ package org.schabi.newpipe.extractor.playlist;
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
public class PlaylistInfoItemsCollector
|
||||
extends InfoItemsCollector<PlaylistInfoItem, PlaylistInfoItemExtractor> {
|
||||
public class PlaylistInfoItemsCollector extends InfoItemsCollector<PlaylistInfoItem, PlaylistInfoItemExtractor> {
|
||||
|
||||
public PlaylistInfoItemsCollector(final int serviceId) {
|
||||
public PlaylistInfoItemsCollector(int serviceId) {
|
||||
super(serviceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistInfoItem extract(final PlaylistInfoItemExtractor extractor)
|
||||
throws ParsingException {
|
||||
final PlaylistInfoItem resultItem = new PlaylistInfoItem(
|
||||
getServiceId(), extractor.getUrl(), extractor.getName());
|
||||
public PlaylistInfoItem extract(PlaylistInfoItemExtractor extractor) throws ParsingException {
|
||||
|
||||
String name = extractor.getName();
|
||||
int serviceId = getServiceId();
|
||||
String url = extractor.getUrl();
|
||||
|
||||
PlaylistInfoItem resultItem = new PlaylistInfoItem(serviceId, url, name);
|
||||
|
||||
try {
|
||||
resultItem.setUploaderName(extractor.getUploaderName());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderUrl(extractor.getUploaderUrl());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setUploaderVerified(extractor.isUploaderVerified());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setThumbnails(extractor.getThumbnails());
|
||||
} catch (final Exception e) {
|
||||
resultItem.setThumbnailUrl(extractor.getThumbnailUrl());
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setStreamCount(extractor.getStreamCount());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setDescription(extractor.getDescription());
|
||||
} catch (final Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
try {
|
||||
resultItem.setPlaylistType(extractor.getPlaylistType());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
addError(e);
|
||||
}
|
||||
return resultItem;
|
||||
|
@ -1,5 +1,8 @@
|
||||
package org.schabi.newpipe.extractor;
|
||||
package org.schabi.newpipe.extractor.search;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.InfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.InfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
@ -15,26 +18,25 @@ import java.util.List;
|
||||
/*
|
||||
* Created by Christian Schabesberger on 12.02.17.
|
||||
*
|
||||
* Copyright (C) 2017 Christian Schabesberger <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsSearchCollector.java is part of NewPipe Extractor.
|
||||
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
|
||||
* InfoItemsSearchCollector.java is part of NewPipe.
|
||||
*
|
||||
* NewPipe Extractor is free software: you can redistribute it and/or modify
|
||||
* NewPipe is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* NewPipe Extractor is distributed in the hope that it will be useful,
|
||||
* NewPipe is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with NewPipe Extractor. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A collector that can handle many extractor types, to be used when a list contains items of
|
||||
* different types (e.g. search)
|
||||
* Collector for search results
|
||||
* <p>
|
||||
* This collector can handle the following extractor types:
|
||||
* <ul>
|
||||
@ -42,15 +44,15 @@ import java.util.List;
|
||||
* <li>{@link ChannelInfoItemExtractor}</li>
|
||||
* <li>{@link PlaylistInfoItemExtractor}</li>
|
||||
* </ul>
|
||||
* Calling {@link #extract(InfoItemExtractor)} or {@link #commit(InfoItemExtractor)} with any
|
||||
* Calling {@link #extract(InfoItemExtractor)} or {@link #commit(Object)} with any
|
||||
* other extractor type will raise an exception.
|
||||
*/
|
||||
public class MultiInfoItemsCollector extends InfoItemsCollector<InfoItem, InfoItemExtractor> {
|
||||
public class InfoItemsSearchCollector extends InfoItemsCollector<InfoItem, InfoItemExtractor> {
|
||||
private final StreamInfoItemsCollector streamCollector;
|
||||
private final ChannelInfoItemsCollector userCollector;
|
||||
private final PlaylistInfoItemsCollector playlistCollector;
|
||||
|
||||
public MultiInfoItemsCollector(final int serviceId) {
|
||||
public InfoItemsSearchCollector(int serviceId) {
|
||||
super(serviceId);
|
||||
streamCollector = new StreamInfoItemsCollector(serviceId);
|
||||
userCollector = new ChannelInfoItemsCollector(serviceId);
|
||||
@ -76,7 +78,7 @@ public class MultiInfoItemsCollector extends InfoItemsCollector<InfoItem, InfoIt
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItem extract(final InfoItemExtractor extractor) throws ParsingException {
|
||||
public InfoItem extract(InfoItemExtractor extractor) throws ParsingException {
|
||||
// Use the corresponding collector for each item extractor type
|
||||
if (extractor instanceof StreamInfoItemExtractor) {
|
||||
return streamCollector.extract((StreamInfoItemExtractor) extractor);
|
@ -2,24 +2,22 @@ package org.schabi.newpipe.extractor.search;
|
||||
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.MetaInfo;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SearchExtractor extends ListExtractor<InfoItem> {
|
||||
|
||||
public static class NothingFoundException extends ExtractionException {
|
||||
public NothingFoundException(final String message) {
|
||||
public NothingFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public SearchExtractor(final StreamingService service, final SearchQueryHandler linkHandler) {
|
||||
public SearchExtractor(StreamingService service, SearchQueryHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@ -34,11 +32,11 @@ public abstract class SearchExtractor extends ListExtractor<InfoItem> {
|
||||
* {@link SearchExtractor#isCorrectedSearch()} is true.
|
||||
*
|
||||
* @return a suggestion to another query, the corrected query, or an empty String.
|
||||
* @throws ParsingException
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract String getSearchSuggestion() throws ParsingException;
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public SearchQueryHandler getLinkHandler() {
|
||||
return (SearchQueryHandler) super.getLinkHandler();
|
||||
@ -59,14 +57,4 @@ public abstract class SearchExtractor extends ListExtractor<InfoItem> {
|
||||
* @return whether the results comes from a corrected query or not.
|
||||
*/
|
||||
public abstract boolean isCorrectedSearch() throws ParsingException;
|
||||
|
||||
/**
|
||||
* Meta information about the search query.
|
||||
* <p>
|
||||
* Example: on YouTube, if you search for "Covid-19",
|
||||
* there is a box with information from the WHO about Covid-19 and a link to the WHO's website.
|
||||
* @return additional meta information about the search query
|
||||
*/
|
||||
@Nonnull
|
||||
public abstract List<MetaInfo> getMetaInfo() throws ParsingException;
|
||||
}
|
||||
|
@ -3,42 +3,35 @@ package org.schabi.newpipe.extractor.search;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.ListInfo;
|
||||
import org.schabi.newpipe.extractor.MetaInfo;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.utils.ExtractorHelper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class SearchInfo extends ListInfo<InfoItem> {
|
||||
private final String searchString;
|
||||
|
||||
private String searchString;
|
||||
private String searchSuggestion;
|
||||
private boolean isCorrectedSearch;
|
||||
private List<MetaInfo> metaInfo = List.of();
|
||||
|
||||
public SearchInfo(final int serviceId,
|
||||
final SearchQueryHandler qIHandler,
|
||||
final String searchString) {
|
||||
public SearchInfo(int serviceId,
|
||||
SearchQueryHandler qIHandler,
|
||||
String searchString) {
|
||||
super(serviceId, qIHandler, "Search");
|
||||
this.searchString = searchString;
|
||||
}
|
||||
|
||||
|
||||
public static SearchInfo getInfo(final StreamingService service,
|
||||
final SearchQueryHandler searchQuery)
|
||||
throws ExtractionException, IOException {
|
||||
final SearchExtractor extractor = service.getSearchExtractor(searchQuery);
|
||||
public static SearchInfo getInfo(StreamingService service, SearchQueryHandler searchQuery) throws ExtractionException, IOException {
|
||||
SearchExtractor extractor = service.getSearchExtractor(searchQuery);
|
||||
extractor.fetchPage();
|
||||
return getInfo(extractor);
|
||||
}
|
||||
|
||||
public static SearchInfo getInfo(final SearchExtractor extractor)
|
||||
throws ExtractionException, IOException {
|
||||
public static SearchInfo getInfo(SearchExtractor extractor) throws ExtractionException, IOException {
|
||||
final SearchInfo info = new SearchInfo(
|
||||
extractor.getServiceId(),
|
||||
extractor.getLinkHandler(),
|
||||
@ -46,39 +39,33 @@ public class SearchInfo extends ListInfo<InfoItem> {
|
||||
|
||||
try {
|
||||
info.setOriginalUrl(extractor.getOriginalUrl());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setSearchSuggestion(extractor.getSearchSuggestion());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setIsCorrectedSearch(extractor.isCorrectedSearch());
|
||||
} catch (final Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
try {
|
||||
info.setMetaInfo(extractor.getMetaInfo());
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
info.addError(e);
|
||||
}
|
||||
|
||||
final ListExtractor.InfoItemsPage<InfoItem> page
|
||||
= ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
ListExtractor.InfoItemsPage<InfoItem> page = ExtractorHelper.getItemsPageOrLogError(info, extractor);
|
||||
info.setRelatedItems(page.getItems());
|
||||
info.setNextPage(page.getNextPage());
|
||||
info.setNextPageUrl(page.getNextPageUrl());
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
public static ListExtractor.InfoItemsPage<InfoItem> getMoreItems(final StreamingService service,
|
||||
final SearchQueryHandler query,
|
||||
final Page page)
|
||||
public static ListExtractor.InfoItemsPage<InfoItem> getMoreItems(StreamingService service,
|
||||
SearchQueryHandler query,
|
||||
String pageUrl)
|
||||
throws IOException, ExtractionException {
|
||||
return service.getSearchExtractor(query).getPage(page);
|
||||
return service.getSearchExtractor(query).getPage(pageUrl);
|
||||
}
|
||||
|
||||
// Getter
|
||||
@ -94,20 +81,11 @@ public class SearchInfo extends ListInfo<InfoItem> {
|
||||
return this.isCorrectedSearch;
|
||||
}
|
||||
|
||||
public void setIsCorrectedSearch(final boolean isCorrectedSearch) {
|
||||
public void setIsCorrectedSearch(boolean isCorrectedSearch) {
|
||||
this.isCorrectedSearch = isCorrectedSearch;
|
||||
}
|
||||
|
||||
public void setSearchSuggestion(final String searchSuggestion) {
|
||||
public void setSearchSuggestion(String searchSuggestion) {
|
||||
this.searchSuggestion = searchSuggestion;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<MetaInfo> getMetaInfo() {
|
||||
return metaInfo;
|
||||
}
|
||||
|
||||
public void setMetaInfo(@Nonnull final List<MetaInfo> metaInfo) {
|
||||
this.metaInfo = metaInfo;
|
||||
}
|
||||
}
|
||||
|
@ -1,174 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp;
|
||||
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.AUDIO;
|
||||
import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.FEATURED_API_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor.KIOSK_FEATURED;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.KIOSK_RADIO;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor.RADIO_API_URL;
|
||||
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskList;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.search.SearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampCommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampFeaturedExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampPlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampRadioStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSearchExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampSuggestionExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelTabLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampCommentsLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampFeaturedLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampPlaylistLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampSearchQueryHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampStreamLinkHandlerFactory;
|
||||
import org.schabi.newpipe.extractor.stream.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.subscription.SubscriptionExtractor;
|
||||
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class BandcampService extends StreamingService {
|
||||
|
||||
public BandcampService(final int id) {
|
||||
super(id, "Bandcamp", Arrays.asList(AUDIO, COMMENTS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return BASE_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkHandlerFactory getStreamLHFactory() {
|
||||
return BandcampStreamLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelLHFactory() {
|
||||
return BandcampChannelLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getChannelTabLHFactory() {
|
||||
return BandcampChannelTabLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getPlaylistLHFactory() {
|
||||
return BandcampPlaylistLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchQueryHandlerFactory getSearchQHFactory() {
|
||||
return BandcampSearchQueryHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListLinkHandlerFactory getCommentsLHFactory() {
|
||||
return BandcampCommentsLinkHandlerFactory.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchExtractor getSearchExtractor(final SearchQueryHandler queryHandler) {
|
||||
return new BandcampSearchExtractor(this, queryHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SuggestionExtractor getSuggestionExtractor() {
|
||||
return new BandcampSuggestionExtractor(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionExtractor getSubscriptionExtractor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KioskList getKioskList() throws ExtractionException {
|
||||
final KioskList kioskList = new KioskList(this);
|
||||
final ListLinkHandlerFactory h = BandcampFeaturedLinkHandlerFactory.getInstance();
|
||||
|
||||
try {
|
||||
kioskList.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new BandcampFeaturedExtractor(
|
||||
BandcampService.this,
|
||||
h.fromUrl(FEATURED_API_URL),
|
||||
kioskId
|
||||
),
|
||||
h,
|
||||
KIOSK_FEATURED
|
||||
);
|
||||
|
||||
kioskList.addKioskEntry(
|
||||
(streamingService, url, kioskId) -> new BandcampRadioExtractor(
|
||||
BandcampService.this,
|
||||
h.fromUrl(RADIO_API_URL),
|
||||
kioskId
|
||||
),
|
||||
h,
|
||||
KIOSK_RADIO
|
||||
);
|
||||
|
||||
kioskList.setDefaultKiosk(KIOSK_FEATURED);
|
||||
|
||||
} catch (final Exception e) {
|
||||
throw new ExtractionException(e);
|
||||
}
|
||||
|
||||
return kioskList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) {
|
||||
return new BandcampChannelExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelTabExtractor getChannelTabExtractor(final ListLinkHandler linkHandler) {
|
||||
if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
|
||||
return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
|
||||
} else {
|
||||
return new BandcampChannelTabExtractor(this, linkHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistExtractor getPlaylistExtractor(final ListLinkHandler linkHandler) {
|
||||
return new BandcampPlaylistExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamExtractor getStreamExtractor(final LinkHandler linkHandler) {
|
||||
if (BandcampExtractorHelper.isRadioUrl(linkHandler.getUrl())) {
|
||||
return new BandcampRadioStreamExtractor(this, linkHandler);
|
||||
}
|
||||
return new BandcampStreamExtractor(this, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommentsExtractor getCommentsExtractor(final ListLinkHandler linkHandler) {
|
||||
return new BandcampCommentsExtractor(this, linkHandler);
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.ListExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampAlbumInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||
private final JsonObject albumInfoItem;
|
||||
private final String uploaderUrl;
|
||||
|
||||
public BandcampAlbumInfoItemExtractor(final JsonObject albumInfoItem,
|
||||
final String uploaderUrl) {
|
||||
this.albumInfoItem = albumInfoItem;
|
||||
this.uploaderUrl = uploaderUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return albumInfoItem.getString("title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return BandcampExtractorHelper.getStreamUrlFromIds(
|
||||
albumInfoItem.getLong("band_id"),
|
||||
albumInfoItem.getLong("item_id"),
|
||||
albumInfoItem.getString("item_type"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return BandcampExtractorHelper.getImagesFromImageId(albumInfoItem.getLong("art_id"), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return albumInfoItem.getString("band_name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return uploaderUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return ListExtractor.ITEM_COUNT_UNKNOWN;
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getArtistDetails;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.linkHandler.BandcampChannelTabLinkHandlerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampChannelExtractor extends ChannelExtractor {
|
||||
|
||||
private JsonObject channelInfo;
|
||||
|
||||
public BandcampChannelExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getAvatars() {
|
||||
return getImagesFromImageId(channelInfo.getLong("bio_image_id"), false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getBanners() throws ParsingException {
|
||||
/*
|
||||
* Mobile API does not return the header or not the correct header.
|
||||
* Therefore, we need to query the website
|
||||
*/
|
||||
try {
|
||||
final String html = getDownloader()
|
||||
.get(replaceHttpWithHttps(channelInfo.getString("bandcamp_url")))
|
||||
.responseBody();
|
||||
|
||||
return Stream.of(Jsoup.parse(html).getElementById("customHeader"))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||
.map(element -> element.attr("src"))
|
||||
.filter(url -> !url.isEmpty())
|
||||
.map(url -> new Image(
|
||||
replaceHttpWithHttps(url), HEIGHT_UNKNOWN, WIDTH_UNKNOWN,
|
||||
ResolutionLevel.UNKNOWN))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
|
||||
} catch (final IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Could not download artist web site", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bandcamp discontinued their RSS feeds because it hadn't been used enough.
|
||||
*/
|
||||
@Override
|
||||
public String getFeedUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSubscriberCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return channelInfo.getString("bio");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentChannelName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentChannelUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getParentChannelAvatars() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<ListLinkHandler> getTabs() throws ParsingException {
|
||||
final JsonArray discography = channelInfo.getArray("discography");
|
||||
final TabExtractorBuilder builder = new TabExtractorBuilder(discography);
|
||||
|
||||
final List<ListLinkHandler> tabs = new ArrayList<>();
|
||||
|
||||
boolean foundTrackItem = false;
|
||||
boolean foundAlbumItem = false;
|
||||
|
||||
for (final Object discographyItem : discography) {
|
||||
if (foundTrackItem && foundAlbumItem) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(discographyItem instanceof JsonObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final JsonObject discographyJsonItem = (JsonObject) discographyItem;
|
||||
final String itemType = discographyJsonItem.getString("item_type");
|
||||
|
||||
if (!foundTrackItem && "track".equals(itemType)) {
|
||||
foundTrackItem = true;
|
||||
tabs.add(new ReadyChannelTabListLinkHandler(getUrl()
|
||||
+ BandcampChannelTabLinkHandlerFactory.getUrlSuffix(ChannelTabs.TRACKS),
|
||||
getId(),
|
||||
ChannelTabs.TRACKS,
|
||||
builder));
|
||||
}
|
||||
|
||||
if (!foundAlbumItem && "album".equals(itemType)) {
|
||||
foundAlbumItem = true;
|
||||
tabs.add(new ReadyChannelTabListLinkHandler(getUrl()
|
||||
+ BandcampChannelTabLinkHandlerFactory.getUrlSuffix(ChannelTabs.ALBUMS),
|
||||
getId(),
|
||||
ChannelTabs.ALBUMS,
|
||||
builder));
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(tabs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
channelInfo = getArtistDetails(getId());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() {
|
||||
return channelInfo.getString("name");
|
||||
}
|
||||
|
||||
private static final class TabExtractorBuilder
|
||||
implements ReadyChannelTabListLinkHandler.ChannelTabExtractorBuilder {
|
||||
private final JsonArray discography;
|
||||
|
||||
TabExtractorBuilder(final JsonArray discography) {
|
||||
this.discography = discography;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public ChannelTabExtractor build(@Nonnull final StreamingService service,
|
||||
@Nonnull final ListLinkHandler linkHandler) {
|
||||
return BandcampChannelTabExtractor.fromDiscography(service, linkHandler, discography);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.channel.ChannelInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||
|
||||
public class BandcampChannelInfoItemExtractor implements ChannelInfoItemExtractor {
|
||||
|
||||
private final Element resultInfo;
|
||||
private final Element searchResult;
|
||||
|
||||
public BandcampChannelInfoItemExtractor(final Element searchResult) {
|
||||
this.searchResult = searchResult;
|
||||
resultInfo = searchResult.getElementsByClass("result-info").first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return resultInfo.getElementsByClass("heading").text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return resultInfo.getElementsByClass("itemurl").text();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromSearchResult(searchResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return resultInfo.getElementsByClass("subhead").text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSubscriberCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.InfoItem;
|
||||
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
|
||||
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampDiscographStreamInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BandcampChannelTabExtractor extends ChannelTabExtractor {
|
||||
private JsonArray discography;
|
||||
private final String filter;
|
||||
|
||||
public BandcampChannelTabExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
|
||||
final String tab = linkHandler.getContentFilters().get(0);
|
||||
switch (tab) {
|
||||
case ChannelTabs.TRACKS:
|
||||
filter = "track";
|
||||
break;
|
||||
case ChannelTabs.ALBUMS:
|
||||
filter = "album";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported channel tab: " + tab);
|
||||
}
|
||||
}
|
||||
|
||||
public static BandcampChannelTabExtractor fromDiscography(final StreamingService service,
|
||||
final ListLinkHandler linkHandler,
|
||||
final JsonArray discography) {
|
||||
final BandcampChannelTabExtractor tabExtractor =
|
||||
new BandcampChannelTabExtractor(service, linkHandler);
|
||||
tabExtractor.discography = discography;
|
||||
return tabExtractor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader) throws ParsingException {
|
||||
if (discography == null) {
|
||||
discography = BandcampExtractorHelper.getArtistDetails(getId())
|
||||
.getArray("discography");
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getInitialPage() throws IOException, ExtractionException {
|
||||
final MultiInfoItemsCollector collector = new MultiInfoItemsCollector(getServiceId());
|
||||
|
||||
for (final Object discograph : discography) {
|
||||
// A discograph is as an item appears in a discography
|
||||
if (!(discograph instanceof JsonObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final JsonObject discographJsonObject = (JsonObject) discograph;
|
||||
final String itemType = discographJsonObject.getString("item_type", "");
|
||||
|
||||
if (!itemType.equals(filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (itemType) {
|
||||
case "track":
|
||||
collector.commit(new BandcampDiscographStreamInfoItemExtractor(
|
||||
discographJsonObject, getUrl()));
|
||||
break;
|
||||
case "album":
|
||||
collector.commit(new BandcampAlbumInfoItemExtractor(
|
||||
discographJsonObject, getUrl()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<InfoItem> getPage(final Page page) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.utils.JsonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampCommentsExtractor extends CommentsExtractor {
|
||||
|
||||
private static final String REVIEWS_API_URL = BASE_API_URL + "/tralbumcollectors/2/reviews";
|
||||
|
||||
private Document document;
|
||||
|
||||
|
||||
public BandcampCommentsExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
document = Jsoup.parse(downloader.get(getLinkHandler().getUrl()).responseBody());
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<CommentsInfoItem> getInitialPage()
|
||||
throws IOException, ExtractionException {
|
||||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
|
||||
final JsonObject collectorsData = JsonUtils.toJsonObject(
|
||||
document.getElementById("collectors-data").attr("data-blob"));
|
||||
final JsonArray reviews = collectorsData.getArray("reviews");
|
||||
|
||||
for (final Object review : reviews) {
|
||||
collector.commit(
|
||||
new BandcampCommentsInfoItemExtractor((JsonObject) review, getUrl()));
|
||||
}
|
||||
|
||||
if (!collectorsData.getBoolean("more_reviews_available")) {
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
final String trackId = getTrackId();
|
||||
final String token = getNextPageToken(reviews);
|
||||
return new InfoItemsPage<>(collector, new Page(List.of(trackId, token)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<CommentsInfoItem> getPage(final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
|
||||
final CommentsInfoItemsCollector collector = new CommentsInfoItemsCollector(getServiceId());
|
||||
|
||||
final List<String> pageIds = page.getIds();
|
||||
final String trackId = pageIds.get(0);
|
||||
final String token = pageIds.get(1);
|
||||
final JsonObject reviewsData = fetchReviewsData(trackId, token);
|
||||
final JsonArray reviews = reviewsData.getArray("results");
|
||||
|
||||
for (final Object review : reviews) {
|
||||
collector.commit(
|
||||
new BandcampCommentsInfoItemExtractor((JsonObject) review, getUrl()));
|
||||
}
|
||||
|
||||
if (!reviewsData.getBoolean("more_available")) {
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector,
|
||||
new Page(List.of(trackId, getNextPageToken(reviews))));
|
||||
}
|
||||
|
||||
private JsonObject fetchReviewsData(final String trackId, final String token)
|
||||
throws ParsingException {
|
||||
try {
|
||||
return JsonUtils.toJsonObject(getDownloader().postWithContentTypeJson(
|
||||
REVIEWS_API_URL,
|
||||
Collections.emptyMap(),
|
||||
JsonWriter.string().object()
|
||||
.value("tralbum_type", "t")
|
||||
.value("tralbum_id", trackId)
|
||||
.value("token", token)
|
||||
.value("count", 7)
|
||||
.array("exclude_fan_ids").end()
|
||||
.end().done().getBytes(StandardCharsets.UTF_8)).responseBody());
|
||||
} catch (final IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Could not fetch reviews", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getNextPageToken(final JsonArray reviews) throws ParsingException {
|
||||
return reviews.stream()
|
||||
.filter(JsonObject.class::isInstance)
|
||||
.map(JsonObject.class::cast)
|
||||
.map(review -> review.getString("token"))
|
||||
.reduce((a, b) -> b) // keep only the last element
|
||||
.orElseThrow(() -> new ParsingException("Could not get token"));
|
||||
}
|
||||
|
||||
private String getTrackId() throws ParsingException {
|
||||
final JsonObject pageProperties = JsonUtils.toJsonObject(
|
||||
document.selectFirst("meta[name=bc-page-properties]")
|
||||
.attr("content"));
|
||||
return Long.toString(pageProperties.getLong("item_id"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommentsDisabled() throws ExtractionException {
|
||||
return BandcampExtractorHelper.isRadioUrl(getUrl());
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.comments.CommentsInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
public class BandcampCommentsInfoItemExtractor implements CommentsInfoItemExtractor {
|
||||
|
||||
private final JsonObject review;
|
||||
private final String url;
|
||||
|
||||
public BandcampCommentsInfoItemExtractor(final JsonObject review, final String url) {
|
||||
this.review = review;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return getCommentText().getContent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getUploaderAvatars();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Description getCommentText() throws ParsingException {
|
||||
return new Description(review.getString("why"), Description.PLAIN_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return review.getString("name");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImagesFromImageId(review.getLong("image_id"), false);
|
||||
}
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
import com.grack.nanojson.JsonWriter;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.utils.ImageSuffix;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.DateTimeException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.schabi.newpipe.extractor.Image.HEIGHT_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.Image.WIDTH_UNKNOWN;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.replaceHttpWithHttps;
|
||||
|
||||
public final class BandcampExtractorHelper {
|
||||
|
||||
/**
|
||||
* List of image IDs which preserve aspect ratio with their theoretical dimension known.
|
||||
*
|
||||
* <p>
|
||||
* Bandcamp images are not always squares, so images which preserve aspect ratio are only used.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* One of the direct consequences of this specificity is that only one dimension of images is
|
||||
* known at time, depending of the image ID.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note also that dimensions are only theoretical because if the image size is less than the
|
||||
* dimensions of the image ID, it will be not upscaled but kept to its original size.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* IDs come from <a href="https://gist.github.com/f2k1de/06f5fd0ae9c919a7c3693a44ee522213">the
|
||||
* GitHub Gist "Bandcamp File Format Parameters" by f2k1de</a>
|
||||
* </p>
|
||||
*/
|
||||
private static final List<ImageSuffix> IMAGE_URL_SUFFIXES_AND_RESOLUTIONS = List.of(
|
||||
// ID | HEIGHT | WIDTH
|
||||
new ImageSuffix("10.jpg", HEIGHT_UNKNOWN, 1200, ResolutionLevel.HIGH),
|
||||
new ImageSuffix("101.jpg", 90, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("170.jpg", 422, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
// 180 returns the same image aspect ratio and size as 171
|
||||
new ImageSuffix("171.jpg", 646, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("20.jpg", HEIGHT_UNKNOWN, 1024, ResolutionLevel.HIGH),
|
||||
// 203 returns the same image aspect ratio and size as 200
|
||||
new ImageSuffix("200.jpg", 420, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("201.jpg", 280, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("202.jpg", 140, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("204.jpg", 360, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("205.jpg", 240, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("206.jpg", 180, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM),
|
||||
new ImageSuffix("207.jpg", 120, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("43.jpg", 100, WIDTH_UNKNOWN, ResolutionLevel.LOW),
|
||||
new ImageSuffix("44.jpg", 200, WIDTH_UNKNOWN, ResolutionLevel.MEDIUM));
|
||||
|
||||
private static final String IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX = "_\\d+\\.\\w+";
|
||||
private static final String IMAGES_DOMAIN_AND_PATH = "https://f4.bcbits.com/img/";
|
||||
|
||||
public static final String BASE_URL = "https://bandcamp.com";
|
||||
public static final String BASE_API_URL = BASE_URL + "/api";
|
||||
|
||||
private BandcampExtractorHelper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate all these parameters together to the URL of the corresponding album or track
|
||||
* using the mobile API
|
||||
*/
|
||||
public static String getStreamUrlFromIds(final long bandId,
|
||||
final long itemId,
|
||||
final String itemType) throws ParsingException {
|
||||
try {
|
||||
final String jsonString = NewPipe.getDownloader().get(
|
||||
BASE_API_URL + "/mobile/22/tralbum_details?band_id=" + bandId
|
||||
+ "&tralbum_id=" + itemId + "&tralbum_type=" + itemType.charAt(0))
|
||||
.responseBody();
|
||||
|
||||
return replaceHttpWithHttps(JsonParser.object().from(jsonString)
|
||||
.getString("bandcamp_url"));
|
||||
|
||||
} catch (final JsonParserException | ReCaptchaException | IOException e) {
|
||||
throw new ParsingException("Ids could not be translated to URL", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch artist details from mobile endpoint.
|
||||
* <a href="https://notabug.org/fynngodau/bandcampDirect/wiki/rewindBandcamp+%E2%80%93+Fetching+artist+details">
|
||||
* More technical info.</a>
|
||||
*/
|
||||
public static JsonObject getArtistDetails(final String id) throws ParsingException {
|
||||
try {
|
||||
return JsonParser.object().from(NewPipe.getDownloader().postWithContentTypeJson(
|
||||
BASE_API_URL + "/mobile/22/band_details",
|
||||
Collections.emptyMap(),
|
||||
JsonWriter.string()
|
||||
.object()
|
||||
.value("band_id", id)
|
||||
.end()
|
||||
.done()
|
||||
.getBytes(StandardCharsets.UTF_8)).responseBody());
|
||||
} catch (final IOException | ReCaptchaException | JsonParserException e) {
|
||||
throw new ParsingException("Could not download band details", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an image url from an image ID.
|
||||
*
|
||||
* <p>
|
||||
* The image ID {@code 10} was chosen because it provides images wide up to 1200px (when
|
||||
* the original image width is more than or equal this resolution).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Other integer values are possible as well (e.g. 0 is a very large resolution, possibly the
|
||||
* original); see {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS} for more details about image
|
||||
* resolution IDs.
|
||||
* </p>
|
||||
*
|
||||
* @param id the image ID
|
||||
* @param isAlbum whether the image is the cover of an album or a track
|
||||
* @return a URL of the image with this ID with a width up to 1200px
|
||||
*/
|
||||
@Nonnull
|
||||
public static String getImageUrl(final long id, final boolean isAlbum) {
|
||||
return IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_10.jpg";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the given URL looks like it comes from a bandcamp custom domain
|
||||
* or a <code>*.bandcamp.com</code> subdomain
|
||||
*/
|
||||
public static boolean isArtistDomain(final String url) throws ParsingException {
|
||||
|
||||
// Accept all bandcamp.com URLs
|
||||
if (url.toLowerCase().matches("https?://.+\\.bandcamp\\.com(/.*)?")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reject non-artist bandcamp.com URLs
|
||||
if (url.toLowerCase().matches("https?://bandcamp\\.com(/.*)?")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Test other URLs for whether they contain a footer that links to bandcamp
|
||||
return Jsoup.parse(
|
||||
NewPipe.getDownloader()
|
||||
.get(Utils.replaceHttpWithHttps(url))
|
||||
.responseBody()
|
||||
)
|
||||
.getElementsByClass("cart-wrapper")
|
||||
.get(0)
|
||||
.getElementsByTag("a")
|
||||
.get(0)
|
||||
.attr("href")
|
||||
.equals("https://bandcamp.com/cart");
|
||||
} catch (final NullPointerException | IndexOutOfBoundsException e) {
|
||||
return false;
|
||||
} catch (final IOException | ReCaptchaException e) {
|
||||
throw new ParsingException("Could not determine whether URL is custom domain "
|
||||
+ "(not available? network error?)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the URL points to a radio kiosk.
|
||||
* @param url the URL to check
|
||||
* @return true if the URL matches {@code https://bandcamp.com/?show=SHOW_ID}
|
||||
*/
|
||||
public static boolean isRadioUrl(final String url) {
|
||||
return url.toLowerCase().matches("https?://bandcamp\\.com/\\?show=\\d+");
|
||||
}
|
||||
|
||||
public static DateWrapper parseDate(final String textDate) throws ParsingException {
|
||||
try {
|
||||
final ZonedDateTime zonedDateTime = ZonedDateTime.parse(textDate,
|
||||
DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH));
|
||||
return new DateWrapper(zonedDateTime.toOffsetDateTime(), false);
|
||||
} catch (final DateTimeException e) {
|
||||
throw new ParsingException("Could not parse date '" + textDate + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of images from a search result {@link Element}.
|
||||
*
|
||||
* <p>
|
||||
* This method will call {@link #getImagesFromImageUrl(String)} using the first non null and
|
||||
* non empty image URL found from the {@code src} attribute of {@code img} HTML elements, or an
|
||||
* empty string if no valid image URL was found.
|
||||
* </p>
|
||||
*
|
||||
* @param searchResult a search result {@link Element}
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where no valid image URL was found
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromSearchResult(@Nonnull final Element searchResult) {
|
||||
return getImagesFromImageUrl(searchResult.getElementsByClass("art")
|
||||
.stream()
|
||||
.flatMap(element -> element.getElementsByTag("img").stream())
|
||||
.map(element -> element.attr("src"))
|
||||
.filter(imageUrl -> !isNullOrEmpty(imageUrl))
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images which have resolutions preserving aspect ratio from an image URL.
|
||||
*
|
||||
* <p>
|
||||
* This method will remove the image ID and its extension from the end of the URL and then call
|
||||
* {@link #getImagesFromImageBaseUrl(String)}.
|
||||
* </p>
|
||||
*
|
||||
* @param imageUrl the full URL of an image provided by Bandcamp, such as in its HTML code
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where the image URL has been not extracted (and so is null or empty)
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromImageUrl(@Nullable final String imageUrl) {
|
||||
if (isNullOrEmpty(imageUrl)) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return getImagesFromImageBaseUrl(
|
||||
imageUrl.replaceFirst(IMAGE_URL_APPENDIX_AND_EXTENSION_REGEX, "_"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images which have resolutions preserving aspect ratio from an image ID.
|
||||
*
|
||||
* <p>
|
||||
* This method will call {@link #getImagesFromImageBaseUrl(String)}.
|
||||
* </p>
|
||||
*
|
||||
* @param id the id of an image provided by Bandcamp
|
||||
* @param isAlbum whether the image is the cover of an album
|
||||
* @return an unmodifiable list of {@link Image}s, which is never null but can be empty, in the
|
||||
* case where the image ID has been not extracted (and so equal to 0)
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Image> getImagesFromImageId(final long id, final boolean isAlbum) {
|
||||
if (id == 0) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return getImagesFromImageBaseUrl(IMAGES_DOMAIN_AND_PATH + (isAlbum ? 'a' : "") + id + "_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all images resolutions preserving aspect ratio from a base image URL.
|
||||
*
|
||||
* <p>
|
||||
* Base image URLs are images containing the image path, a {@code a} letter if it comes from an
|
||||
* album, its ID and an underscore.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Images resolutions returned are the ones of {@link #IMAGE_URL_SUFFIXES_AND_RESOLUTIONS}.
|
||||
* </p>
|
||||
*
|
||||
* @param baseUrl the base URL of the image
|
||||
* @return an unmodifiable and non-empty list of {@link Image}s
|
||||
*/
|
||||
@Nonnull
|
||||
private static List<Image> getImagesFromImageBaseUrl(@Nonnull final String baseUrl) {
|
||||
return IMAGE_URL_SUFFIXES_AND_RESOLUTIONS.stream()
|
||||
.map(imageSuffix -> new Image(baseUrl + imageSuffix.getSuffix(),
|
||||
imageSuffix.getHeight(), imageSuffix.getWidth(),
|
||||
imageSuffix.getResolutionLevel()))
|
||||
.collect(Collectors.toUnmodifiableList());
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemsCollector;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
public class BandcampFeaturedExtractor extends KioskExtractor<PlaylistInfoItem> {
|
||||
|
||||
public static final String KIOSK_FEATURED = "Featured";
|
||||
public static final String FEATURED_API_URL = BASE_API_URL + "/mobile/24/bootstrap_data";
|
||||
public static final String MORE_FEATURED_API_URL
|
||||
= BASE_API_URL + "/mobile/24/feed_older_logged_out";
|
||||
|
||||
private JsonObject json;
|
||||
|
||||
public BandcampFeaturedExtractor(final StreamingService streamingService,
|
||||
final ListLinkHandler listLinkHandler,
|
||||
final String kioskId) {
|
||||
super(streamingService, listLinkHandler, kioskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
try {
|
||||
json = JsonParser.object().from(getDownloader().postWithContentTypeJson(
|
||||
FEATURED_API_URL,
|
||||
Collections.emptyMap(),
|
||||
"{\"platform\":\"\",\"version\":0}".getBytes(StandardCharsets.UTF_8))
|
||||
.responseBody());
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse Bandcamp featured API response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return KIOSK_FEATURED;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<PlaylistInfoItem> getInitialPage()
|
||||
throws IOException, ExtractionException {
|
||||
final JsonArray featuredStories = json.getObject("feed_content")
|
||||
.getObject("stories")
|
||||
.getArray("featured");
|
||||
|
||||
return extractItems(featuredStories);
|
||||
}
|
||||
|
||||
private InfoItemsPage<PlaylistInfoItem> extractItems(final JsonArray featuredStories) {
|
||||
final PlaylistInfoItemsCollector c = new PlaylistInfoItemsCollector(getServiceId());
|
||||
|
||||
for (int i = 0; i < featuredStories.size(); i++) {
|
||||
final JsonObject featuredStory = featuredStories.getObject(i);
|
||||
|
||||
if (featuredStory.isNull("album_title")) {
|
||||
// Is not an album, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
c.commit(new BandcampPlaylistInfoItemFeaturedExtractor(featuredStory));
|
||||
}
|
||||
|
||||
final JsonObject lastFeaturedStory = featuredStories.getObject(featuredStories.size() - 1);
|
||||
return new InfoItemsPage<>(c, getNextPageFrom(lastFeaturedStory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Next Page can be generated from metadata of last featured story
|
||||
*/
|
||||
private Page getNextPageFrom(final JsonObject lastFeaturedStory) {
|
||||
final long lastStoryDate = lastFeaturedStory.getLong("story_date");
|
||||
final long lastStoryId = lastFeaturedStory.getLong("ntid");
|
||||
final String lastStoryType = lastFeaturedStory.getString("story_type");
|
||||
return new Page(
|
||||
MORE_FEATURED_API_URL + "?story_groups=featured"
|
||||
+ ':' + lastStoryDate + ':' + lastStoryType + ':' + lastStoryId
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<PlaylistInfoItem> getPage(final Page page)
|
||||
throws IOException, ExtractionException {
|
||||
|
||||
final JsonObject response;
|
||||
try {
|
||||
response = JsonParser.object().from(
|
||||
getDownloader().get(page.getUrl()).responseBody()
|
||||
);
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Could not parse Bandcamp featured API response", e);
|
||||
}
|
||||
|
||||
return extractItems(response.getObject("stories").getArray("featured"));
|
||||
}
|
||||
}
|
@ -1,179 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampStreamExtractor.getAlbumInfoJson;
|
||||
import static org.schabi.newpipe.extractor.utils.JsonUtils.getJsonData;
|
||||
import static org.schabi.newpipe.extractor.utils.Utils.HTTPS;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
|
||||
import org.schabi.newpipe.extractor.services.bandcamp.extractors.streaminfoitem.BandcampPlaylistStreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class BandcampPlaylistExtractor extends PlaylistExtractor {
|
||||
|
||||
/**
|
||||
* An arbitrarily chosen number above which cover arts won't be fetched individually for each
|
||||
* track; instead, it will be assumed that every track has the same cover art as the album,
|
||||
* which is not always the case.
|
||||
*/
|
||||
private static final int MAXIMUM_INDIVIDUAL_COVER_ARTS = 10;
|
||||
|
||||
private Document document;
|
||||
private JsonObject albumJson;
|
||||
private JsonArray trackInfo;
|
||||
private String name;
|
||||
|
||||
public BandcampPlaylistExtractor(final StreamingService service,
|
||||
final ListLinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
final String html = downloader.get(getLinkHandler().getUrl()).responseBody();
|
||||
document = Jsoup.parse(html);
|
||||
albumJson = getAlbumInfoJson(html);
|
||||
trackInfo = albumJson.getArray("trackinfo");
|
||||
|
||||
try {
|
||||
name = getJsonData(html, "data-embed").getString("album_title");
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ParsingException("Faulty JSON; page likely does not contain album data", e);
|
||||
} catch (final ArrayIndexOutOfBoundsException e) {
|
||||
throw new ParsingException("JSON does not exist", e);
|
||||
}
|
||||
|
||||
if (trackInfo.isEmpty()) {
|
||||
// Albums without trackInfo need to be purchased before they can be played
|
||||
throw new PaidContentException("Album needs to be purchased");
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
if (albumJson.isNull("art_id")) {
|
||||
return List.of();
|
||||
} else {
|
||||
return getImagesFromImageId(albumJson.getLong("art_id"), true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() throws ParsingException {
|
||||
final String[] parts = getUrl().split("/");
|
||||
// https: (/) (/) * .bandcamp.com (/) and leave out the rest
|
||||
return HTTPS + parts[2] + "/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() {
|
||||
return albumJson.getString("artist");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return getImagesFromImageUrl(document.getElementsByClass("band-photo")
|
||||
.stream()
|
||||
.map(element -> element.attr("src"))
|
||||
.findFirst()
|
||||
.orElse(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return trackInfo.size();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Description getDescription() throws ParsingException {
|
||||
final Element tInfo = document.getElementById("trackInfo");
|
||||
if (tInfo == null) {
|
||||
throw new ParsingException("Could not find trackInfo in document");
|
||||
}
|
||||
final Elements about = tInfo.getElementsByClass("tralbum-about");
|
||||
final Elements credits = tInfo.getElementsByClass("tralbum-credits");
|
||||
final Element license = document.getElementById("license");
|
||||
if (about.isEmpty() && credits.isEmpty() && license == null) {
|
||||
return Description.EMPTY_DESCRIPTION;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
if (!about.isEmpty()) {
|
||||
sb.append(Objects.requireNonNull(about.first()).html());
|
||||
}
|
||||
if (!credits.isEmpty()) {
|
||||
sb.append(Objects.requireNonNull(credits.first()).html());
|
||||
}
|
||||
if (license != null) {
|
||||
sb.append(license.html());
|
||||
}
|
||||
return new Description(sb.toString(), Description.HTML);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ExtractionException {
|
||||
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
for (int i = 0; i < trackInfo.size(); i++) {
|
||||
final JsonObject track = trackInfo.getObject(i);
|
||||
|
||||
if (trackInfo.size() < MAXIMUM_INDIVIDUAL_COVER_ARTS) {
|
||||
// Load cover art of every track individually
|
||||
collector.commit(new BandcampPlaylistStreamInfoItemExtractor(
|
||||
track, getUploaderUrl(), getService()));
|
||||
} else {
|
||||
// Pretend every track has the same cover art as the album
|
||||
collector.commit(new BandcampPlaylistStreamInfoItemExtractor(
|
||||
track, getUploaderUrl(), getThumbnails()));
|
||||
}
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return name;
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromSearchResult;
|
||||
|
||||
public class BandcampPlaylistInfoItemExtractor implements PlaylistInfoItemExtractor {
|
||||
private final Element searchResult;
|
||||
private final Element resultInfo;
|
||||
|
||||
public BandcampPlaylistInfoItemExtractor(@Nonnull final Element searchResult) {
|
||||
this.searchResult = searchResult;
|
||||
resultInfo = searchResult.getElementsByClass("result-info").first();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() {
|
||||
return resultInfo.getElementsByClass("subhead").text()
|
||||
.split(" by")[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
final String length = resultInfo.getElementsByClass("length").text();
|
||||
return Integer.parseInt(length.split(" track")[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return resultInfo.getElementsByClass("heading").text();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return resultInfo.getElementsByClass("itemurl").text();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() {
|
||||
return getImagesFromSearchResult(searchResult);
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.utils.Utils;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
public class BandcampPlaylistInfoItemFeaturedExtractor implements PlaylistInfoItemExtractor {
|
||||
|
||||
private final JsonObject featuredStory;
|
||||
|
||||
public BandcampPlaylistInfoItemFeaturedExtractor(final JsonObject featuredStory) {
|
||||
this.featuredStory = featuredStory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() {
|
||||
return featuredStory.getString("band_name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getStreamCount() {
|
||||
return featuredStory.getInt("num_streamable_tracks");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return featuredStory.getString("album_title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return Utils.replaceHttpWithHttps(featuredStory.getString("item_url"));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() {
|
||||
return featuredStory.has("art_id")
|
||||
? getImagesFromImageId(featuredStory.getLong("art_id"), true)
|
||||
: getImagesFromImageId(featuredStory.getLong("item_art_id"), true);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
import org.schabi.newpipe.extractor.Page;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.kiosk.KioskExtractor;
|
||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemsCollector;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
|
||||
public class BandcampRadioExtractor extends KioskExtractor<StreamInfoItem> {
|
||||
|
||||
public static final String KIOSK_RADIO = "Radio";
|
||||
public static final String RADIO_API_URL = BASE_API_URL + "/bcweekly/3/list";
|
||||
|
||||
private JsonObject json = null;
|
||||
|
||||
public BandcampRadioExtractor(final StreamingService streamingService,
|
||||
final ListLinkHandler linkHandler,
|
||||
final String kioskId) {
|
||||
super(streamingService, linkHandler, kioskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
try {
|
||||
json = JsonParser.object().from(
|
||||
getDownloader().get(RADIO_API_URL).responseBody());
|
||||
} catch (final JsonParserException e) {
|
||||
throw new ExtractionException("Could not parse Bandcamp Radio API response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return KIOSK_RADIO;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getInitialPage() {
|
||||
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
|
||||
|
||||
final JsonArray radioShows = json.getArray("results");
|
||||
|
||||
for (int i = 0; i < radioShows.size(); i++) {
|
||||
final JsonObject radioShow = radioShows.getObject(i);
|
||||
collector.commit(new BandcampRadioInfoItemExtractor(radioShow));
|
||||
}
|
||||
|
||||
return new InfoItemsPage<>(collector, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemsPage<StreamInfoItem> getPage(final Page page) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
// Created by Fynn Godau 2019, licensed GNU GPL version 3 or later
|
||||
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.localization.DateWrapper;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfoItemExtractor;
|
||||
import org.schabi.newpipe.extractor.stream.StreamType;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.parseDate;
|
||||
|
||||
public class BandcampRadioInfoItemExtractor implements StreamInfoItemExtractor {
|
||||
|
||||
private final JsonObject show;
|
||||
|
||||
public BandcampRadioInfoItemExtractor(final JsonObject radioShow) {
|
||||
show = radioShow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
/* Duration is only present in the more detailed information that has to be queried
|
||||
separately. Therefore, over 300 queries would be needed every time the kiosk is opened if we
|
||||
were to display the real value. */
|
||||
//return query(show.getInt("id")).getLong("audio_duration");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getShortDescription() {
|
||||
return show.getString("desc");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getTextualUploadDate() {
|
||||
return show.getString("date");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public DateWrapper getUploadDate() throws ParsingException {
|
||||
return parseDate(getTextualUploadDate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
return show.getString("subtitle");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUrl() {
|
||||
return BASE_URL + "/?show=" + show.getInt("id");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() {
|
||||
return getImagesFromImageId(show.getLong("image_id"), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamType getStreamType() {
|
||||
return StreamType.AUDIO_STREAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getViewCount() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderName() {
|
||||
// The "title" field contains the title of the series, e.g. "Bandcamp Weekly".
|
||||
return show.getString("title");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploaderUrl() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUploaderVerified() throws ParsingException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAd() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,194 +0,0 @@
|
||||
package org.schabi.newpipe.extractor.services.bandcamp.extractors;
|
||||
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_API_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.BASE_URL;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImageUrl;
|
||||
import static org.schabi.newpipe.extractor.services.bandcamp.extractors.BandcampExtractorHelper.getImagesFromImageId;
|
||||
|
||||
import com.grack.nanojson.JsonArray;
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.Image;
|
||||
import org.schabi.newpipe.extractor.Image.ResolutionLevel;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.NewPipe;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.downloader.Downloader;
|
||||
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ParsingException;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
import org.schabi.newpipe.extractor.linkhandler.LinkHandler;
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItemsCollector;
|
||||
import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||
import org.schabi.newpipe.extractor.stream.Description;
|
||||
import org.schabi.newpipe.extractor.stream.StreamSegment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class BandcampRadioStreamExtractor extends BandcampStreamExtractor {
|
||||
|
||||
private static final String OPUS_LO = "opus-lo";
|
||||
private static final String MP3_128 = "mp3-128";
|
||||
private JsonObject showInfo;
|
||||
|
||||
public BandcampRadioStreamExtractor(final StreamingService service,
|
||||
final LinkHandler linkHandler) {
|
||||
super(service, linkHandler);
|
||||
}
|
||||
|
||||
static JsonObject query(final int id) throws ParsingException {
|
||||
try {
|
||||
return JsonParser.object().from(NewPipe.getDownloader()
|
||||
.get(BASE_API_URL + "/bcweekly/1/get?id=" + id).responseBody());
|
||||
} catch (final IOException | ReCaptchaException | JsonParserException e) {
|
||||
throw new ParsingException("could not get show data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchPage(@Nonnull final Downloader downloader)
|
||||
throws IOException, ExtractionException {
|
||||
showInfo = query(Integer.parseInt(getId()));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getName() throws ParsingException {
|
||||
/* Select "subtitle" and not "audio_title", as the latter would cause a lot of
|
||||
* items to show the same title, e.g. "Bandcamp Weekly".
|
||||
*/
|
||||
return showInfo.getString("subtitle");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderUrl() throws ContentNotSupportedException {
|
||||
throw new ContentNotSupportedException("Fan pages are not supported");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUrl() throws ParsingException {
|
||||
return getLinkHandler().getUrl();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getUploaderName() throws ParsingException {
|
||||
return Jsoup.parse(showInfo.getString("image_caption")).getElementsByTag("a").stream()
|
||||
.map(Element::text)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ParsingException("Could not get uploader name"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getTextualUploadDate() {
|
||||
return showInfo.getString("published_date");
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getThumbnails() throws ParsingException {
|
||||
return getImagesFromImageId(showInfo.getLong("show_image_id"), false);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<Image> getUploaderAvatars() {
|
||||
return Collections.singletonList(
|
||||
new Image(BASE_URL + "/img/buttons/bandcamp-button-circle-whitecolor-512.png",
|
||||
512, 512, ResolutionLevel.MEDIUM));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Description getDescription() {
|
||||
return new Description(showInfo.getString("desc"), Description.PLAIN_TEXT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return showInfo.getLong("audio_duration");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AudioStream> getAudioStreams() {
|
||||
final List<AudioStream> audioStreams = new ArrayList<>();
|
||||
final JsonObject streams = showInfo.getObject("audio_stream");
|
||||
|
||||
if (streams.has(MP3_128)) {
|
||||
audioStreams.add(new AudioStream.Builder()
|
||||
.setId(MP3_128)
|
||||
.setContent(streams.getString(MP3_128), true)
|
||||
.setMediaFormat(MediaFormat.MP3)
|
||||
.setAverageBitrate(128)
|
||||
.build());
|
||||
}
|
||||
|
||||
if (streams.has(OPUS_LO)) {
|
||||
audioStreams.add(new AudioStream.Builder()
|
||||
.setId(OPUS_LO)
|
||||
.setContent(streams.getString(OPUS_LO), true)
|
||||
.setMediaFormat(MediaFormat.OPUS)
|
||||
.setAverageBitrate(100).build());
|
||||
}
|
||||
|
||||
return audioStreams;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<StreamSegment> getStreamSegments() throws ParsingException {
|
||||
final JsonArray tracks = showInfo.getArray("tracks");
|
||||
final List<StreamSegment> segments = new ArrayList<>(tracks.size());
|
||||
for (final Object t : tracks) {
|
||||
final JsonObject track = (JsonObject) t;
|
||||
final StreamSegment segment = new StreamSegment(
|
||||
track.getString("title"), track.getInt("timecode"));
|
||||
// "track art" is the track's album cover
|
||||
segment.setPreviewUrl(getImageUrl(track.getLong("track_art_id"), true));
|
||||
segment.setChannelName(track.getString("artist"));
|
||||
segments.add(segment);
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getLicence() {
|
||||
// Contrary to other Bandcamp streams, radio streams don't have a license
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getCategory() {
|
||||
// Contrary to other Bandcamp streams, radio streams don't have categories
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public List<String> getTags() {
|
||||
// Contrary to other Bandcamp streams, radio streams don't have tags
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaylistInfoItemsCollector getRelatedItems() {
|
||||
// Contrary to other Bandcamp streams, radio streams don't have related items
|
||||
return null;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user