mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
With data-client added as library module (#3656)
* With data-client added as library module * Fix build
This commit is contained in:
parent
9ee04f3df4
commit
32ee0b4f9a
258 changed files with 34820 additions and 2 deletions
|
|
@ -17,6 +17,7 @@ if(isRunningOnTravisAndIsNotPRBuild) {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
|
implementation project(':wikimedia-data-client')
|
||||||
// Utils
|
// Utils
|
||||||
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
||||||
implementation 'in.yuvi:http.fluent:1.3'
|
implementation 'in.yuvi:http.fluent:1.3'
|
||||||
|
|
@ -31,7 +32,6 @@ dependencies {
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
|
||||||
implementation 'com.facebook.fresco:fresco:1.13.0'
|
implementation 'com.facebook.fresco:fresco:1.13.0'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||||
implementation 'com.github.maskaravivek:wikimedia-android-data-client:v0.0.30'
|
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
|
|
|
||||||
59
data-client/.gitignore
vendored
Normal file
59
data-client/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.ap_
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following line if you do not want to check your keystore files in.
|
||||||
|
#*.jks
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
23
data-client/.travis.yml
Normal file
23
data-client/.travis.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
language: android
|
||||||
|
jdk: oraclejdk8
|
||||||
|
android:
|
||||||
|
components:
|
||||||
|
- platform-tools
|
||||||
|
- tools
|
||||||
|
|
||||||
|
# The BuildTools version used by your project
|
||||||
|
- build-tools-28.0.3
|
||||||
|
|
||||||
|
# The SDK version used to compile your project
|
||||||
|
- android-28
|
||||||
|
|
||||||
|
# Additional components
|
||||||
|
- extra-android-m2repository
|
||||||
|
|
||||||
|
licenses:
|
||||||
|
- android-sdk-preview-license-.+
|
||||||
|
- android-sdk-license-.+
|
||||||
|
before_script:
|
||||||
|
- chmod +x gradlew
|
||||||
|
|
||||||
|
script: "./gradlew test"
|
||||||
201
data-client/LICENSE
Normal file
201
data-client/LICENSE
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
101
data-client/README.md
Normal file
101
data-client/README.md
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# Wikimedia Android data client
|
||||||
|
An Android library for communicating with Wikimedia projects, with Rx bindings and other utilities.
|
||||||
|
|
||||||
|
## Motivation and philosophy
|
||||||
|
|
||||||
|
Here are the purposes for creating this library:
|
||||||
|
|
||||||
|
* Encapsulate the various model structures that are returned by MediaWiki APIs,
|
||||||
|
as well as by REST APIs provided by Wikimedia services.
|
||||||
|
|
||||||
|
* Provide high-level bindings for Retrofit and RxJava for executing calls to MediaWiki APIs to
|
||||||
|
further simplify client integration, while also allowing customization and extension.
|
||||||
|
|
||||||
|
* Provide numerous common utility methods, so that they don't need to be duplicated.
|
||||||
|
|
||||||
|
## Integration with your app
|
||||||
|
|
||||||
|
Add the dependency to your Gradle file as usual:
|
||||||
|
|
||||||
|
```
|
||||||
|
implementation "com.dmitrybrant:wikimedia-android-data-client:0.0.18"
|
||||||
|
```
|
||||||
|
|
||||||
|
The only nontrivial point of integration with the library is the `AppAdapter` class: You
|
||||||
|
need to create a class that inherits from `AppAdapter` and implement its methods. The
|
||||||
|
methods are mostly self-explanatory, and deal with user account management, cookie storage,
|
||||||
|
and a few other customizations.
|
||||||
|
|
||||||
|
Once you create this class (suppose it's called `MyAppAdapter`), you should pass it into
|
||||||
|
the `AppAdapter` singleton when your app starts:
|
||||||
|
|
||||||
|
```
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
...
|
||||||
|
AppAdapter.set(new MyAppAdapter());
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Making calls to APIs
|
||||||
|
|
||||||
|
Notice that there is an interface called `Service` that contains a number of API definitions
|
||||||
|
for talking with a MediaWiki server. To use any of the functions in the interface, you should
|
||||||
|
use the `ServiceFactory` class. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
WikiSite wiki = new WikiSite("en.wikipedia.org");
|
||||||
|
|
||||||
|
Observable observable = ServiceFactory.get(wiki).fullTextSearch("foo");
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Notice that most of the API calls return an `Observable` response which you can
|
||||||
|
feed into an Rx subscription.
|
||||||
|
|
||||||
|
Note: the `ServiceFactory` class contains automatic caching logic, so that multiple calls to
|
||||||
|
`get()` the service for the same `WikiSite` will be very efficient.
|
||||||
|
|
||||||
|
## Custom API calls
|
||||||
|
|
||||||
|
The `ServiceFactory` class also allows you to provide a service interface with custom
|
||||||
|
API functions. Suppose you create your own service interface that looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
public interface MyInterface {
|
||||||
|
|
||||||
|
@GET("action=myawesomeaction")
|
||||||
|
Observable<MyAwesomeResponse> myAwesomeApiCall(@Query("parameter1") parameter);
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then use it with `ServiceFactory` this way:
|
||||||
|
|
||||||
|
```
|
||||||
|
WikiSite wiki = new WikiSite("my.awesome.wiki");
|
||||||
|
|
||||||
|
Observable observable = ServiceFactory.get(wiki, "https://my.awesome.wiki/", MyInterface.class)
|
||||||
|
.myAwesomeApiCall("foo");
|
||||||
|
```
|
||||||
|
|
||||||
|
## Utility methods
|
||||||
|
|
||||||
|
The library contains a potpourri of utility methods found under the `util` package. Feel free
|
||||||
|
to browse through them and use them as necessary.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright 2019 Wikimedia Foundation
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
147
data-client/build.gradle
Normal file
147
data-client/build.gradle
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.31'
|
||||||
|
repositories {
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath "com.android.tools.build:gradle:3.4.1"
|
||||||
|
classpath "com.github.dcendents:android-maven-gradle-plugin:2.1"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "com.jfrog.bintray" version "1.7.3"
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
|
||||||
|
apply plugin: 'com.github.dcendents.android-maven'
|
||||||
|
apply plugin: 'com.jfrog.bintray'
|
||||||
|
}
|
||||||
|
|
||||||
|
version = "${VERSION_NAME}"
|
||||||
|
group = "${GROUP_ID}"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 19
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "${VERSION_NAME}"
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = '1.8'
|
||||||
|
targetCompatibility = '1.8'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
String retrofitVersion = '2.4.0'
|
||||||
|
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
implementation "androidx.core:core:1.0.2"
|
||||||
|
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
|
||||||
|
implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
|
||||||
|
implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
|
||||||
|
implementation "io.reactivex.rxjava2:rxjava:2.2.3"
|
||||||
|
implementation "io.reactivex.rxjava2:rxandroid:2.1.0"
|
||||||
|
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
|
testImplementation 'org.robolectric:robolectric:3.8'
|
||||||
|
testImplementation "com.squareup.okhttp3:mockwebserver:3.12.1"
|
||||||
|
testImplementation "commons-io:commons-io:2.6"
|
||||||
|
}
|
||||||
|
|
||||||
|
task sourcesJar(type: Jar) {
|
||||||
|
from android.sourceSets.main.java.srcDirs
|
||||||
|
classifier = 'sources'
|
||||||
|
}
|
||||||
|
artifacts {
|
||||||
|
archives sourcesJar
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties properties = new Properties()
|
||||||
|
if ( project.rootProject.file('local.properties').isFile() ) {
|
||||||
|
properties.load(project.rootProject.file('local.properties').newDataInputStream())
|
||||||
|
}
|
||||||
|
|
||||||
|
bintray {
|
||||||
|
user = properties.getProperty("bintray.user")
|
||||||
|
key = properties.getProperty("bintray.apikey")
|
||||||
|
println 'Bintray user: ' + user
|
||||||
|
configurations = ['archives']
|
||||||
|
pkg {
|
||||||
|
repo = 'maven'
|
||||||
|
name = "${ARTIFACT_ID}"
|
||||||
|
vcsUrl = 'https://github.com/wikimedia/wikimedia-android-data-client.git'
|
||||||
|
licenses = ['Apache-2.0']
|
||||||
|
version {
|
||||||
|
name = "${VERSION_NAME}"
|
||||||
|
}
|
||||||
|
publish = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
install {
|
||||||
|
repositories.mavenInstaller {
|
||||||
|
// This generates POM.xml with proper parameters
|
||||||
|
pom {
|
||||||
|
project {
|
||||||
|
packaging 'aar'
|
||||||
|
|
||||||
|
name "${ARTIFACT_ID}"
|
||||||
|
artifactId "${ARTIFACT_ID}"
|
||||||
|
description 'Android library for accessing the Wikimedia APIs.'
|
||||||
|
url 'https://github.com/wikimedia/wikimedia-android-data-client'
|
||||||
|
inceptionYear '2019'
|
||||||
|
|
||||||
|
licenses {
|
||||||
|
license {
|
||||||
|
name 'The Apache Software License, Version 2.0'
|
||||||
|
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id 'dmitrybrant'
|
||||||
|
name 'Dmitry Brant'
|
||||||
|
email 'me@dmitrybrant.com'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
25
data-client/gradle.properties
Normal file
25
data-client/gradle.properties
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||||
|
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
VERSION_NAME=0.0.27
|
||||||
|
GROUP_ID=com.dmitrybrant
|
||||||
|
ARTIFACT_ID=wikimedia-android-data-client
|
||||||
|
GRADLE_BINTRAY_PLUGIN_VERSION=1.7.3
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
BIN
data-client/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
data-client/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
data-client/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
data-client/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#Fri Jun 07 09:14:55 EDT 2019
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||||
160
data-client/gradlew
vendored
Executable file
160
data-client/gradlew
vendored
Executable file
|
|
@ -0,0 +1,160 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn ( ) {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die ( ) {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||||
|
function splitJvmOpts() {
|
||||||
|
JVM_OPTS=("$@")
|
||||||
|
}
|
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||||
90
data-client/gradlew.bat
vendored
Normal file
90
data-client/gradlew.bat
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windowz variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
goto execute
|
||||||
|
|
||||||
|
:4NT_args
|
||||||
|
@rem Get arguments from the 4NT Shell from JP Software
|
||||||
|
set CMD_LINE_ARGS=%$
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
25
data-client/proguard-rules.pro
vendored
Normal file
25
data-client/proguard-rules.pro
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in F:\android-sdk-windows/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
101
data-client/scripts/generate_wiki_languages.py
Normal file
101
data-client/scripts/generate_wiki_languages.py
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import lxml
|
||||||
|
import lxml.builder as lb
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
QUERY_SITEMATRIX = 'https://www.mediawiki.org/w/api.php?action=sitematrix' \
|
||||||
|
'&format=json&formatversion=2&smtype=language&smstate=all'
|
||||||
|
|
||||||
|
QUERY_ALLUSERS = '/w/api.php?action=query&format=json&formatversion=2&list=allusers' \
|
||||||
|
'&aulimit=50&auactiveusers=1&auwitheditsonly=1'
|
||||||
|
|
||||||
|
lang_keys = []
|
||||||
|
lang_local_names = []
|
||||||
|
lang_eng_names = []
|
||||||
|
lang_rank = []
|
||||||
|
|
||||||
|
|
||||||
|
def add_lang(key, local_name, eng_name, rank):
|
||||||
|
rank_pos = 0
|
||||||
|
# Automatically keep the arrays sorted by rank
|
||||||
|
for index, item in enumerate(lang_rank):
|
||||||
|
rank_pos = index
|
||||||
|
if (rank > item):
|
||||||
|
break
|
||||||
|
lang_keys.insert(rank_pos, key)
|
||||||
|
lang_local_names.insert(rank_pos, local_name)
|
||||||
|
lang_eng_names.insert(rank_pos, eng_name)
|
||||||
|
lang_rank.insert(rank_pos, rank)
|
||||||
|
|
||||||
|
|
||||||
|
data = json.loads(requests.get(QUERY_SITEMATRIX).text)
|
||||||
|
|
||||||
|
for key, value in data[u"sitematrix"].items():
|
||||||
|
if type(value) is not dict:
|
||||||
|
continue
|
||||||
|
language_code = value[u"code"]
|
||||||
|
if language_code == 'got':
|
||||||
|
# 'got' is Gothic Runes, which lie outside the Basic Multilingual Plane
|
||||||
|
# Android segfaults on these. So let's ignore those.
|
||||||
|
continue
|
||||||
|
site_list = value[u"site"]
|
||||||
|
if type(site_list) is not list:
|
||||||
|
continue
|
||||||
|
wikipedia_url = ""
|
||||||
|
for site in site_list:
|
||||||
|
if "wikipedia.org" in site[u"url"] and u"closed" not in site:
|
||||||
|
wikipedia_url = site[u"url"]
|
||||||
|
if len(wikipedia_url) == 0:
|
||||||
|
continue
|
||||||
|
# TODO: If we want to remove languages with too few active users:
|
||||||
|
# allusers = json.loads(requests.get(wikipedia_url + QUERY_ALLUSERS).text)
|
||||||
|
# if len(allusers[u"query"][u"allusers"]) < 10:
|
||||||
|
# print ("Excluding " + language_code + " (too few active users).")
|
||||||
|
# continue
|
||||||
|
# Use the AQS API to get total pageviews for this language wiki in the last month:
|
||||||
|
date = datetime.today() - timedelta(days=31)
|
||||||
|
unique_device_response = json.loads(requests.get('https://wikimedia.org/api/rest_v1/metrics/unique-devices/' +
|
||||||
|
wikipedia_url.replace('https://', '') + '/all-sites/monthly/' +
|
||||||
|
date.strftime('%Y%m01') + '/' + date.strftime('%Y%m01')).text)
|
||||||
|
rank = 0
|
||||||
|
if u"items" in unique_device_response:
|
||||||
|
if len(unique_device_response[u"items"]) > 0:
|
||||||
|
rank = unique_device_response[u"items"][0][u"devices"]
|
||||||
|
print ("Rank for " + language_code + ": " + str(rank))
|
||||||
|
if language_code == 'zh':
|
||||||
|
add_lang(key='zh-hans', local_name=u'简体中文',
|
||||||
|
eng_name='Simplified Chinese', rank=rank)
|
||||||
|
add_lang(key='zh-hant', local_name=u'繁體中文',
|
||||||
|
eng_name='Traditional Chinese', rank=rank)
|
||||||
|
continue
|
||||||
|
if language_code == 'no': # T114042
|
||||||
|
language_code = 'nb'
|
||||||
|
add_lang(language_code, value[u"name"].replace("'", "\\'"), value[u"localname"].replace("'", "\\'"), rank)
|
||||||
|
|
||||||
|
|
||||||
|
add_lang(key='test', local_name='Test', eng_name='Test', rank=0)
|
||||||
|
add_lang(key='en-x-piglatin', local_name='Igpay Atinlay', eng_name='Pig Latin', rank=0)
|
||||||
|
|
||||||
|
# Generate the XML, for Android
|
||||||
|
NAMESPACE = 'http://schemas.android.com/tools'
|
||||||
|
TOOLS = '{%s}' % NAMESPACE
|
||||||
|
x = lb.ElementMaker(nsmap={'tools': NAMESPACE})
|
||||||
|
|
||||||
|
keys = [x.item(k) for k in lang_keys]
|
||||||
|
local_names = [x.item(k) for k in lang_local_names]
|
||||||
|
eng_names = [x.item(k) for k in lang_eng_names]
|
||||||
|
|
||||||
|
resources = x.resources(
|
||||||
|
getattr(x, 'string-array')(*keys, name='preference_language_keys'),
|
||||||
|
getattr(x, 'string-array')(*local_names, name='preference_language_local_names'),
|
||||||
|
getattr(x, 'string-array')(*eng_names, name='preference_language_canonical_names'))
|
||||||
|
resources.set(TOOLS + 'ignore', 'MissingTranslation')
|
||||||
|
|
||||||
|
with open('../src/main/res/values/languages_list.xml', 'wb') as f:
|
||||||
|
f.write(lxml.etree.tostring(resources, pretty_print=True,
|
||||||
|
xml_declaration=True, encoding='utf-8'))
|
||||||
183
data-client/scripts/make-templates.py
Normal file
183
data-client/scripts/make-templates.py
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import codecs
|
||||||
|
import requests
|
||||||
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
|
|
||||||
|
CHINESE_WIKI_LANG = "zh"
|
||||||
|
SIMPLIFIED_CHINESE_LANG = "zh-hans"
|
||||||
|
TRADITIONAL_CHINESE_LANG = "zh-hant"
|
||||||
|
|
||||||
|
# T114042
|
||||||
|
NORWEGIAN_BOKMAL_WIKI_LANG = "no"
|
||||||
|
NORWEGIAN_BOKMAL_LANG = "nb"
|
||||||
|
|
||||||
|
|
||||||
|
# Wikis that cause problems and hence we pretend
|
||||||
|
# do not exist.
|
||||||
|
# - "got" -> Gothic runes wiki. The name of got in got
|
||||||
|
# contains characters outside the Unicode BMP. Android
|
||||||
|
# hard crashes on these. Let's ignore these fellas
|
||||||
|
# for now.
|
||||||
|
# - "mo" -> Moldovan, which automatically redirects to Romanian (ro),
|
||||||
|
# which already exists in our list.
|
||||||
|
OSTRICH_WIKIS = [u"got", "mo"]
|
||||||
|
|
||||||
|
|
||||||
|
# Represents a single wiki, along with arbitrary properties of that wiki
|
||||||
|
# Simple data container object
|
||||||
|
class Wiki(object):
|
||||||
|
def __init__(self, lang):
|
||||||
|
self.lang = lang
|
||||||
|
self.props = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Represents a list of wikis plus their properties.
|
||||||
|
# Encapsulates rendering code as well
|
||||||
|
class WikiList(object):
|
||||||
|
def __init__(self, wikis):
|
||||||
|
self.wikis = wikis
|
||||||
|
self.template_env = Environment(loader=FileSystemLoader(
|
||||||
|
os.path.join(os.path.dirname(os.path.realpath(__file__)), u"templates")
|
||||||
|
))
|
||||||
|
|
||||||
|
def render(self, template, class_name, **kwargs):
|
||||||
|
data = {
|
||||||
|
u"class_name": class_name,
|
||||||
|
u"wikis": self.wikis
|
||||||
|
}
|
||||||
|
data.update(kwargs)
|
||||||
|
rendered = self.template_env.get_template(template).render(**data)
|
||||||
|
out = codecs.open(u"../src/main/java/org/wikipedia/staticdata/" + class_name + u".java", u"w", u"utf-8")
|
||||||
|
out.write(rendered)
|
||||||
|
out.close()
|
||||||
|
|
||||||
|
|
||||||
|
def build_wiki(lang, english_name, local_name):
|
||||||
|
wiki = Wiki(lang)
|
||||||
|
wiki.props["english_name"] = english_name
|
||||||
|
wiki.props["local_name"] = local_name
|
||||||
|
return wiki
|
||||||
|
|
||||||
|
|
||||||
|
def list_from_sitematrix():
|
||||||
|
QUERY_SITEMATRIX = 'https://www.mediawiki.org/w/api.php?action=sitematrix' \
|
||||||
|
'&format=json&formatversion=2&smtype=language&smstate=all'
|
||||||
|
|
||||||
|
print(u"Fetching languages...")
|
||||||
|
data = json.loads(requests.get(QUERY_SITEMATRIX).text)
|
||||||
|
wikis = []
|
||||||
|
|
||||||
|
for key, value in data[u"sitematrix"].items():
|
||||||
|
if type(value) is not dict:
|
||||||
|
continue
|
||||||
|
site_list = value[u"site"]
|
||||||
|
if type(site_list) is not list:
|
||||||
|
continue
|
||||||
|
wikipedia_url = ""
|
||||||
|
for site in site_list:
|
||||||
|
if "wikipedia.org" in site[u"url"] and u"closed" not in site:
|
||||||
|
wikipedia_url = site[u"url"]
|
||||||
|
if len(wikipedia_url) == 0:
|
||||||
|
continue
|
||||||
|
wikis.append(build_wiki(value[u"code"], value[u"localname"], value[u"name"]))
|
||||||
|
|
||||||
|
return wikis
|
||||||
|
|
||||||
|
|
||||||
|
# Remove unsupported wikis.
|
||||||
|
def filter_supported_wikis(wikis):
|
||||||
|
return [wiki for wiki in wikis if wiki.lang not in OSTRICH_WIKIS]
|
||||||
|
|
||||||
|
|
||||||
|
# Apply manual tweaks to the list of wikis before they're populated.
|
||||||
|
def preprocess_wikis(wikis):
|
||||||
|
# Add TestWiki.
|
||||||
|
wikis.append(build_wiki(lang="test", english_name="Test", local_name="Test"))
|
||||||
|
|
||||||
|
return wikis
|
||||||
|
|
||||||
|
|
||||||
|
# Apply manual tweaks to the list of wikis after they're populated.
|
||||||
|
def postprocess_wikis(wiki_list):
|
||||||
|
# Add Simplified and Traditional Chinese dialects.
|
||||||
|
chineseWiki = next((wiki for wiki in wiki_list.wikis if wiki.lang == CHINESE_WIKI_LANG), None)
|
||||||
|
chineseWikiIndex = wiki_list.wikis.index(chineseWiki)
|
||||||
|
|
||||||
|
simplifiedWiki = copy.deepcopy(chineseWiki)
|
||||||
|
simplifiedWiki.lang = SIMPLIFIED_CHINESE_LANG
|
||||||
|
simplifiedWiki.props["english_name"] = "Simplified Chinese"
|
||||||
|
simplifiedWiki.props["local_name"] = "简体中文"
|
||||||
|
wiki_list.wikis.insert(chineseWikiIndex + 1, simplifiedWiki)
|
||||||
|
|
||||||
|
traditionalWiki = copy.deepcopy(chineseWiki)
|
||||||
|
traditionalWiki.lang = TRADITIONAL_CHINESE_LANG
|
||||||
|
traditionalWiki.props["english_name"] = "Traditional Chinese"
|
||||||
|
traditionalWiki.props["local_name"] = "繁體中文"
|
||||||
|
wiki_list.wikis.insert(chineseWikiIndex + 2, traditionalWiki)
|
||||||
|
|
||||||
|
bokmalWiki = next((wiki for wiki in wiki_list.wikis if wiki.lang == NORWEGIAN_BOKMAL_WIKI_LANG), None)
|
||||||
|
bokmalWiki.lang = NORWEGIAN_BOKMAL_LANG
|
||||||
|
|
||||||
|
return wiki_list
|
||||||
|
|
||||||
|
|
||||||
|
# Populate the aliases for "Special:" and "File:" in all wikis
|
||||||
|
def populate_aliases(wikis):
|
||||||
|
for wiki in wikis.wikis:
|
||||||
|
print(u"Fetching Special Page and File alias for %s" % wiki.lang)
|
||||||
|
url = u"https://%s.wikipedia.org/w/api.php" % wiki.lang + \
|
||||||
|
u"?action=query&meta=siteinfo&format=json&siprop=namespaces"
|
||||||
|
data = json.loads(requests.get(url).text)
|
||||||
|
# according to https://www.mediawiki.org/wiki/Manual:Namespace
|
||||||
|
# -1 seems to be the ID for Special Pages
|
||||||
|
wiki.props[u"special_alias"] = data[u"query"][u"namespaces"][u"-1"][u"*"]
|
||||||
|
# 6 is the ID for File pages
|
||||||
|
wiki.props[u"file_alias"] = data[u"query"][u"namespaces"][u"6"][u"*"]
|
||||||
|
return wikis
|
||||||
|
|
||||||
|
|
||||||
|
# Populates data on names of main page in each wiki
|
||||||
|
def populate_main_pages(wikis):
|
||||||
|
for wiki in wikis.wikis:
|
||||||
|
print(u"Fetching Main Page for %s" % wiki.lang)
|
||||||
|
url = u"https://%s.wikipedia.org/w/api.php" % wiki.lang + \
|
||||||
|
u"?action=query&meta=siteinfo&format=json&siprop=general"
|
||||||
|
data = json.loads(requests.get(url).text)
|
||||||
|
wiki.props[u"main_page_name"] = data[u"query"][u"general"][u"mainpage"]
|
||||||
|
return wikis
|
||||||
|
|
||||||
|
|
||||||
|
# Returns a function that renders a particular template when passed
|
||||||
|
# a WikiList object
|
||||||
|
def render_template(template, filename, **kwargs):
|
||||||
|
def _actual_render(wikis):
|
||||||
|
wikis.render(template, filename, **kwargs)
|
||||||
|
return wikis
|
||||||
|
return _actual_render
|
||||||
|
|
||||||
|
|
||||||
|
# Kinda like reduce(), but special cases first function
|
||||||
|
def chain(*funcs):
|
||||||
|
res = funcs[0]()
|
||||||
|
for func in funcs[1:]:
|
||||||
|
res = func(res)
|
||||||
|
|
||||||
|
|
||||||
|
chain(
|
||||||
|
list_from_sitematrix,
|
||||||
|
filter_supported_wikis,
|
||||||
|
preprocess_wikis,
|
||||||
|
WikiList,
|
||||||
|
populate_aliases,
|
||||||
|
populate_main_pages,
|
||||||
|
postprocess_wikis,
|
||||||
|
render_template(u"basichash.java.jinja", u"SpecialAliasData", key=u"special_alias"),
|
||||||
|
render_template(u"basichash.java.jinja", u"FileAliasData", key=u"file_alias"),
|
||||||
|
render_template(u"basichash.java.jinja", u"MainPageNameData", key=u"main_page_name"),
|
||||||
|
)
|
||||||
36
data-client/scripts/templates/basichash.java.jinja
Normal file
36
data-client/scripts/templates/basichash.java.jinja
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
This file is auto-generated from a template (/scripts/templates).
|
||||||
|
If you need to modify it, make sure to modify the template, not this file.
|
||||||
|
*/
|
||||||
|
package org.wikipedia.staticdata;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public final class {{class_name}} {
|
||||||
|
@NonNull private static final Map<String, String> DATA_MAP = Collections.unmodifiableMap(newMap());
|
||||||
|
|
||||||
|
@NonNull public static String valueFor(String key) {
|
||||||
|
if (DATA_MAP.containsKey(key)) {
|
||||||
|
return DATA_MAP.get(key);
|
||||||
|
}
|
||||||
|
return DATA_MAP.get("en");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"checkstyle:methodlength", "SpellCheckingInspection"})
|
||||||
|
private static Map<String, String> newMap() {
|
||||||
|
final int size = {{wikis|length}};
|
||||||
|
Map<String, String> map = new HashMap<>(size);
|
||||||
|
|
||||||
|
{%- for wiki in wikis %}
|
||||||
|
map.put("{{wiki.lang}}", "{{wiki.props[key]}}");
|
||||||
|
{%- endfor %}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private {{class_name}}() { }
|
||||||
|
}
|
||||||
|
|
||||||
4
data-client/src/main/AndroidManifest.xml
Normal file
4
data-client/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.wikipedia.dataclient">
|
||||||
|
<application android:allowBackup="true" android:label="@string/app_name" android:supportsRtl="true">
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
38
data-client/src/main/java/org/wikipedia/AppAdapter.java
Normal file
38
data-client/src/main/java/org/wikipedia/AppAdapter.java
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.wikipedia;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.login.LoginResult;
|
||||||
|
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
public abstract class AppAdapter {
|
||||||
|
|
||||||
|
public abstract String getMediaWikiBaseUrl();
|
||||||
|
public abstract String getRestbaseUriFormat();
|
||||||
|
public abstract OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite);
|
||||||
|
public abstract int getDesiredLeadImageDp();
|
||||||
|
|
||||||
|
public abstract boolean isLoggedIn();
|
||||||
|
public abstract String getUserName();
|
||||||
|
public abstract String getPassword();
|
||||||
|
public abstract void updateAccount(@NonNull LoginResult result);
|
||||||
|
|
||||||
|
public abstract SharedPreferenceCookieManager getCookies();
|
||||||
|
public abstract void setCookies(@NonNull SharedPreferenceCookieManager cookies);
|
||||||
|
|
||||||
|
public abstract boolean logErrorsInsteadOfCrashing();
|
||||||
|
|
||||||
|
private static AppAdapter INSTANCE;
|
||||||
|
public static void set(AppAdapter instance) {
|
||||||
|
INSTANCE = instance;
|
||||||
|
}
|
||||||
|
public static AppAdapter get() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
throw new RuntimeException("Please provide an instance of AppAdapter when using this library.");
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
data-client/src/main/java/org/wikipedia/captcha/Captcha.java
Normal file
19
data-client/src/main/java/org/wikipedia/captcha/Captcha.java
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package org.wikipedia.captcha;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwResponse;
|
||||||
|
|
||||||
|
public class Captcha extends MwResponse {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private FancyCaptchaReload fancycaptchareload;
|
||||||
|
@NonNull String captchaId() {
|
||||||
|
return fancycaptchareload.index();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class FancyCaptchaReload {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String index;
|
||||||
|
@NonNull String index() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.wikipedia.captcha;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.edit.EditResult;
|
||||||
|
|
||||||
|
// Handles only Image Captchas
|
||||||
|
public class CaptchaResult extends EditResult {
|
||||||
|
private final String captchaId;
|
||||||
|
|
||||||
|
public CaptchaResult(String captchaId) {
|
||||||
|
super("Failure");
|
||||||
|
this.captchaId = captchaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CaptchaResult(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
captchaId = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCaptchaId() {
|
||||||
|
return captchaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCaptchaUrl(WikiSite wiki) {
|
||||||
|
return wiki.url("index.php") + "?title=Special:Captcha/image&wpCaptchaId=" + captchaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeString(captchaId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<CaptchaResult> CREATOR
|
||||||
|
= new Parcelable.Creator<CaptchaResult>() {
|
||||||
|
@Override
|
||||||
|
public CaptchaResult createFromParcel(Parcel in) {
|
||||||
|
return new CaptchaResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CaptchaResult[] newArray(int size) {
|
||||||
|
return new CaptchaResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.wikipedia.concurrency;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.annotations.CheckReturnValue;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.functions.Consumer;
|
||||||
|
import io.reactivex.subjects.PublishSubject;
|
||||||
|
import io.reactivex.subjects.Subject;
|
||||||
|
|
||||||
|
public class RxBus {
|
||||||
|
|
||||||
|
private final Subject<Object> bus = PublishSubject.create().toSerialized();
|
||||||
|
private final Observable<Object> observable = bus.observeOn(AndroidSchedulers.mainThread());
|
||||||
|
|
||||||
|
public void post(Object o) {
|
||||||
|
bus.onNext(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@CheckReturnValue
|
||||||
|
public Disposable subscribe(@NonNull Consumer<Object> consumer) {
|
||||||
|
return observable.subscribe(consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.wikipedia.createaccount;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception thrown when an account creation request FAILs
|
||||||
|
*/
|
||||||
|
public class CreateAccountException extends RuntimeException {
|
||||||
|
CreateAccountException(@NonNull String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package org.wikipedia.createaccount;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class CreateAccountResult implements Parcelable {
|
||||||
|
@NonNull private final String status;
|
||||||
|
@NonNull private final String message;
|
||||||
|
|
||||||
|
public CreateAccountResult(@NonNull String status, @NonNull String message) {
|
||||||
|
this.status = status;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel parcel, int flags) {
|
||||||
|
parcel.writeString(status);
|
||||||
|
parcel.writeString(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CreateAccountResult(Parcel in) {
|
||||||
|
status = in.readString();
|
||||||
|
message = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static final Parcelable.Creator<CreateAccountResult> CREATOR
|
||||||
|
= new Parcelable.Creator<CreateAccountResult>() {
|
||||||
|
@Override
|
||||||
|
public CreateAccountResult createFromParcel(Parcel in) {
|
||||||
|
return new CreateAccountResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CreateAccountResult[] newArray(int size) {
|
||||||
|
return new CreateAccountResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.wikipedia.createaccount;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
class CreateAccountSuccessResult extends CreateAccountResult implements Parcelable {
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
CreateAccountSuccessResult(@NonNull String username) {
|
||||||
|
super("PASS", "Account created");
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel parcel, int flags) {
|
||||||
|
super.writeToParcel(parcel, flags);
|
||||||
|
parcel.writeString(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CreateAccountSuccessResult(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
username = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<CreateAccountSuccessResult> CREATOR
|
||||||
|
= new Creator<CreateAccountSuccessResult>() {
|
||||||
|
@Override
|
||||||
|
public CreateAccountSuccessResult createFromParcel(Parcel in) {
|
||||||
|
return new CreateAccountSuccessResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CreateAccountSuccessResult[] newArray(int size) {
|
||||||
|
return new CreateAccountSuccessResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
package org.wikipedia.csrf;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.wikipedia.AppAdapter;
|
||||||
|
import org.wikipedia.dataclient.Service;
|
||||||
|
import org.wikipedia.dataclient.ServiceFactory;
|
||||||
|
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
import org.wikipedia.login.LoginClient;
|
||||||
|
import org.wikipedia.login.LoginResult;
|
||||||
|
import org.wikipedia.util.log.L;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class CsrfTokenClient {
|
||||||
|
private static final String ANON_TOKEN = "+\\";
|
||||||
|
private static final int MAX_RETRIES = 1;
|
||||||
|
private static final int MAX_RETRIES_OF_LOGIN_BLOCKING = 2;
|
||||||
|
@NonNull private final WikiSite csrfWikiSite;
|
||||||
|
@NonNull private final WikiSite loginWikiSite;
|
||||||
|
private int retries = 0;
|
||||||
|
|
||||||
|
@Nullable private Call<MwQueryResponse> csrfTokenCall;
|
||||||
|
@NonNull private LoginClient loginClient = new LoginClient();
|
||||||
|
|
||||||
|
public CsrfTokenClient(@NonNull WikiSite csrfWikiSite, @NonNull WikiSite loginWikiSite) {
|
||||||
|
this.csrfWikiSite = csrfWikiSite;
|
||||||
|
this.loginWikiSite = loginWikiSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void request(@NonNull final Callback callback) {
|
||||||
|
request(false, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void request(boolean forceLogin, @NonNull final Callback callback) {
|
||||||
|
cancel();
|
||||||
|
if (forceLogin) {
|
||||||
|
retryWithLogin(new RuntimeException("Forcing login..."), callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
csrfTokenCall = request(ServiceFactory.get(csrfWikiSite), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancel() {
|
||||||
|
loginClient.cancel();
|
||||||
|
if (csrfTokenCall != null) {
|
||||||
|
csrfTokenCall.cancel();
|
||||||
|
csrfTokenCall = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
Call<MwQueryResponse> request(@NonNull Service service, @NonNull final Callback cb) {
|
||||||
|
return requestToken(service, new CsrfTokenClient.Callback() {
|
||||||
|
@Override public void success(@NonNull String token) {
|
||||||
|
if (AppAdapter.get().isLoggedIn() && token.equals(ANON_TOKEN)) {
|
||||||
|
retryWithLogin(new RuntimeException("App believes we're logged in, but got anonymous token."), cb);
|
||||||
|
} else {
|
||||||
|
cb.success(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void failure(@NonNull Throwable caught) {
|
||||||
|
retryWithLogin(caught, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void twoFactorPrompt() {
|
||||||
|
cb.twoFactorPrompt();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void retryWithLogin(@NonNull Throwable caught, @NonNull final Callback callback) {
|
||||||
|
if (retries < MAX_RETRIES
|
||||||
|
&& !TextUtils.isEmpty(AppAdapter.get().getUserName())
|
||||||
|
&& !TextUtils.isEmpty(AppAdapter.get().getPassword())) {
|
||||||
|
retries++;
|
||||||
|
|
||||||
|
SharedPreferenceCookieManager.getInstance().clearAllCookies();
|
||||||
|
|
||||||
|
login(AppAdapter.get().getUserName(), AppAdapter.get().getPassword(), () -> {
|
||||||
|
L.i("retrying...");
|
||||||
|
request(callback);
|
||||||
|
}, callback);
|
||||||
|
} else {
|
||||||
|
callback.failure(caught);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void login(@NonNull final String username, @NonNull final String password,
|
||||||
|
@NonNull final RetryCallback retryCallback,
|
||||||
|
@NonNull final Callback callback) {
|
||||||
|
new LoginClient().request(loginWikiSite, username, password,
|
||||||
|
new LoginClient.LoginCallback() {
|
||||||
|
@Override
|
||||||
|
public void success(@NonNull LoginResult loginResult) {
|
||||||
|
if (loginResult.pass()) {
|
||||||
|
AppAdapter.get().updateAccount(loginResult);
|
||||||
|
retryCallback.retry();
|
||||||
|
} else {
|
||||||
|
callback.failure(new LoginClient.LoginFailedException(loginResult.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void twoFactorPrompt(@NonNull Throwable caught, @Nullable String token) {
|
||||||
|
callback.twoFactorPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void passwordResetPrompt(@Nullable String token) {
|
||||||
|
// Should not happen here, but call the callback just in case.
|
||||||
|
callback.failure(new LoginClient.LoginFailedException("Logged in with temporary password."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(@NonNull Throwable caught) {
|
||||||
|
callback.failure(caught);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getTokenBlocking() throws Throwable {
|
||||||
|
String token = "";
|
||||||
|
Service service = ServiceFactory.get(csrfWikiSite);
|
||||||
|
|
||||||
|
for (int retry = 0; retry < MAX_RETRIES_OF_LOGIN_BLOCKING; retry++) {
|
||||||
|
try {
|
||||||
|
if (retry > 0) {
|
||||||
|
// Log in explicitly
|
||||||
|
new LoginClient().loginBlocking(loginWikiSite, AppAdapter.get().getUserName(),
|
||||||
|
AppAdapter.get().getPassword(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
Response<MwQueryResponse> response = service.getCsrfTokenCall().execute();
|
||||||
|
if (response.body() == null || response.body().query() == null
|
||||||
|
|| TextUtils.isEmpty(response.body().query().csrfToken())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
token = response.body().query().csrfToken();
|
||||||
|
if (AppAdapter.get().isLoggedIn() && token.equals(ANON_TOKEN)) {
|
||||||
|
throw new RuntimeException("App believes we're logged in, but got anonymous token.");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
L.w(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(token) || token.equals(ANON_TOKEN)) {
|
||||||
|
throw new IOException("Invalid token, or login failure.");
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting @NonNull Call<MwQueryResponse> requestToken(@NonNull Service service,
|
||||||
|
@NonNull final Callback cb) {
|
||||||
|
Call<MwQueryResponse> call = service.getCsrfTokenCall();
|
||||||
|
call.enqueue(new retrofit2.Callback<MwQueryResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<MwQueryResponse> call, @NonNull Response<MwQueryResponse> response) {
|
||||||
|
if (call.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.success(response.body().query().csrfToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<MwQueryResponse> call, @NonNull Throwable t) {
|
||||||
|
if (call.isCanceled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.failure(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void success(@NonNull String token);
|
||||||
|
void failure(@NonNull Throwable caught);
|
||||||
|
void twoFactorPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DefaultCallback implements Callback {
|
||||||
|
@Override
|
||||||
|
public void success(@NonNull String token) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failure(@NonNull Throwable caught) {
|
||||||
|
L.e(caught);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void twoFactorPrompt() {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface RetryCallback {
|
||||||
|
void retry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.restbase.RbDefinition;
|
||||||
|
import org.wikipedia.dataclient.restbase.RbRelatedPages;
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageLead;
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageRemaining;
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
import org.wikipedia.feed.aggregated.AggregatedFeedContent;
|
||||||
|
import org.wikipedia.feed.announcement.AnnouncementList;
|
||||||
|
import org.wikipedia.feed.configure.FeedAvailability;
|
||||||
|
import org.wikipedia.feed.onthisday.OnThisDay;
|
||||||
|
import org.wikipedia.gallery.Gallery;
|
||||||
|
import org.wikipedia.readinglist.sync.SyncedReadingLists;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.DELETE;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Header;
|
||||||
|
import retrofit2.http.Headers;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.PUT;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
public interface RestService {
|
||||||
|
String REST_API_PREFIX = "api/rest_v1/";
|
||||||
|
|
||||||
|
String ACCEPT_HEADER_PREFIX = "accept: application/json; charset=utf-8; profile=\"https://www.mediawiki.org/wiki/Specs/";
|
||||||
|
String ACCEPT_HEADER_SUMMARY = ACCEPT_HEADER_PREFIX + "Summary/1.2.0\"";
|
||||||
|
String ACCEPT_HEADER_MOBILE_SECTIONS = ACCEPT_HEADER_PREFIX + "mobile-sections/0.12.4\"";
|
||||||
|
String ACCEPT_HEADER_DEFINITION = ACCEPT_HEADER_PREFIX + "definition/0.7.2\"";
|
||||||
|
|
||||||
|
String REST_PAGE_SECTIONS_URL = "page/mobile-sections-remaining/{title}";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a page summary for a given title -- for link previews
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@Headers({
|
||||||
|
"x-analytics: preview=1",
|
||||||
|
ACCEPT_HEADER_SUMMARY
|
||||||
|
})
|
||||||
|
@GET("page/summary/{title}")
|
||||||
|
@NonNull
|
||||||
|
Observable<RbPageSummary> getSummary(@Nullable @Header("Referer") String referrerUrl,
|
||||||
|
@NonNull @Path("title") String title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lead section and initial metadata of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title with prefix if necessary
|
||||||
|
*/
|
||||||
|
@Headers({
|
||||||
|
"x-analytics: pageview=1",
|
||||||
|
ACCEPT_HEADER_MOBILE_SECTIONS
|
||||||
|
})
|
||||||
|
@GET("page/mobile-sections-lead/{title}")
|
||||||
|
@NonNull
|
||||||
|
Observable<Response<RbPageLead>> getLeadSection(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(Service.OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@Nullable @Header("Referer") String referrerUrl,
|
||||||
|
@NonNull @Path("title") String title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remaining sections of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@Headers(ACCEPT_HEADER_MOBILE_SECTIONS)
|
||||||
|
@GET(REST_PAGE_SECTIONS_URL)
|
||||||
|
@NonNull Observable<Response<RbPageRemaining>> getRemainingSections(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(Service.OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@NonNull @Path("title") String title);
|
||||||
|
/**
|
||||||
|
* TODO: remove this if we find a way to get the request url before the observable object being executed
|
||||||
|
* Gets the remaining sections request url of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@Headers(ACCEPT_HEADER_MOBILE_SECTIONS)
|
||||||
|
@GET(REST_PAGE_SECTIONS_URL)
|
||||||
|
@NonNull Call<RbPageRemaining> getRemainingSectionsUrl(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(Service.OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@NonNull @Path("title") String title);
|
||||||
|
|
||||||
|
// todo: this Content Service-only endpoint is under page/ but that implementation detail should
|
||||||
|
// probably not be reflected here. Move to WordDefinitionClient
|
||||||
|
/**
|
||||||
|
* Gets selected Wiktionary content for a given title derived from user-selected text
|
||||||
|
*
|
||||||
|
* @param title the Wiktionary page title derived from user-selected Wikipedia article text
|
||||||
|
*/
|
||||||
|
@Headers(ACCEPT_HEADER_DEFINITION)
|
||||||
|
@GET("page/definition/{title}")
|
||||||
|
@NonNull Observable<Map<String, RbDefinition.Usage[]>> getDefinition(@NonNull @Path("title") String title);
|
||||||
|
|
||||||
|
@Headers(ACCEPT_HEADER_SUMMARY)
|
||||||
|
@GET("page/random/summary")
|
||||||
|
@NonNull Observable<RbPageSummary> getRandomSummary();
|
||||||
|
|
||||||
|
@Headers(ACCEPT_HEADER_SUMMARY)
|
||||||
|
@GET("page/related/{title}")
|
||||||
|
@NonNull Observable<RbRelatedPages> getRelatedPages(@Path("title") String title);
|
||||||
|
|
||||||
|
@GET("page/media/{title}")
|
||||||
|
@NonNull Observable<Gallery> getMedia(@Path("title") String title);
|
||||||
|
|
||||||
|
@GET("feed/onthisday/events/{mm}/{dd}")
|
||||||
|
@NonNull Observable<OnThisDay> getOnThisDay(@Path("mm") int month, @Path("dd") int day);
|
||||||
|
|
||||||
|
@Headers(ACCEPT_HEADER_PREFIX + "announcements/0.1.0\"")
|
||||||
|
@GET("feed/announcements")
|
||||||
|
@NonNull Call<AnnouncementList> getAnnouncements();
|
||||||
|
|
||||||
|
@Headers(ACCEPT_HEADER_PREFIX + "aggregated-feed/0.5.0\"")
|
||||||
|
@GET("feed/featured/{year}/{month}/{day}")
|
||||||
|
@NonNull Observable<AggregatedFeedContent> getAggregatedFeed(@Path("year") String year,
|
||||||
|
@Path("month") String month,
|
||||||
|
@Path("day") String day);
|
||||||
|
|
||||||
|
@GET("feed/availability")
|
||||||
|
@NonNull Observable<FeedAvailability> getFeedAvailability();
|
||||||
|
|
||||||
|
|
||||||
|
// ------- Reading lists -------
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST("data/lists/setup")
|
||||||
|
@NonNull Call<Void> setupReadingLists(@Query("csrf_token") String token);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST("data/lists/teardown")
|
||||||
|
@NonNull Call<Void> tearDownReadingLists(@Query("csrf_token") String token);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET("data/lists/")
|
||||||
|
@NonNull Call<SyncedReadingLists> getReadingLists(@Query("next") String next);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST("data/lists/")
|
||||||
|
@NonNull Call<SyncedReadingLists.RemoteIdResponse> createReadingList(@Query("csrf_token") String token,
|
||||||
|
@Body SyncedReadingLists.RemoteReadingList list);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@PUT("data/lists/{id}")
|
||||||
|
@NonNull Call<Void> updateReadingList(@Path("id") long listId, @Query("csrf_token") String token,
|
||||||
|
@Body SyncedReadingLists.RemoteReadingList list);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@DELETE("data/lists/{id}")
|
||||||
|
@NonNull Call<Void> deleteReadingList(@Path("id") long listId, @Query("csrf_token") String token);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET("data/lists/changes/since/{date}")
|
||||||
|
@NonNull Call<SyncedReadingLists> getReadingListChangesSince(@Path("date") String iso8601Date,
|
||||||
|
@Query("next") String next);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET("data/lists/pages/{project}/{title}")
|
||||||
|
@NonNull Call<SyncedReadingLists> getReadingListsContaining(@Path("project") String project,
|
||||||
|
@Path("title") String title,
|
||||||
|
@Query("next") String next);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET("data/lists/{id}/entries/")
|
||||||
|
@NonNull Call<SyncedReadingLists> getReadingListEntries(@Path("id") long listId, @Query("next") String next);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST("data/lists/{id}/entries/")
|
||||||
|
@NonNull Call<SyncedReadingLists.RemoteIdResponse> addEntryToReadingList(@Path("id") long listId,
|
||||||
|
@Query("csrf_token") String token,
|
||||||
|
@Body SyncedReadingLists.RemoteReadingListEntry entry);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST("data/lists/{id}/entries/batch")
|
||||||
|
@NonNull Call<SyncedReadingLists.RemoteIdResponseBatch> addEntriesToReadingList(@Path("id") long listId,
|
||||||
|
@Query("csrf_token") String token,
|
||||||
|
@Body SyncedReadingLists.RemoteReadingListEntryBatch batch);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@DELETE("data/lists/{id}/entries/{entry_id}")
|
||||||
|
@NonNull Call<Void> deleteEntryFromReadingList(@Path("id") long listId, @Path("entry_id") long entryId,
|
||||||
|
@Query("csrf_token") String token);
|
||||||
|
|
||||||
|
}
|
||||||
401
data-client/src/main/java/org/wikipedia/dataclient/Service.java
Normal file
401
data-client/src/main/java/org/wikipedia/dataclient/Service.java
Normal file
|
|
@ -0,0 +1,401 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.captcha.Captcha;
|
||||||
|
import org.wikipedia.dataclient.mwapi.CreateAccountResponse;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwPostResponse;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
import org.wikipedia.dataclient.mwapi.SiteMatrix;
|
||||||
|
import org.wikipedia.dataclient.mwapi.page.MwMobileViewPageLead;
|
||||||
|
import org.wikipedia.dataclient.mwapi.page.MwMobileViewPageRemaining;
|
||||||
|
import org.wikipedia.dataclient.mwapi.page.MwQueryPageSummary;
|
||||||
|
import org.wikipedia.edit.Edit;
|
||||||
|
import org.wikipedia.edit.preview.EditPreview;
|
||||||
|
import org.wikipedia.login.LoginClient;
|
||||||
|
import org.wikipedia.search.PrefixSearchResponse;
|
||||||
|
import org.wikipedia.wikidata.Entities;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Response;
|
||||||
|
import retrofit2.http.Field;
|
||||||
|
import retrofit2.http.FormUrlEncoded;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Header;
|
||||||
|
import retrofit2.http.Headers;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrofit service layer for all API interactions, including regular MediaWiki and RESTBase.
|
||||||
|
*/
|
||||||
|
public interface Service {
|
||||||
|
String WIKIPEDIA_URL = "https://wikipedia.org/";
|
||||||
|
String WIKIDATA_URL = "https://www.wikidata.org/";
|
||||||
|
String COMMONS_URL = "https://commons.wikimedia.org/";
|
||||||
|
String META_URL = "https://meta.wikimedia.org/";
|
||||||
|
|
||||||
|
String MW_API_PREFIX = "w/api.php?format=json&formatversion=2&errorformat=plaintext&";
|
||||||
|
|
||||||
|
String MW_PAGE_SECTIONS_URL = MW_API_PREFIX + "action=mobileview&prop="
|
||||||
|
+ "text|sections&onlyrequestedsections=1§ions=1-"
|
||||||
|
+ "§ionprop=toclevel|line|anchor&noheadings=";
|
||||||
|
|
||||||
|
int PREFERRED_THUMB_SIZE = 320;
|
||||||
|
|
||||||
|
String OFFLINE_SAVE_HEADER = "X-Offline-Save";
|
||||||
|
String OFFLINE_SAVE_HEADER_SAVE = "save";
|
||||||
|
String OFFLINE_SAVE_HEADER_DELETE = "delete";
|
||||||
|
String OFFLINE_SAVE_HEADER_NONE = "none";
|
||||||
|
|
||||||
|
// ------- MobileView page content -------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lead section and initial metadata of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title with prefix if necessary
|
||||||
|
* @return a Retrofit Call which provides the populated MwMobileViewPageLead object in #success
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
Here's the rationale for this API call:
|
||||||
|
We request 10 sentences from the lead section, and then re-parse the text using our own
|
||||||
|
sentence parsing logic to end up with 2 sentences for the link preview. We trust our
|
||||||
|
parsing logic more than TextExtracts because it's better-tailored to the user's
|
||||||
|
Locale on the client side. For example, the TextExtracts extension incorrectly treats
|
||||||
|
abbreviations like "i.e.", "B.C.", "Jr.", etc. as separate sentences, whereas our parser
|
||||||
|
will leave those alone.
|
||||||
|
|
||||||
|
Also, we no longer request "excharacters" from TextExtracts, since it has an issue where
|
||||||
|
it's liable to return content that lies beyond the lead section, which might include
|
||||||
|
unparsed wikitext, which we certainly don't want.
|
||||||
|
*/
|
||||||
|
@Headers("x-analytics: preview=1")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&redirects=&converttitles="
|
||||||
|
+ "&prop=extracts|pageimages|pageprops&exsentences=5&piprop=thumbnail|name"
|
||||||
|
+ "&pilicense=any&explaintext=&pithumbsize=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryPageSummary> getSummary(@Nullable @Header("Referer") String referrerUrl,
|
||||||
|
@NonNull @Query("titles") String title,
|
||||||
|
@Nullable @Query("uselang") String useLang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lead section and initial metadata of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title with prefix if necessary
|
||||||
|
* @param leadImageWidth one of the bucket widths for the lead image
|
||||||
|
*/
|
||||||
|
@Headers("x-analytics: pageview=1")
|
||||||
|
@GET(MW_API_PREFIX + "action=mobileview&prop="
|
||||||
|
+ "text|sections|languagecount|thumb|image|id|namespace|revision"
|
||||||
|
+ "|description|lastmodified|normalizedtitle|displaytitle|protection"
|
||||||
|
+ "|editable|pageprops&pageprops=wikibase_item"
|
||||||
|
+ "§ions=0§ionprop=toclevel|line|anchor&noheadings=")
|
||||||
|
@NonNull Observable<Response<MwMobileViewPageLead>> getLeadSection(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@Nullable @Header("Referer") String referrerUrl,
|
||||||
|
@NonNull @Query("page") String title,
|
||||||
|
@Query("thumbwidth") int leadImageWidth,
|
||||||
|
@Nullable @Query("uselang") String useLang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remaining sections of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@GET(MW_PAGE_SECTIONS_URL)
|
||||||
|
@NonNull Observable<Response<MwMobileViewPageRemaining>> getRemainingSections(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@NonNull @Query("page") String title,
|
||||||
|
@Nullable @Query("uselang") String useLang);
|
||||||
|
/**
|
||||||
|
* TODO: remove this if we find a way to get the request url before the observable object being executed
|
||||||
|
* Gets the remaining sections request url of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@GET(MW_PAGE_SECTIONS_URL)
|
||||||
|
@NonNull Call<MwMobileViewPageRemaining> getRemainingSectionsUrl(@Nullable @Header("Cache-Control") String cacheControl,
|
||||||
|
@Nullable @Header(OFFLINE_SAVE_HEADER) String saveHeader,
|
||||||
|
@NonNull @Query("page") String title,
|
||||||
|
@Nullable @Query("uselang") String useLang);
|
||||||
|
|
||||||
|
// ------- Search -------
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=pageimages&piprop=thumbnail"
|
||||||
|
+ "&converttitles=&pilicense=any&pithumbsize=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryResponse> getPageImages(@NonNull @Query("titles") String titles);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&redirects="
|
||||||
|
+ "&converttitles=&prop=description|pageimages&piprop=thumbnail"
|
||||||
|
+ "&pilicense=any&generator=prefixsearch&gpsnamespace=0&list=search&srnamespace=0"
|
||||||
|
+ "&srwhat=text&srinfo=suggestion&srprop=&sroffset=0&srlimit=1&pithumbsize=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<PrefixSearchResponse> prefixSearch(@Query("gpssearch") String title,
|
||||||
|
@Query("gpslimit") int maxResults,
|
||||||
|
@Query("srsearch") String repeat);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&converttitles="
|
||||||
|
+ "&prop=description|pageimages|pageprops&ppprop=mainpage|disambiguation"
|
||||||
|
+ "&generator=search&gsrnamespace=0&gsrwhat=text"
|
||||||
|
+ "&gsrinfo=&gsrprop=redirecttitle&piprop=thumbnail&pilicense=any&pithumbsize="
|
||||||
|
+ PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryResponse> fullTextSearch(@Query("gsrsearch") String searchTerm,
|
||||||
|
@Query("gsrlimit") int gsrLimit,
|
||||||
|
@Query("continue") String cont,
|
||||||
|
@Query("gsroffset") String gsrOffset);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=coordinates|description|pageimages"
|
||||||
|
+ "&colimit=50&piprop=thumbnail&pilicense=any"
|
||||||
|
+ "&generator=geosearch&ggslimit=50&pithumbsize=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryResponse> nearbySearch(@NonNull @Query("ggscoord") String coord,
|
||||||
|
@Query("ggsradius") double radius);
|
||||||
|
|
||||||
|
|
||||||
|
// ------- Miscellaneous -------
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=fancycaptchareload")
|
||||||
|
@NonNull Observable<Captcha> getNewCaptcha();
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=langlinks&lllimit=500&redirects=&converttitles=")
|
||||||
|
@NonNull Observable<MwQueryResponse> getLangLinks(@NonNull @Query("titles") String title);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=description|pageprops&redirects")
|
||||||
|
@NonNull Observable<MwQueryResponse> getPagePropsAndDescription(@NonNull @Query("titles") String titles);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=description")
|
||||||
|
@NonNull Observable<MwQueryResponse> getDescription(@NonNull @Query("titles") String titles);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=imageinfo&iiprop=timestamp|user|url|extmetadata&iiurlwidth=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryResponse> getImageExtMetadata(@NonNull @Query("titles") String titles);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=sitematrix&smtype=language&smlangprop=code|name|localname")
|
||||||
|
@NonNull Observable<SiteMatrix> getSiteMatrix();
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=siteinfo")
|
||||||
|
@NonNull Observable<MwQueryResponse> getSiteInfo();
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&generator=random&redirects=1&grnnamespace=0&grnlimit=50&prop=pageprops|description")
|
||||||
|
@NonNull Observable<MwQueryResponse> getRandomWithPageProps();
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&generator=random&redirects=1&grnnamespace=6&grnlimit=50"
|
||||||
|
+ "&prop=description|imageinfo&iiprop=timestamp|user|url|mime&iiurlwidth=" + PREFERRED_THUMB_SIZE)
|
||||||
|
@NonNull Observable<MwQueryResponse> getRandomWithImageInfo();
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=categories&clprop=hidden&cllimit=500")
|
||||||
|
@NonNull Observable<MwQueryResponse> getCategories(@NonNull @Query("titles") String titles);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&list=categorymembers&cmlimit=500")
|
||||||
|
@NonNull Observable<MwQueryResponse> getCategoryMembers(@NonNull @Query("cmtitle") String title,
|
||||||
|
@Nullable @Query("cmcontinue") String continueStr);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=thank")
|
||||||
|
@NonNull Observable<MwPostResponse> thank(@Nullable @Field("rev") String rev,
|
||||||
|
@Nullable @Field("log") String log,
|
||||||
|
@NonNull @Field("token") String token,
|
||||||
|
@Nullable @Field("source") String source);
|
||||||
|
|
||||||
|
|
||||||
|
// ------- CSRF, Login, and Create Account -------
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf")
|
||||||
|
@NonNull Call<MwQueryResponse> getCsrfTokenCall();
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf")
|
||||||
|
@NonNull Observable<MwQueryResponse> getCsrfToken();
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=createaccount&createmessageformat=html")
|
||||||
|
@NonNull Observable<CreateAccountResponse> postCreateAccount(@NonNull @Field("username") String user,
|
||||||
|
@NonNull @Field("password") String pass,
|
||||||
|
@NonNull @Field("retype") String retype,
|
||||||
|
@NonNull @Field("createtoken") String token,
|
||||||
|
@NonNull @Field("createreturnurl") String returnurl,
|
||||||
|
@Nullable @Field("email") String email,
|
||||||
|
@Nullable @Field("captchaId") String captchaId,
|
||||||
|
@Nullable @Field("captchaWord") String captchaWord);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=login")
|
||||||
|
@NonNull Call<MwQueryResponse> getLoginToken();
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=clientlogin&rememberMe=")
|
||||||
|
@NonNull Call<LoginClient.LoginResponse> postLogIn(@Field("username") String user, @Field("password") String pass,
|
||||||
|
@Field("logintoken") String token, @Field("loginreturnurl") String url);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=clientlogin&rememberMe=")
|
||||||
|
@NonNull Call<LoginClient.LoginResponse> postLogIn(@Field("username") String user, @Field("password") String pass,
|
||||||
|
@Field("retype") String retypedPass, @Field("OATHToken") String twoFactorCode,
|
||||||
|
@Field("logintoken") String token,
|
||||||
|
@Field("logincontinue") boolean loginContinue);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=logout")
|
||||||
|
@NonNull Observable<MwPostResponse> postLogout(@NonNull @Field("token") String token);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=authmanagerinfo|tokens&amirequestsfor=create&type=createaccount")
|
||||||
|
@NonNull Observable<MwQueryResponse> getAuthManagerInfo();
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=userinfo&list=users&usprop=groups|cancreate")
|
||||||
|
@NonNull Observable<MwQueryResponse> getUserInfo(@Query("ususers") @NonNull String userName);
|
||||||
|
|
||||||
|
|
||||||
|
// ------- Notifications -------
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=notifications¬format=model¬limit=max")
|
||||||
|
@NonNull Observable<MwQueryResponse> getAllNotifications(@Query("notwikis") @Nullable String wikiList,
|
||||||
|
@Query("notfilter") @Nullable String filter,
|
||||||
|
@Query("notcontinue") @Nullable String continueStr);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=echomarkread")
|
||||||
|
@NonNull Observable<MwQueryResponse> markRead(@Field("token") @NonNull String token, @Field("list") @Nullable String readList, @Field("unreadlist") @Nullable String unreadList);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=notifications¬prop=list¬filter=!read¬limit=1")
|
||||||
|
@NonNull Observable<MwQueryResponse> getLastUnreadNotification();
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=unreadnotificationpages&unplimit=max&unpwikis=*")
|
||||||
|
@NonNull Observable<MwQueryResponse> getUnreadNotificationWikis();
|
||||||
|
|
||||||
|
|
||||||
|
// ------- User Options -------
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=userinfo&uiprop=options")
|
||||||
|
@NonNull Observable<MwQueryResponse> getUserOptions();
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=options")
|
||||||
|
@NonNull Observable<MwPostResponse> postUserOption(@Field("token") @NonNull String token,
|
||||||
|
@Query("optionname") @NonNull String key,
|
||||||
|
@Query("optionvalue") @Nullable String value);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=options")
|
||||||
|
@NonNull Observable<MwPostResponse> deleteUserOption(@Field("token") @NonNull String token,
|
||||||
|
@Query("change") @NonNull String key);
|
||||||
|
|
||||||
|
|
||||||
|
// ------- Editing -------
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles=")
|
||||||
|
@NonNull Observable<MwQueryResponse> getWikiTextForSection(@NonNull @Query("titles") String title, @Query("rvsection") int section);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST(MW_API_PREFIX + "action=parse&prop=text§ionpreview=&pst=&mobileformat=")
|
||||||
|
@NonNull Observable<EditPreview> postEditPreview(@NonNull @Field("title") String title,
|
||||||
|
@NonNull @Field("text") String text);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=edit&nocreate=")
|
||||||
|
@SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
@NonNull Call<Edit> postEditSubmit(@NonNull @Field("title") String title,
|
||||||
|
@Nullable @Field("section") Integer section,
|
||||||
|
@NonNull @Field("summary") String summary,
|
||||||
|
@Nullable @Field("assert") String user,
|
||||||
|
@NonNull @Field("text") String text,
|
||||||
|
@Nullable @Field("basetimestamp") String baseTimeStamp,
|
||||||
|
@NonNull @Field("token") String token,
|
||||||
|
@Nullable @Field("captchaid") String captchaId,
|
||||||
|
@Nullable @Field("captchaword") String captchaWord);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=edit&nocreate=")
|
||||||
|
@NonNull Observable<Edit> postAppendEdit(@NonNull @Field("title") String title,
|
||||||
|
@NonNull @Field("summary") String summary,
|
||||||
|
@NonNull @Field("appendtext") String text,
|
||||||
|
@NonNull @Field("token") String token);
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=edit&nocreate=")
|
||||||
|
@NonNull Observable<Edit> postPrependEdit(@NonNull @Field("title") String title,
|
||||||
|
@NonNull @Field("summary") String summary,
|
||||||
|
@NonNull @Field("prependtext") String text,
|
||||||
|
@NonNull @Field("token") String token);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=tag")
|
||||||
|
@FormUrlEncoded
|
||||||
|
Observable<MwPostResponse> addEditTag(@NonNull @Field("revid") String revId,
|
||||||
|
@NonNull @Field("add") String tagName,
|
||||||
|
@NonNull @Field("reason") String reason,
|
||||||
|
@NonNull @Field("token") String token);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@GET(MW_API_PREFIX + "action=query&meta=wikimediaeditortaskscounts")
|
||||||
|
@NonNull Observable<MwQueryResponse> getEditorTaskCounts();
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&generator=wikimediaeditortaskssuggestions&prop=pageprops&gwetstask=missingdescriptions&gwetslimit=3")
|
||||||
|
@NonNull Observable<MwQueryResponse> getEditorTaskMissingDescriptions(@NonNull @Query("gwetstarget") String targetLanguage);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=query&generator=wikimediaeditortaskssuggestions&prop=pageprops&gwetstask=descriptiontranslations&gwetslimit=3")
|
||||||
|
@NonNull Observable<MwQueryResponse> getEditorTaskTranslatableDescriptions(@NonNull @Query("gwetssource") String sourceLanguage,
|
||||||
|
@NonNull @Query("gwetstarget") String targetLanguage);
|
||||||
|
|
||||||
|
|
||||||
|
// ------- Wikidata -------
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=wbgetentities")
|
||||||
|
@NonNull Observable<Entities> getEntitiesByTitle(@Query("titles") @NonNull String titles,
|
||||||
|
@Query("sites") @NonNull String sites);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=wbgetentities&props=labels&languagefallback=1")
|
||||||
|
@NonNull Call<Entities> getWikidataLabels(@Query("ids") @NonNull String idList,
|
||||||
|
@Query("languages") @NonNull String langList);
|
||||||
|
|
||||||
|
@GET(MW_API_PREFIX + "action=wbgetentities&props=descriptions|labels|sitelinks")
|
||||||
|
@NonNull Observable<Entities> getWikidataLabelsAndDescriptions(@Query("ids") @NonNull String idList);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=wbsetdescription&errorlang=uselang")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
Observable<MwPostResponse> postDescriptionEdit(@NonNull @Field("language") String language,
|
||||||
|
@NonNull @Field("uselang") String useLang,
|
||||||
|
@NonNull @Field("site") String site,
|
||||||
|
@NonNull @Field("title") String title,
|
||||||
|
@NonNull @Field("value") String newDescription,
|
||||||
|
@Nullable @Field("summary") String summary,
|
||||||
|
@NonNull @Field("token") String token,
|
||||||
|
@Nullable @Field("assert") String user);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=wbsetlabel&errorlang=uselang")
|
||||||
|
@FormUrlEncoded
|
||||||
|
@SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
Observable<MwPostResponse> postLabelEdit(@NonNull @Field("language") String language,
|
||||||
|
@NonNull @Field("uselang") String useLang,
|
||||||
|
@NonNull @Field("site") String site,
|
||||||
|
@NonNull @Field("title") String title,
|
||||||
|
@NonNull @Field("value") String newDescription,
|
||||||
|
@Nullable @Field("summary") String summary,
|
||||||
|
@NonNull @Field("token") String token,
|
||||||
|
@Nullable @Field("assert") String user);
|
||||||
|
|
||||||
|
@Headers("Cache-Control: no-cache")
|
||||||
|
@POST(MW_API_PREFIX + "action=wbcreateclaim&errorlang=uselang")
|
||||||
|
@FormUrlEncoded
|
||||||
|
Observable<MwPostResponse> postCreateClaim(@NonNull @Field("entity") String entity,
|
||||||
|
@NonNull @Field("snaktype") String snakType,
|
||||||
|
@NonNull @Field("property") String property,
|
||||||
|
@NonNull @Field("value") String value,
|
||||||
|
@NonNull @Field("uselang") String useLang,
|
||||||
|
@NonNull @Field("token") String token);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The API reported an error in the payload.
|
||||||
|
*/
|
||||||
|
public interface ServiceError {
|
||||||
|
@NonNull String getTitle();
|
||||||
|
|
||||||
|
@NonNull String getDetails();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.collection.LruCache;
|
||||||
|
|
||||||
|
import org.wikipedia.AppAdapter;
|
||||||
|
import org.wikipedia.json.GsonUtil;
|
||||||
|
|
||||||
|
import retrofit2.Retrofit;
|
||||||
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
public final class ServiceFactory {
|
||||||
|
private static final int SERVICE_CACHE_SIZE = 8;
|
||||||
|
private static LruCache<Long, Service> SERVICE_CACHE = new LruCache<>(SERVICE_CACHE_SIZE);
|
||||||
|
private static LruCache<Long, RestService> REST_SERVICE_CACHE = new LruCache<>(SERVICE_CACHE_SIZE);
|
||||||
|
|
||||||
|
public static Service get(@NonNull WikiSite wiki) {
|
||||||
|
long hashCode = wiki.hashCode();
|
||||||
|
if (SERVICE_CACHE.get(hashCode) != null) {
|
||||||
|
return SERVICE_CACHE.get(hashCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Retrofit r = createRetrofit(wiki, wiki.url() + "/");
|
||||||
|
|
||||||
|
Service s = r.create(Service.class);
|
||||||
|
SERVICE_CACHE.put(hashCode, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T get(@NonNull WikiSite wiki, Class<T> service) {
|
||||||
|
return get(wiki, wiki.url() + "/", service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T get(@NonNull WikiSite wiki, @Nullable String baseUrl, Class<T> service) {
|
||||||
|
Retrofit r = createRetrofit(wiki, TextUtils.isEmpty(baseUrl) ? wiki.url() + "/" : baseUrl);
|
||||||
|
return r.create(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RestService getRest(@NonNull WikiSite wiki) {
|
||||||
|
long hashCode = wiki.hashCode();
|
||||||
|
if (REST_SERVICE_CACHE.get(hashCode) != null) {
|
||||||
|
return REST_SERVICE_CACHE.get(hashCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
Retrofit r = createRetrofit(wiki, TextUtils.isEmpty(AppAdapter.get().getRestbaseUriFormat())
|
||||||
|
? wiki.url() + "/" + RestService.REST_API_PREFIX
|
||||||
|
: String.format(AppAdapter.get().getRestbaseUriFormat(), "https", wiki.authority()));
|
||||||
|
|
||||||
|
RestService s = r.create(RestService.class);
|
||||||
|
REST_SERVICE_CACHE.put(hashCode, s);
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Retrofit createRetrofit(@NonNull WikiSite wiki, @NonNull String baseUrl) {
|
||||||
|
return new Retrofit.Builder()
|
||||||
|
.client(AppAdapter.get().getOkHttpClient(wiki))
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
|
||||||
|
.addConverterFactory(GsonConverterFactory.create(GsonUtil.getDefaultGson()))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceFactory() { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,166 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.AppAdapter;
|
||||||
|
import org.wikipedia.util.log.L;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.CookieJar;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
public final class SharedPreferenceCookieManager implements CookieJar {
|
||||||
|
private static final String CENTRALAUTH_PREFIX = "centralauth_";
|
||||||
|
private static SharedPreferenceCookieManager INSTANCE;
|
||||||
|
|
||||||
|
// Map: domain -> list of cookies
|
||||||
|
private final Map<String, List<Cookie>> cookieJar;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static SharedPreferenceCookieManager getInstance() {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
try {
|
||||||
|
INSTANCE = AppAdapter.get().getCookies();
|
||||||
|
} catch (Exception e) {
|
||||||
|
L.logRemoteErrorIfProd(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
INSTANCE = new SharedPreferenceCookieManager();
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedPreferenceCookieManager(Map<String, List<Cookie>> cookieJar) {
|
||||||
|
this.cookieJar = cookieJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SharedPreferenceCookieManager() {
|
||||||
|
cookieJar = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, List<Cookie>> getCookieJar() {
|
||||||
|
return cookieJar;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void persistCookies() {
|
||||||
|
AppAdapter.get().setCookies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void clearAllCookies() {
|
||||||
|
cookieJar.clear();
|
||||||
|
persistCookies();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public synchronized String getCookieByName(@NonNull String name) {
|
||||||
|
for (String domainSpec: cookieJar.keySet()) {
|
||||||
|
for (Cookie cookie : cookieJar.get(domainSpec)) {
|
||||||
|
if (cookie.name().equals(name)) {
|
||||||
|
return cookie.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized void saveFromResponse(@NonNull HttpUrl url, @NonNull List<Cookie> cookies) {
|
||||||
|
if (cookies.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boolean cookieJarModified = false;
|
||||||
|
for (Cookie cookie : cookies) {
|
||||||
|
// Default to the URI's domain if cookie's domain is not explicitly set
|
||||||
|
String domainSpec = TextUtils.isEmpty(cookie.domain()) ? url.uri().getAuthority() : cookie.domain();
|
||||||
|
if (!cookieJar.containsKey(domainSpec)) {
|
||||||
|
cookieJar.put(domainSpec, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Cookie> cookieList = cookieJar.get(domainSpec);
|
||||||
|
if (cookie.expiresAt() < System.currentTimeMillis() || "deleted".equals(cookie.value())) {
|
||||||
|
Iterator<Cookie> i = cookieList.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
if (i.next().name().equals(cookie.name())) {
|
||||||
|
i.remove();
|
||||||
|
cookieJarModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Iterator<Cookie> i = cookieList.iterator();
|
||||||
|
boolean exists = false;
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Cookie c = i.next();
|
||||||
|
if (c.equals(cookie)) {
|
||||||
|
// an identical cookie already exists, so we don't need to update it.
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
} else if (c.name().equals(cookie.name())) {
|
||||||
|
// it's a cookie with the same name, but different contents, so remove the
|
||||||
|
// current cookie, so that the new one will be added.
|
||||||
|
i.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
cookieList.add(cookie);
|
||||||
|
cookieJarModified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cookieJarModified) {
|
||||||
|
persistCookies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized List<Cookie> loadForRequest(@NonNull HttpUrl url) {
|
||||||
|
List<Cookie> cookieList = new ArrayList<>();
|
||||||
|
String domain = url.uri().getAuthority();
|
||||||
|
|
||||||
|
Log.d("CookieManager", "Domain:" + domain);
|
||||||
|
|
||||||
|
for (String domainSpec : cookieJar.keySet()) {
|
||||||
|
List<Cookie> cookiesForDomainSpec = cookieJar.get(domainSpec);
|
||||||
|
|
||||||
|
if (domain.endsWith(domainSpec)) {
|
||||||
|
buildCookieList(cookieList, cookiesForDomainSpec, null);
|
||||||
|
} else if (domainSpec.endsWith("commons.wikimedia.org")) {
|
||||||
|
Log.d("CookieManager", "Adding centralauth cookies");
|
||||||
|
// For sites outside the wikipedia.org domain, transfer the centralauth cookies
|
||||||
|
// from commons.wikimedia.org unconditionally.
|
||||||
|
buildCookieList(cookieList, cookiesForDomainSpec, CENTRALAUTH_PREFIX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildCookieList(@NonNull List<Cookie> outList, @NonNull List<Cookie> inList, @Nullable String prefix) {
|
||||||
|
Iterator<Cookie> i = inList.iterator();
|
||||||
|
boolean cookieJarModified = false;
|
||||||
|
while (i.hasNext()) {
|
||||||
|
Cookie cookie = i.next();
|
||||||
|
if (prefix != null && !cookie.name().startsWith(prefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// But wait, is the cookie expired?
|
||||||
|
if (cookie.expiresAt() < System.currentTimeMillis()) {
|
||||||
|
i.remove();
|
||||||
|
cookieJarModified = true;
|
||||||
|
} else {
|
||||||
|
outList.add(cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cookieJarModified) {
|
||||||
|
persistCookies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
320
data-client/src/main/java/org/wikipedia/dataclient/WikiSite.java
Normal file
320
data-client/src/main/java/org/wikipedia/dataclient/WikiSite.java
Normal file
|
|
@ -0,0 +1,320 @@
|
||||||
|
package org.wikipedia.dataclient;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
import org.wikipedia.util.UriUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base URL and Wikipedia language code for a MediaWiki site. Examples:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <lh>Name: scheme / authority / language code</lh>
|
||||||
|
* <li>English Wikipedia: HTTPS / en.wikipedia.org / en</li>
|
||||||
|
* <li>Chinese Wikipedia: HTTPS / zh.wikipedia.org / zh-hans or zh-hant</li>
|
||||||
|
* <li>Meta-Wiki: HTTPS / meta.wikimedia.org / (none)</li>
|
||||||
|
* <li>Test Wikipedia: HTTPS / test.wikipedia.org / test</li>
|
||||||
|
* <li>Võro Wikipedia: HTTPS / fiu-vro.wikipedia.org / fiu-vro</li>
|
||||||
|
* <li>Simple English Wikipedia: HTTPS / simple.wikipedia.org / simple</li>
|
||||||
|
* <li>Simple English Wikipedia (beta cluster mirror): HTTP / simple.wikipedia.beta.wmflabs.org / simple</li>
|
||||||
|
* <li>Development: HTTP / 192.168.1.11:8080 / (none)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <strong>As shown above, the language code or mapping is part of the authority:</strong>
|
||||||
|
* <ul>
|
||||||
|
* <lh>Validity: authority / language code</lh>
|
||||||
|
* <li>Correct: "test.wikipedia.org" / "test"</li>
|
||||||
|
* <li>Correct: "wikipedia.org", ""</li>
|
||||||
|
* <li>Correct: "no.wikipedia.org", "nb"</li>
|
||||||
|
* <li>Incorrect: "wikipedia.org", "test"</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class WikiSite implements Parcelable {
|
||||||
|
public static final String DEFAULT_SCHEME = "https";
|
||||||
|
private static String DEFAULT_BASE_URL = Service.WIKIPEDIA_URL;
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<WikiSite> CREATOR = new Parcelable.Creator<WikiSite>() {
|
||||||
|
@Override
|
||||||
|
public WikiSite createFromParcel(Parcel in) {
|
||||||
|
return new WikiSite(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WikiSite[] newArray(int size) {
|
||||||
|
return new WikiSite[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// todo: remove @SerializedName. this is now in the TypeAdapter and a "uri" case may be added
|
||||||
|
@SerializedName("domain") @NonNull private final Uri uri;
|
||||||
|
@NonNull private String languageCode;
|
||||||
|
|
||||||
|
public static boolean supportedAuthority(@NonNull String authority) {
|
||||||
|
return authority.endsWith(Uri.parse(DEFAULT_BASE_URL).getAuthority());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDefaultBaseUrl(@NonNull String url) {
|
||||||
|
DEFAULT_BASE_URL = TextUtils.isEmpty(url) ? Service.WIKIPEDIA_URL : url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WikiSite forLanguageCode(@NonNull String languageCode) {
|
||||||
|
Uri uri = ensureScheme(Uri.parse(DEFAULT_BASE_URL));
|
||||||
|
return new WikiSite((languageCode.isEmpty()
|
||||||
|
? "" : (languageCodeToSubdomain(languageCode) + ".")) + uri.getAuthority(),
|
||||||
|
languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WikiSite(@NonNull Uri uri) {
|
||||||
|
Uri tempUri = ensureScheme(uri);
|
||||||
|
String authority = tempUri.getAuthority();
|
||||||
|
if (("wikipedia.org".equals(authority) || "www.wikipedia.org".equals(authority))
|
||||||
|
&& tempUri.getPath() != null && tempUri.getPath().startsWith("/wiki")) {
|
||||||
|
// Special case for Wikipedia only: assume English subdomain when none given.
|
||||||
|
authority = "en.wikipedia.org";
|
||||||
|
}
|
||||||
|
String langVariant = UriUtil.getLanguageVariantFromUri(tempUri);
|
||||||
|
if (!TextUtils.isEmpty(langVariant)) {
|
||||||
|
languageCode = langVariant;
|
||||||
|
} else {
|
||||||
|
languageCode = authorityToLanguageCode(authority);
|
||||||
|
}
|
||||||
|
this.uri = new Uri.Builder()
|
||||||
|
.scheme(tempUri.getScheme())
|
||||||
|
.encodedAuthority(authority)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WikiSite(@NonNull String url) {
|
||||||
|
this(url.startsWith("http") ? Uri.parse(url) : url.startsWith("//")
|
||||||
|
? Uri.parse(DEFAULT_SCHEME + ":" + url) : Uri.parse(DEFAULT_SCHEME + "://" + url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public WikiSite(@NonNull String authority, @NonNull String languageCode) {
|
||||||
|
this(authority);
|
||||||
|
this.languageCode = languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String scheme() {
|
||||||
|
return TextUtils.isEmpty(uri.getScheme()) ? DEFAULT_SCHEME : uri.getScheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The complete wiki authority including language subdomain but not including scheme,
|
||||||
|
* authentication, port, nor trailing slash.
|
||||||
|
*
|
||||||
|
* @see <a href='https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax'>URL syntax</a>
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String authority() {
|
||||||
|
return uri.getAuthority();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like {@link #authority()} but with a "m." between the language subdomain and the rest of the host.
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>English Wikipedia: en.m.wikipedia.org</li>
|
||||||
|
* <li>Chinese Wikipedia: zh.m.wikipedia.org</li>
|
||||||
|
* <li>Meta-Wiki: meta.m.wikimedia.org</li>
|
||||||
|
* <li>Test Wikipedia: test.m.wikipedia.org</li>
|
||||||
|
* <li>Võro Wikipedia: fiu-vro.m.wikipedia.org</li>
|
||||||
|
* <li>Simple English Wikipedia: simple.m.wikipedia.org</li>
|
||||||
|
* <li>Simple English Wikipedia (beta cluster mirror): simple.m.wikipedia.beta.wmflabs.org</li>
|
||||||
|
* <li>Development: m.192.168.1.11</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String mobileAuthority() {
|
||||||
|
return authorityToMobile(authority());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The canonical "desktop" form of the authority. For example, if the authority
|
||||||
|
* is in a "mobile" form, e.g. en.m.wikipedia.org, this will become en.wikipedia.org.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String desktopAuthority() {
|
||||||
|
return authority().replace(".m.", ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String subdomain() {
|
||||||
|
return languageCodeToSubdomain(languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A path without an authority for the segment including a leading "/".
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String path(@NonNull String segment) {
|
||||||
|
return "/w/" + segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull public Uri uri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The canonical URL. e.g., https://en.wikipedia.org.
|
||||||
|
*/
|
||||||
|
@NonNull public String url() {
|
||||||
|
return uri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The canonical URL for segment. e.g., https://en.wikipedia.org/w/foo.
|
||||||
|
*/
|
||||||
|
@NonNull public String url(@NonNull String segment) {
|
||||||
|
return url() + path(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The wiki language code which may differ from the language subdomain. Empty if
|
||||||
|
* language code is unknown. Ex: "en", "zh-hans", ""
|
||||||
|
*
|
||||||
|
* @see AppLanguageLookUpTable
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public String languageCode() {
|
||||||
|
return languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this method doesn't have much to do with WikiSite. Move to PageTitle?
|
||||||
|
/**
|
||||||
|
* Create a PageTitle object from an internal link string.
|
||||||
|
*
|
||||||
|
* @param internalLink Internal link target text (eg. /wiki/Target).
|
||||||
|
* Should be URL decoded before passing in
|
||||||
|
* @return A {@link PageTitle} object representing the internalLink passed in.
|
||||||
|
*/
|
||||||
|
public PageTitle titleForInternalLink(String internalLink) {
|
||||||
|
// Strip the /wiki/ from the href
|
||||||
|
return new PageTitle(UriUtil.removeInternalLinkPrefix(internalLink), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this method doesn't have much to do with WikiSite. Move to PageTitle?
|
||||||
|
/**
|
||||||
|
* Create a PageTitle object from a Uri, taking into account any fragment (section title) in the link.
|
||||||
|
* @param uri Uri object to be turned into a PageTitle.
|
||||||
|
* @return {@link PageTitle} object that corresponds to the given Uri.
|
||||||
|
*/
|
||||||
|
public PageTitle titleForUri(Uri uri) {
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (!TextUtils.isEmpty(uri.getFragment())) {
|
||||||
|
path += "#" + uri.getFragment();
|
||||||
|
}
|
||||||
|
return titleForInternalLink(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String dbName() {
|
||||||
|
return subdomain().replaceAll("-", "_") + "wiki";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generated
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (o == null || getClass() != o.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WikiSite wiki = (WikiSite) o;
|
||||||
|
|
||||||
|
if (!uri.equals(wiki.uri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return languageCode.equals(wiki.languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generated
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = uri.hashCode();
|
||||||
|
result = 31 * result + languageCode.hashCode();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-generated
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "WikiSite{"
|
||||||
|
+ "uri=" + uri
|
||||||
|
+ ", languageCode='" + languageCode + '\''
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||||
|
dest.writeParcelable(uri, 0);
|
||||||
|
dest.writeString(languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected WikiSite(@NonNull Parcel in) {
|
||||||
|
this.uri = in.readParcelable(Uri.class.getClassLoader());
|
||||||
|
this.languageCode = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String languageCodeToSubdomain(@NonNull String languageCode) {
|
||||||
|
switch (languageCode) {
|
||||||
|
case AppLanguageLookUpTable.SIMPLIFIED_CHINESE_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.TRADITIONAL_CHINESE_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.CHINESE_CN_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.CHINESE_HK_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.CHINESE_MO_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.CHINESE_SG_LANGUAGE_CODE:
|
||||||
|
case AppLanguageLookUpTable.CHINESE_TW_LANGUAGE_CODE:
|
||||||
|
return AppLanguageLookUpTable.CHINESE_LANGUAGE_CODE;
|
||||||
|
case AppLanguageLookUpTable.NORWEGIAN_BOKMAL_LANGUAGE_CODE:
|
||||||
|
return AppLanguageLookUpTable.NORWEGIAN_LEGACY_LANGUAGE_CODE; // T114042
|
||||||
|
default:
|
||||||
|
return languageCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull private static String authorityToLanguageCode(@NonNull String authority) {
|
||||||
|
String[] parts = authority.split("\\.");
|
||||||
|
final int minLengthForSubdomain = 3;
|
||||||
|
if (parts.length < minLengthForSubdomain
|
||||||
|
|| parts.length == minLengthForSubdomain && parts[0].equals("m")) {
|
||||||
|
// ""
|
||||||
|
// wikipedia.org
|
||||||
|
// m.wikipedia.org
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return parts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull private static Uri ensureScheme(@NonNull Uri uri) {
|
||||||
|
if (TextUtils.isEmpty(uri.getScheme())) {
|
||||||
|
return uri.buildUpon().scheme(DEFAULT_SCHEME).build();
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param authority Host and optional port. */
|
||||||
|
@NonNull private String authorityToMobile(@NonNull String authority) {
|
||||||
|
if (authority.startsWith("m.") || authority.contains(".m.")) {
|
||||||
|
return authority;
|
||||||
|
}
|
||||||
|
return authority.replaceFirst("^" + subdomain() + "\\.?", "$0m.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class CreateAccountResponse extends MwResponse {
|
||||||
|
@SuppressWarnings("unused") @Nullable private Result createaccount;
|
||||||
|
|
||||||
|
@Nullable public String status() {
|
||||||
|
return createaccount.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String user() {
|
||||||
|
return createaccount.user();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String message() {
|
||||||
|
return createaccount.message();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasResult() {
|
||||||
|
return createaccount != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Result {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String status;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String message;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String username;
|
||||||
|
|
||||||
|
@NonNull public String status() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String user() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String message() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.json.GsonUtil;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class EditorTaskCounts {
|
||||||
|
@Nullable private JsonElement counts;
|
||||||
|
@Nullable @SerializedName("targets_passed") private JsonElement targetsPassed;
|
||||||
|
@Nullable private JsonElement targets;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Map<String, Integer> getDescriptionEditsPerLanguage() {
|
||||||
|
Map<String, Integer> editsPerLanguage = null;
|
||||||
|
if (counts != null && !(counts instanceof JsonArray)) {
|
||||||
|
editsPerLanguage = GsonUtil.getDefaultGson().fromJson(counts, Counts.class).appDescriptionEdits;
|
||||||
|
}
|
||||||
|
return editsPerLanguage == null ? Collections.emptyMap() : editsPerLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Integer> getDescriptionEditTargetsPassed() {
|
||||||
|
List<Integer> passedList = null;
|
||||||
|
if (targetsPassed != null && !(targetsPassed instanceof JsonArray)) {
|
||||||
|
passedList = GsonUtil.getDefaultGson().fromJson(targetsPassed, Targets.class).appDescriptionEdits;
|
||||||
|
}
|
||||||
|
return passedList == null ? Collections.emptyList() : passedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDescriptionEditTargetsPassedCount() {
|
||||||
|
List<Integer> targetList = getDescriptionEditTargets();
|
||||||
|
List<Integer> passedList = getDescriptionEditTargetsPassed();
|
||||||
|
int count = 0;
|
||||||
|
if (!targetList.isEmpty() && !passedList.isEmpty()) {
|
||||||
|
for (int target : targetList) {
|
||||||
|
if (passedList.contains(target)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Integer> getDescriptionEditTargets() {
|
||||||
|
List<Integer> targetList = null;
|
||||||
|
if (targets != null && !(targets instanceof JsonArray)) {
|
||||||
|
targetList = GsonUtil.getDefaultGson().fromJson(targets, Targets.class).appDescriptionEdits;
|
||||||
|
}
|
||||||
|
return targetList == null ? Collections.emptyList() : targetList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Map<String, Integer> getCaptionEditsPerLanguage() {
|
||||||
|
Map<String, Integer> editsPerLanguage = null;
|
||||||
|
if (counts != null && !(counts instanceof JsonArray)) {
|
||||||
|
editsPerLanguage = GsonUtil.getDefaultGson().fromJson(counts, Counts.class).appCaptionEdits;
|
||||||
|
}
|
||||||
|
return editsPerLanguage == null ? Collections.emptyMap() : editsPerLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Integer> getCaptionEditTargetsPassed() {
|
||||||
|
List<Integer> passedList = null;
|
||||||
|
if (targetsPassed != null && !(targetsPassed instanceof JsonArray)) {
|
||||||
|
passedList = GsonUtil.getDefaultGson().fromJson(targetsPassed, Targets.class).appCaptionEdits;
|
||||||
|
}
|
||||||
|
return passedList == null ? Collections.emptyList() : passedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCaptionEditTargetsPassedCount() {
|
||||||
|
List<Integer> targetList = getCaptionEditTargets();
|
||||||
|
List<Integer> passedList = getCaptionEditTargetsPassed();
|
||||||
|
int count = 0;
|
||||||
|
if (!targetList.isEmpty() && !passedList.isEmpty()) {
|
||||||
|
for (int target : targetList) {
|
||||||
|
if (passedList.contains(target)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<Integer> getCaptionEditTargets() {
|
||||||
|
List<Integer> targetList = null;
|
||||||
|
if (targets != null && !(targets instanceof JsonArray)) {
|
||||||
|
targetList = GsonUtil.getDefaultGson().fromJson(targets, Targets.class).appCaptionEdits;
|
||||||
|
}
|
||||||
|
return targetList == null ? Collections.emptyList() : targetList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Counts {
|
||||||
|
@Nullable @SerializedName("app_description_edits") private Map<String, Integer> appDescriptionEdits;
|
||||||
|
@Nullable @SerializedName("app_caption_edits") private Map<String, Integer> appCaptionEdits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Targets {
|
||||||
|
@Nullable @SerializedName("app_description_edits") private List<Integer> appDescriptionEdits;
|
||||||
|
@Nullable @SerializedName("app_caption_edits") private List<Integer> appCaptionEdits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class GeoSearchItem {
|
||||||
|
@Nullable private String title;
|
||||||
|
@SerializedName("lat") private double latitude;
|
||||||
|
@SerializedName("lon") private double longitude;
|
||||||
|
@SerializedName("dist") private double distance;
|
||||||
|
|
||||||
|
@NonNull public String getTitle() {
|
||||||
|
return StringUtils.defaultString(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLatitude() {
|
||||||
|
return latitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getLongitude() {
|
||||||
|
return longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
public class ImageDetails {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") private String name;
|
||||||
|
@SuppressWarnings("unused") private String title;
|
||||||
|
|
||||||
|
@NonNull public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.collection.ArraySet;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ListUserResponse {
|
||||||
|
@SerializedName("name") @Nullable private String name;
|
||||||
|
private long userid;
|
||||||
|
@Nullable private List<String> groups;
|
||||||
|
@Nullable private String cancreate;
|
||||||
|
@Nullable private List<UserResponseCreateError> cancreateerror;
|
||||||
|
|
||||||
|
@Nullable public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canCreate() {
|
||||||
|
return cancreate != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public Set<String> getGroups() {
|
||||||
|
return groups != null ? new ArraySet<>(groups) : Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UserResponseCreateError {
|
||||||
|
@Nullable private String message;
|
||||||
|
@Nullable private String code;
|
||||||
|
@Nullable private String type;
|
||||||
|
|
||||||
|
@NonNull public String message() {
|
||||||
|
return StringUtils.defaultString(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
class MwAuthManagerInfo extends BaseModel {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private List<Request> requests;
|
||||||
|
|
||||||
|
@NonNull List<Request> requests() {
|
||||||
|
return requests;
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Request {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String id;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private Map<String, String> metadata;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String required;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String provider;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String account;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private Map<String, Field> fields;
|
||||||
|
|
||||||
|
@NonNull String id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull Map<String, Field> fields() {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Field {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String type;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String value;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String label;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String help;
|
||||||
|
@SuppressWarnings("unused") private boolean optional;
|
||||||
|
@SuppressWarnings("unused") private boolean sensitive;
|
||||||
|
|
||||||
|
@Nullable String value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class MwException extends RuntimeException {
|
||||||
|
@SuppressWarnings("unused") @NonNull private final MwServiceError error;
|
||||||
|
|
||||||
|
public MwException(@NonNull MwServiceError error) {
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public MwServiceError getError() {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getTitle() {
|
||||||
|
return error.getTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable public String getMessage() {
|
||||||
|
return error.getDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class MwPostResponse extends MwResponse {
|
||||||
|
@Nullable @SuppressWarnings("unused") private String options;
|
||||||
|
@SuppressWarnings("unused") private int success;
|
||||||
|
|
||||||
|
public boolean success(@Nullable String result) {
|
||||||
|
return "success".equals(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getOptions() {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSuccessVal() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
import org.wikipedia.util.DateUtil;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class MwQueryLogEvent extends BaseModel {
|
||||||
|
private int logid;
|
||||||
|
private int ns;
|
||||||
|
private int index;
|
||||||
|
private String title;
|
||||||
|
private int pageid;
|
||||||
|
private Params params;
|
||||||
|
private String type;
|
||||||
|
private String action;
|
||||||
|
private String user;
|
||||||
|
private int userid;
|
||||||
|
private String timestamp;
|
||||||
|
private String comment;
|
||||||
|
private String parsedcomment;
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
public int logid() {
|
||||||
|
return logid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ns() {
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int index() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String title() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int pageid() {
|
||||||
|
return pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String action() {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String user() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int userid() {
|
||||||
|
return userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String timestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date date(){
|
||||||
|
try {
|
||||||
|
return DateUtil.iso8601DateParse(timestamp);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String comment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String parsedcomment() {
|
||||||
|
return parsedcomment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> tags() {
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDeleted() {
|
||||||
|
return pageid==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Params params() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Params{
|
||||||
|
private String img_sha1;
|
||||||
|
private String img_timestamp;
|
||||||
|
|
||||||
|
public String img_sha1() {
|
||||||
|
return img_sha1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String img_timestamp() {
|
||||||
|
return img_timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.gallery.ImageInfo;
|
||||||
|
import org.wikipedia.gallery.VideoInfo;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class representing a standard page object as returned by the MediaWiki API.
|
||||||
|
*/
|
||||||
|
public class MwQueryPage extends BaseModel {
|
||||||
|
@SuppressWarnings("unused") private int pageid;
|
||||||
|
@SuppressWarnings("unused") private int ns;
|
||||||
|
@SuppressWarnings("unused") private int index;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String title;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<LangLink> langlinks;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Revision> revisions;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Coordinates> coordinates;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Category> categories;
|
||||||
|
@SuppressWarnings("unused") @Nullable private PageProps pageprops;
|
||||||
|
@SuppressWarnings("unused") @Nullable private PageTerms terms;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String extract;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Thumbnail thumbnail;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String description;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("descriptionsource") @Nullable private String descriptionSource;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("imageinfo") @Nullable private List<ImageInfo> imageInfo;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("videoinfo") @Nullable private List<VideoInfo> videoInfo;
|
||||||
|
@Nullable private String redirectFrom;
|
||||||
|
@Nullable private String convertedFrom;
|
||||||
|
@Nullable private String convertedTo;
|
||||||
|
|
||||||
|
@NonNull public String title() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int index() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public Namespace namespace() {
|
||||||
|
return Namespace.of(ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public List<LangLink> langLinks() {
|
||||||
|
return langlinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public List<Revision> revisions() {
|
||||||
|
return revisions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public List<Category> categories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public List<Coordinates> coordinates() {
|
||||||
|
// TODO: Handle null values in lists during deserialization, perhaps with a new
|
||||||
|
// @RequiredElements annotation and corresponding TypeAdapter
|
||||||
|
if (coordinates != null) {
|
||||||
|
coordinates.removeAll(Collections.singleton(null));
|
||||||
|
}
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> labels() {
|
||||||
|
return terms != null && terms.label != null ? terms.label : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int pageId() {
|
||||||
|
return pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public PageProps pageProps() {
|
||||||
|
return pageprops;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String extract() {
|
||||||
|
return extract;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String thumbUrl() {
|
||||||
|
return thumbnail != null ? thumbnail.source() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String descriptionSource() {
|
||||||
|
return descriptionSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public ImageInfo imageInfo() {
|
||||||
|
return imageInfo != null ? imageInfo.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public VideoInfo videoInfo() {
|
||||||
|
return videoInfo != null ? videoInfo.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String redirectFrom() {
|
||||||
|
return redirectFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void redirectFrom(@Nullable String from) {
|
||||||
|
redirectFrom = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String convertedFrom() {
|
||||||
|
return convertedFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void convertedFrom(@Nullable String from) {
|
||||||
|
convertedFrom = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String convertedTo() {
|
||||||
|
return convertedTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void convertedTo(@Nullable String to) {
|
||||||
|
convertedTo = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void appendTitleFragment(@Nullable String fragment) {
|
||||||
|
title += "#" + fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Revision {
|
||||||
|
@SerializedName("revid") private long revisionId;
|
||||||
|
private String user;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("contentformat") @NonNull private String contentFormat;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("contentmodel") @NonNull private String contentModel;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("timestamp") @NonNull private String timeStamp;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String content;
|
||||||
|
|
||||||
|
@NonNull public String content() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String timeStamp() {
|
||||||
|
return StringUtils.defaultString(timeStamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRevisionId() {
|
||||||
|
return revisionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getUser() {
|
||||||
|
return StringUtils.defaultString(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class LangLink {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String lang;
|
||||||
|
@NonNull public String lang() {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String title;
|
||||||
|
@NonNull public String title() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Coordinates {
|
||||||
|
@SuppressWarnings("unused") @Nullable private Double lat;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Double lon;
|
||||||
|
|
||||||
|
@Nullable public Double lat() {
|
||||||
|
return lat;
|
||||||
|
}
|
||||||
|
@Nullable public Double lon() {
|
||||||
|
return lon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Thumbnail {
|
||||||
|
@SuppressWarnings("unused") private String source;
|
||||||
|
@SuppressWarnings("unused") private int width;
|
||||||
|
@SuppressWarnings("unused") private int height;
|
||||||
|
String source() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PageProps {
|
||||||
|
@SuppressWarnings("unused") @SerializedName("wikibase_item") @Nullable private String wikiBaseItem;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String displaytitle;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String disambiguation;
|
||||||
|
|
||||||
|
@Nullable public String getDisplayTitle() {
|
||||||
|
return displaytitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getWikiBaseItem() {
|
||||||
|
return StringUtils.defaultString(wikiBaseItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDisambiguation() {
|
||||||
|
return disambiguation != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Category {
|
||||||
|
@SuppressWarnings("unused") private int ns;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String title;
|
||||||
|
@SuppressWarnings("unused") private boolean hidden;
|
||||||
|
|
||||||
|
public int ns() {
|
||||||
|
return ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String title() {
|
||||||
|
return StringUtils.defaultString(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hidden() {
|
||||||
|
return hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PageTerms {
|
||||||
|
@SuppressWarnings("unused") private List<String> alias;
|
||||||
|
@SuppressWarnings("unused") private List<String> label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MwQueryResponse extends MwResponse {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") @SerializedName("batchcomplete") private boolean batchComplete;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") @SerializedName("continue") @Nullable private Map<String, String> continuation;
|
||||||
|
|
||||||
|
@Nullable private MwQueryResult query;
|
||||||
|
|
||||||
|
public boolean batchComplete() {
|
||||||
|
return batchComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Map<String, String> continuation() {
|
||||||
|
return continuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public MwQueryResult query() {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean success() {
|
||||||
|
return query != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting protected void setQuery(@Nullable MwQueryResult query) {
|
||||||
|
this.query = query;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,314 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.gallery.ImageInfo;
|
||||||
|
import org.wikipedia.gallery.VideoInfo;
|
||||||
|
import org.wikipedia.json.PostProcessingTypeAdapter;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
import org.wikipedia.notifications.Notification;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
import org.wikipedia.settings.SiteInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class MwQueryResult extends BaseModel implements PostProcessingTypeAdapter.PostProcessable {
|
||||||
|
@Nullable private List<MwQueryPage> pages;
|
||||||
|
@Nullable private List<Redirect> redirects;
|
||||||
|
@Nullable private List<ConvertedTitle> converted;
|
||||||
|
@SerializedName("userinfo") private UserInfo userInfo;
|
||||||
|
@Nullable private List<ListUserResponse> users;
|
||||||
|
@Nullable private Tokens tokens;
|
||||||
|
@SerializedName("authmanagerinfo") @Nullable private MwAuthManagerInfo amInfo;
|
||||||
|
@Nullable private MarkReadResponse echomarkread;
|
||||||
|
@Nullable private MarkReadResponse echomarkseen;
|
||||||
|
@Nullable private NotificationList notifications;
|
||||||
|
@Nullable private Map<String, Notification.UnreadNotificationWikiItem> unreadnotificationpages;
|
||||||
|
@SerializedName("general") @Nullable private SiteInfo generalSiteInfo;
|
||||||
|
@Nullable private List<RecentChange> recentchanges;
|
||||||
|
@SerializedName("wikimediaeditortaskscounts") @Nullable private EditorTaskCounts editorTaskCounts;
|
||||||
|
@SerializedName("allimages") @Nullable private List<ImageDetails> allImages;
|
||||||
|
@SerializedName("geosearch") @Nullable private List<GeoSearchItem> geoSearch;
|
||||||
|
@Nullable private List<MwQueryLogEvent> logevents;
|
||||||
|
|
||||||
|
|
||||||
|
@Nullable public List<MwQueryPage> pages() {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public MwQueryPage firstPage() {
|
||||||
|
if (pages != null && pages.size() > 0) {
|
||||||
|
return pages.get(0);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<ImageDetails> allImages() {
|
||||||
|
return allImages == null ? Collections.emptyList() : allImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<GeoSearchItem> geoSearch() {
|
||||||
|
return geoSearch == null ? Collections.emptyList() : geoSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public UserInfo userInfo() {
|
||||||
|
return userInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String csrfToken() {
|
||||||
|
return tokens != null ? tokens.csrf() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String createAccountToken() {
|
||||||
|
return tokens != null ? tokens.createAccount() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String loginToken() {
|
||||||
|
return tokens != null ? tokens.login() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public NotificationList notifications() {
|
||||||
|
return notifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Map<String, Notification.UnreadNotificationWikiItem> unreadNotificationWikis() {
|
||||||
|
return unreadnotificationpages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public MarkReadResponse getEchoMarkSeen() {
|
||||||
|
return echomarkseen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String captchaId() {
|
||||||
|
String captchaId = null;
|
||||||
|
if (amInfo != null) {
|
||||||
|
for (MwAuthManagerInfo.Request request : amInfo.requests()) {
|
||||||
|
if ("CaptchaAuthenticationRequest".equals(request.id())) {
|
||||||
|
captchaId = request.fields().get("captchaId").value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return captchaId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public List<RecentChange> getRecentChanges() {
|
||||||
|
return recentchanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public ListUserResponse getUserResponse(@NonNull String userName) {
|
||||||
|
if (users != null) {
|
||||||
|
for (ListUserResponse user : users) {
|
||||||
|
// MediaWiki user names are case sensitive, but the first letter is always capitalized.
|
||||||
|
if (StringUtils.capitalize(userName).equals(user.name())) {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public Map<String, ImageInfo> images() {
|
||||||
|
Map<String, ImageInfo> result = new HashMap<>();
|
||||||
|
if (pages != null) {
|
||||||
|
for (MwQueryPage page : pages) {
|
||||||
|
if (page.imageInfo() != null) {
|
||||||
|
result.put(page.title(), page.imageInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public Map<String, VideoInfo> videos() {
|
||||||
|
Map<String, VideoInfo> result = new HashMap<>();
|
||||||
|
if (pages != null) {
|
||||||
|
for (MwQueryPage page : pages) {
|
||||||
|
if (page.videoInfo() != null) {
|
||||||
|
result.put(page.title(), page.videoInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<PageTitle> langLinks() {
|
||||||
|
List<PageTitle> result = new ArrayList<>();
|
||||||
|
if (pages == null || pages.isEmpty() || pages.get(0).langLinks() == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
for (MwQueryPage.LangLink link : pages.get(0).langLinks()) {
|
||||||
|
PageTitle title = new PageTitle(link.title(), WikiSite.forLanguageCode(link.lang()));
|
||||||
|
result.add(title);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<NearbyPage> nearbyPages(@NonNull WikiSite wiki) {
|
||||||
|
List<NearbyPage> result = new ArrayList<>();
|
||||||
|
if (pages != null) {
|
||||||
|
for (MwQueryPage page : pages) {
|
||||||
|
NearbyPage nearbyPage = new NearbyPage(page, wiki);
|
||||||
|
if (nearbyPage.getLocation() != null) {
|
||||||
|
result.add(nearbyPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public SiteInfo siteInfo() {
|
||||||
|
return generalSiteInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public EditorTaskCounts editorTaskCounts() {
|
||||||
|
return editorTaskCounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcess() {
|
||||||
|
resolveConvertedTitles();
|
||||||
|
resolveRedirectedTitles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveRedirectedTitles() {
|
||||||
|
if (redirects == null || pages == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (MwQueryPage page : pages) {
|
||||||
|
for (MwQueryResult.Redirect redirect : redirects) {
|
||||||
|
// TODO: Looks like result pages and redirects can also be matched on the "index"
|
||||||
|
// property. Confirm in the API docs and consider updating.
|
||||||
|
if (page.title().equals(redirect.to())) {
|
||||||
|
page.redirectFrom(redirect.from());
|
||||||
|
if (redirect.toFragment() != null) {
|
||||||
|
page.appendTitleFragment(redirect.toFragment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveConvertedTitles() {
|
||||||
|
if (converted == null || pages == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
for (MwQueryResult.ConvertedTitle convertedTitle : converted) {
|
||||||
|
// noinspection ConstantConditions
|
||||||
|
for (MwQueryPage page : pages) {
|
||||||
|
if (page.title().equals(convertedTitle.to())) {
|
||||||
|
page.convertedFrom(convertedTitle.from());
|
||||||
|
page.convertedTo(convertedTitle.to());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<MwQueryLogEvent> logevents() {
|
||||||
|
return logevents;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Redirect {
|
||||||
|
@SuppressWarnings("unused") private int index;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String from;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String to;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("tofragment") @Nullable private String toFragment;
|
||||||
|
|
||||||
|
@Nullable public String to() {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String from() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String toFragment() {
|
||||||
|
return toFragment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ConvertedTitle {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String from;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String to;
|
||||||
|
|
||||||
|
@Nullable public String to() {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String from() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Tokens {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("csrftoken")
|
||||||
|
@Nullable private String csrf;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("createaccounttoken")
|
||||||
|
@Nullable private String createAccount;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("logintoken")
|
||||||
|
@Nullable private String login;
|
||||||
|
|
||||||
|
@Nullable private String csrf() {
|
||||||
|
return csrf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable private String createAccount() {
|
||||||
|
return createAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable private String login() {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MarkReadResponse {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String result;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String timestamp;
|
||||||
|
|
||||||
|
@Nullable public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class NotificationList {
|
||||||
|
@SuppressWarnings("unused") private int count;
|
||||||
|
@SuppressWarnings("unused") private int rawcount;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Notification.SeenTime seenTime;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Notification> list;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("continue") @Nullable private String continueStr;
|
||||||
|
|
||||||
|
@Nullable public List<Notification> list() {
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getContinue() {
|
||||||
|
return continueStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Notification.SeenTime getSeenTime() {
|
||||||
|
return seenTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.json.PostProcessingTypeAdapter;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public abstract class MwResponse extends BaseModel implements PostProcessingTypeAdapter.PostProcessable {
|
||||||
|
@SuppressWarnings({"unused"}) @Nullable private List<MwServiceError> errors;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("servedby") @NonNull private String servedBy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcess() {
|
||||||
|
if (errors != null && !errors.isEmpty()) {
|
||||||
|
throw new MwException(errors.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.ServiceError;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for a MediaWiki API error.
|
||||||
|
*/
|
||||||
|
public class MwServiceError extends BaseModel implements ServiceError {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String code;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String text;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Data data;
|
||||||
|
|
||||||
|
@Override @NonNull public String getTitle() {
|
||||||
|
return StringUtils.defaultString(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull public String getDetails() {
|
||||||
|
return StringUtils.defaultString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean badToken() {
|
||||||
|
return "badtoken".equals(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean badLoginState() {
|
||||||
|
return "assertuserfailed".equals(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMessageName(@NonNull String messageName) {
|
||||||
|
if (data != null && data.messages() != null) {
|
||||||
|
for (Message msg : data.messages()) {
|
||||||
|
if (messageName.equals(msg.name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getMessageHtml(@NonNull String messageName) {
|
||||||
|
if (data != null && data.messages() != null) {
|
||||||
|
for (Message msg : data.messages()) {
|
||||||
|
if (messageName.equals(msg.name)) {
|
||||||
|
return msg.html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Data {
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Message> messages;
|
||||||
|
|
||||||
|
@Nullable private List<Message> messages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Message {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String name;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String html;
|
||||||
|
|
||||||
|
@NonNull private String html() {
|
||||||
|
return StringUtils.defaultString(html);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NearbyPage {
|
||||||
|
@NonNull private PageTitle title;
|
||||||
|
@Nullable private Location location;
|
||||||
|
|
||||||
|
/** calculated externally */
|
||||||
|
private int distance;
|
||||||
|
|
||||||
|
public NearbyPage(@NonNull MwQueryPage page, @NonNull WikiSite wiki) {
|
||||||
|
title = new PageTitle(page.title(), wiki);
|
||||||
|
title.setThumbUrl(page.thumbUrl());
|
||||||
|
List<MwQueryPage.Coordinates> coordinates = page.coordinates();
|
||||||
|
if (coordinates == null || coordinates.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (coordinates.get(0).lat() != null && coordinates.get(0).lon() != null) {
|
||||||
|
location = new Location(title.getPrefixedText());
|
||||||
|
location.setLatitude(coordinates.get(0).lat());
|
||||||
|
location.setLongitude(coordinates.get(0).lon());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public NearbyPage(@NonNull PageTitle title, @Nullable Location location) {
|
||||||
|
this.title = title;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public PageTitle getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Location getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
|
return "NearbyPage{"
|
||||||
|
+ "title='" + title + '\''
|
||||||
|
+ ", thumbUrl='" + title.getThumbUrl() + '\''
|
||||||
|
+ ", location=" + location + '\''
|
||||||
|
+ ", distance='" + distance
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the distance from the point where the device is.
|
||||||
|
* Calculated later and can change. Needs to be set first by #setDistance!
|
||||||
|
*/
|
||||||
|
public int getDistance() {
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDistance(int distance) {
|
||||||
|
this.distance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class RecentChange {
|
||||||
|
@Nullable private String type;
|
||||||
|
@Nullable private String title;
|
||||||
|
private long pageid;
|
||||||
|
private long revid;
|
||||||
|
@SerializedName("old_revid") private long oldRevisionId;
|
||||||
|
@Nullable private String timestamp;
|
||||||
|
|
||||||
|
@NonNull public String getType() {
|
||||||
|
return StringUtils.defaultString(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getTitle() {
|
||||||
|
return StringUtils.defaultString(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPageId() {
|
||||||
|
return pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getRevId() {
|
||||||
|
return revid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getOldRevisionId() {
|
||||||
|
return oldRevisionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTimestamp() {
|
||||||
|
return StringUtils.defaultString(timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import org.wikipedia.json.GsonUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SiteMatrix extends MwResponse {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private JsonObject sitematrix;
|
||||||
|
|
||||||
|
public JsonObject siteMatrix() {
|
||||||
|
return sitematrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused,NullableProblems")
|
||||||
|
public class SiteInfo {
|
||||||
|
@NonNull
|
||||||
|
private String code;
|
||||||
|
@NonNull
|
||||||
|
private String name;
|
||||||
|
@NonNull
|
||||||
|
private String localname;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String localName() {
|
||||||
|
return localname;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<SiteInfo> getSites(@NonNull SiteMatrix siteMatrix) {
|
||||||
|
List<SiteInfo> sites = new ArrayList<>();
|
||||||
|
// We have to parse the Json manually because the list of SiteInfo objects
|
||||||
|
// contains a "count" member that prevents it from being able to deserialize
|
||||||
|
// as a list automatically.
|
||||||
|
for (String key : siteMatrix.siteMatrix().keySet()) {
|
||||||
|
if (key.equals("count")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SiteInfo info = GsonUtil.getDefaultGson().fromJson(siteMatrix.siteMatrix().get(key), SiteInfo.class);
|
||||||
|
if (info != null) {
|
||||||
|
sites.add(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sites;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class UserInfo {
|
||||||
|
@NonNull
|
||||||
|
private String name;
|
||||||
|
@NonNull
|
||||||
|
private int id;
|
||||||
|
|
||||||
|
//Block information
|
||||||
|
private int blockid;
|
||||||
|
private String blockedby;
|
||||||
|
private int blockedbyid;
|
||||||
|
private String blockreason;
|
||||||
|
private String blocktimestamp;
|
||||||
|
private String blockexpiry;
|
||||||
|
|
||||||
|
// Object type is any JSON type.
|
||||||
|
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||||
|
@Nullable
|
||||||
|
private Map<String, ?> options;
|
||||||
|
|
||||||
|
public int id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Map<String, String> userjsOptions() {
|
||||||
|
Map<String, String> map = new HashMap<>();
|
||||||
|
if (options != null) {
|
||||||
|
for (Map.Entry<String, ?> entry : options.entrySet()) {
|
||||||
|
if (entry.getKey().startsWith("userjs-")) {
|
||||||
|
// T161866 entry.valueOf() should always return a String but doesn't
|
||||||
|
map.put(entry.getKey(), entry.getValue() == null ? "" : String.valueOf(entry.getValue()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public int blockid() {
|
||||||
|
return blockid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String blockedby() {
|
||||||
|
if (blockedby != null)
|
||||||
|
return blockedby;
|
||||||
|
else return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public int blockedbyid() {
|
||||||
|
return blockedbyid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String blockreason() {
|
||||||
|
if (blockreason != null)
|
||||||
|
return blockreason;
|
||||||
|
else return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String blocktimestamp() {
|
||||||
|
if (blocktimestamp != null)
|
||||||
|
return blocktimestamp;
|
||||||
|
else return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String blockexpiry() {
|
||||||
|
if (blockexpiry != null)
|
||||||
|
return blockexpiry;
|
||||||
|
else return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,281 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi.page;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryPage;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwResponse;
|
||||||
|
import org.wikipedia.dataclient.page.PageLead;
|
||||||
|
import org.wikipedia.dataclient.page.PageLeadProperties;
|
||||||
|
import org.wikipedia.dataclient.page.Protection;
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
import org.wikipedia.page.Page;
|
||||||
|
import org.wikipedia.page.PageProperties;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
import org.wikipedia.util.StringUtil;
|
||||||
|
import org.wikipedia.util.UriUtil;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.wikipedia.dataclient.Service.PREFERRED_THUMB_SIZE;
|
||||||
|
import static org.wikipedia.util.ImageUrlUtil.getUrlForSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for loading the first stage of page content.
|
||||||
|
*/
|
||||||
|
public class MwMobileViewPageLead extends MwResponse implements PageLead {
|
||||||
|
@SuppressWarnings("unused") private Mobileview mobileview;
|
||||||
|
|
||||||
|
/** Note: before using this check that #getMobileview != null */
|
||||||
|
@Override
|
||||||
|
public Page toPage(@NonNull PageTitle title) {
|
||||||
|
return new Page(adjustPageTitle(title, title.getPrefixedText()),
|
||||||
|
mobileview.getSections(),
|
||||||
|
mobileview.toPageProperties(),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PageTitle adjustPageTitle(@NonNull PageTitle title, @NonNull String originalPrefixedText) {
|
||||||
|
if (mobileview.getRedirected() != null) {
|
||||||
|
// Handle redirects properly.
|
||||||
|
title = new PageTitle(mobileview.getRedirected(), title.getWikiSite(),
|
||||||
|
title.getThumbUrl());
|
||||||
|
} else if (mobileview.getNormalizedTitle() != null) {
|
||||||
|
// We care about the normalized title only if we were not redirected
|
||||||
|
title = new PageTitle(mobileview.getNormalizedTitle(), title.getWikiSite(),
|
||||||
|
title.getThumbUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobileview.getDisplayTitle() != null
|
||||||
|
&& !StringUtil.removeHTMLTags(title.getDisplayText()).equals(StringUtil.removeHTMLTags(mobileview.getDisplayTitle()))) {
|
||||||
|
title = new PageTitle(StringUtil.removeHTMLTags(mobileview.getDisplayTitle()), title.getWikiSite(),
|
||||||
|
title.getThumbUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobileview.getDisplayTitle() != null
|
||||||
|
&& !mobileview.getDisplayTitle().equals(originalPrefixedText)
|
||||||
|
&& mobileview.getNormalizedTitle() == null) {
|
||||||
|
// Sometimes the MW api will not give us the "converted" or "redirected" title if switching between Chinese variants
|
||||||
|
// Ticket: https://phabricator.wikimedia.org/T206891#4672777
|
||||||
|
// We can the original prefixed title text (the one we used for calling API) to build the PageTitle
|
||||||
|
title = new PageTitle(originalPrefixedText, title.getWikiSite(), title.getThumbUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mobileview.getRedirected() != null) {
|
||||||
|
title.setConvertedText(mobileview.getRedirected());
|
||||||
|
}
|
||||||
|
|
||||||
|
title.setDescription(mobileview.getDescription());
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull public String getLeadSectionContent() {
|
||||||
|
if (mobileview != null) {
|
||||||
|
return mobileview.getSections().get(0).getContent();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getTitlePronunciationUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable @Override public String getLeadImageUrl(int leadImageWidth) {
|
||||||
|
return mobileview == null ? null : mobileview.getLeadImageUrl(leadImageWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable @Override public String getThumbUrl() {
|
||||||
|
return mobileview == null ? null : mobileview.getThumbUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable @Override public String getDescription() {
|
||||||
|
return mobileview == null ? null : mobileview.getDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Location getGeo() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public Mobileview getMobileview() {
|
||||||
|
return mobileview;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Almost everything is in this inner class.
|
||||||
|
*/
|
||||||
|
public static class Mobileview implements PageLeadProperties {
|
||||||
|
@SuppressWarnings("unused") private int id;
|
||||||
|
@SuppressWarnings("unused") private int namespace;
|
||||||
|
@SuppressWarnings("unused") private long revision;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String lastmodified;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String displaytitle;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String redirected;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String normalizedtitle;
|
||||||
|
@SuppressWarnings("unused") private int languagecount;
|
||||||
|
@SuppressWarnings("unused") private boolean editable;
|
||||||
|
@SuppressWarnings("unused") private boolean mainpage;
|
||||||
|
@SuppressWarnings("unused") private boolean disambiguation;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String description;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String descriptionsource;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("image") @Nullable private PageImage pageImage;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("thumb") @Nullable private PageImageThumb leadImage;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Protection protection;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Section> sections;
|
||||||
|
@SuppressWarnings("unused") @Nullable private MwQueryPage.PageProps pageprops;
|
||||||
|
|
||||||
|
/** Converter */
|
||||||
|
public PageProperties toPageProperties() {
|
||||||
|
return new PageProperties(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull public Namespace getNamespace() {
|
||||||
|
return Namespace.of(namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLastModified() {
|
||||||
|
return lastmodified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLanguageCount() {
|
||||||
|
return languagecount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getDisplayTitle() {
|
||||||
|
return displaytitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getTitlePronunciationUrl() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Location getGeo() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getRedirected() {
|
||||||
|
return redirected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getNormalizedTitle() {
|
||||||
|
return normalizedtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLeadImageUrl(int leadImageWidth) {
|
||||||
|
return leadImage != null ? leadImage.getUrl() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getThumbUrl() {
|
||||||
|
return leadImage != null ? UriUtil.resolveProtocolRelativeUrl(getUrlForSize(leadImage.getUrl(), PREFERRED_THUMB_SIZE)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLeadImageFileName() {
|
||||||
|
return pageImage != null ? pageImage.getFileName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getWikiBaseItem() {
|
||||||
|
return pageprops != null && pageprops.getWikiBaseItem() != null ? pageprops.getWikiBaseItem() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getDescriptionSource() {
|
||||||
|
return descriptionsource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getFirstAllowedEditorRole() {
|
||||||
|
return protection != null ? protection.getFirstAllowedEditorRole() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable() {
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMainPage() {
|
||||||
|
return mainpage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisambiguation() {
|
||||||
|
return disambiguation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull public List<Section> getSections() {
|
||||||
|
return sections == null ? Collections.emptyList() : sections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the lead image File: page name
|
||||||
|
*/
|
||||||
|
public static class PageImage {
|
||||||
|
@SuppressWarnings("unused") @SerializedName("file") private String fileName;
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the lead image URL
|
||||||
|
*/
|
||||||
|
public static class PageImageThumb {
|
||||||
|
@SuppressWarnings("unused") private String url;
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.page.PageRemaining;
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for loading remaining page content.
|
||||||
|
*/
|
||||||
|
public class MwMobileViewPageRemaining implements PageRemaining {
|
||||||
|
@SuppressWarnings("unused") @Nullable private MwMobileViewPageLead.Mobileview mobileview;
|
||||||
|
|
||||||
|
@NonNull @Override public List<Section> sections() {
|
||||||
|
return mobileview == null ? Collections.emptyList() : mobileview.getSections();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.ServiceFactory;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.dataclient.page.PageClient;
|
||||||
|
import org.wikipedia.dataclient.page.PageSummary;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import okhttp3.CacheControl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrofit web service client for MediaWiki PHP API.
|
||||||
|
*/
|
||||||
|
public class MwPageClient implements PageClient {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<? extends PageSummary> summary(@NonNull WikiSite wiki, @NonNull String title, @Nullable String referrerUrl) {
|
||||||
|
return ServiceFactory.get(wiki).getSummary(referrerUrl, title, wiki.languageCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<Response<MwMobileViewPageLead>> lead(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@Nullable String referrerUrl,
|
||||||
|
@NonNull String title,
|
||||||
|
int leadImageWidth) {
|
||||||
|
return ServiceFactory.get(wiki).getLeadSection(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, referrerUrl, title, leadImageWidth, wiki.languageCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<Response<MwMobileViewPageRemaining>> sections(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title) {
|
||||||
|
return ServiceFactory.get(wiki).getRemainingSections(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, title, wiki.languageCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Request sectionsUrl(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title) {
|
||||||
|
return ServiceFactory.get(wiki).getRemainingSectionsUrl(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, title, wiki.languageCode()).request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
package org.wikipedia.dataclient.mwapi.page;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
import org.wikipedia.dataclient.page.PageSummary;
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Useful for link previews coming from MW API.
|
||||||
|
*/
|
||||||
|
public class MwQueryPageSummary extends MwQueryResponse implements PageSummary {
|
||||||
|
@Override @Nullable public String getTitle() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return query().firstPage().title();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable public String getDisplayTitle() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (query().firstPage().pageProps() != null && !TextUtils.isEmpty(query().firstPage().pageProps().getDisplayTitle()))
|
||||||
|
? query().firstPage().pageProps().getDisplayTitle() : query().firstPage().title();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable public String getConvertedTitle() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (query().firstPage().convertedTo() != null && !TextUtils.isEmpty(query().firstPage().convertedTo()))
|
||||||
|
? query().firstPage().convertedTo() : query().firstPage().title();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getExtract() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return query().firstPage().extract();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getExtractHtml() {
|
||||||
|
return getExtract();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return query().firstPage().thumbUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public Namespace getNamespace() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return Namespace.MAIN;
|
||||||
|
}
|
||||||
|
return query().firstPage().namespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull @Override
|
||||||
|
public String getType() {
|
||||||
|
if (query() != null && query().firstPage() != null && query().firstPage().pageProps() != null
|
||||||
|
&& query().firstPage().pageProps().isDisambiguation()) {
|
||||||
|
return TYPE_DISAMBIGUATION;
|
||||||
|
}
|
||||||
|
return TYPE_STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getPageId() {
|
||||||
|
if (query() == null || query().firstPage() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return query().firstPage().pageId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
package org.wikipedia.dataclient.okhttp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a subclass of InputStream that implements the available() method reliably enough
|
||||||
|
* to satisfy WebResourceResponses or other consumers like BufferedInputStream that depend
|
||||||
|
* on available() to return a meaningful value.
|
||||||
|
*
|
||||||
|
* The problem is that the InputStream provided by OkHttp's body().byteStream() returns zero
|
||||||
|
* when calling available() prior to making any read() calls, which means that it will break
|
||||||
|
* any consumers that wrap a BufferedInputStream onto this stream, or any other wrapper that
|
||||||
|
* relies on a consistent implementation of available().
|
||||||
|
*
|
||||||
|
* This is initialized with the original InputStream plus its total size, which must be known
|
||||||
|
* at the time of instantiation. You may then call the read() and skip() methods in the usual
|
||||||
|
* way, and then be able to call available() and get the number of bytes left to read.
|
||||||
|
*/
|
||||||
|
public class AvailableInputStream extends InputStream {
|
||||||
|
private InputStream stream;
|
||||||
|
private long available;
|
||||||
|
|
||||||
|
public AvailableInputStream(InputStream stream, long available) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.available = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read() throws IOException {
|
||||||
|
decreaseAvailable(1);
|
||||||
|
return stream.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read(@NonNull byte[] b) throws IOException {
|
||||||
|
int ret = stream.read(b);
|
||||||
|
if (ret > 0) {
|
||||||
|
decreaseAvailable(ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int read(@NonNull byte[] b, int off, int len) throws IOException {
|
||||||
|
int ret = stream.read(b, off, len);
|
||||||
|
if (ret > 0) {
|
||||||
|
decreaseAvailable(ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public long skip(long n) throws IOException {
|
||||||
|
long ret = stream.skip(n);
|
||||||
|
if (ret > 0) {
|
||||||
|
decreaseAvailable(ret);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int available() throws IOException {
|
||||||
|
int ret = stream.available();
|
||||||
|
if (ret == 0 && available > 0) {
|
||||||
|
return (int) available;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decreaseAvailable(long n) {
|
||||||
|
available -= n;
|
||||||
|
if (available < 0) {
|
||||||
|
available = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package org.wikipedia.dataclient.okhttp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.ServiceError;
|
||||||
|
import org.wikipedia.dataclient.restbase.RbServiceError;
|
||||||
|
import org.wikipedia.util.log.L;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class HttpStatusException extends IOException {
|
||||||
|
private final int code;
|
||||||
|
private final String url;
|
||||||
|
@Nullable private ServiceError serviceError;
|
||||||
|
|
||||||
|
public HttpStatusException(@NonNull Response rsp) {
|
||||||
|
this.code = rsp.code();
|
||||||
|
this.url = rsp.request().url().uri().toString();
|
||||||
|
try {
|
||||||
|
if (rsp.body() != null && rsp.body().contentType() != null
|
||||||
|
&& rsp.body().contentType().toString().contains("json")) {
|
||||||
|
serviceError = RbServiceError.create(rsp.body().string());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
L.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpStatusException(@Nullable ServiceError error) {
|
||||||
|
serviceError = error;
|
||||||
|
code = 0;
|
||||||
|
url = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public int code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceError serviceError() {
|
||||||
|
return serviceError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
String str = "Code: " + Integer.toString(code) + ", URL: " + url;
|
||||||
|
if (serviceError != null) {
|
||||||
|
str += ", title: " + serviceError.getTitle() + ", detail: " + serviceError.getDetails();
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.wikipedia.dataclient.okhttp
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class TestStubInterceptor : Interceptor {
|
||||||
|
interface Callback {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun getResponse(request: Interceptor.Chain): Response
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
return if (CALLBACK != null) {
|
||||||
|
CALLBACK!!.getResponse(chain)
|
||||||
|
} else chain.proceed(chain.request())
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var CALLBACK: Callback? = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.wikipedia.dataclient.okhttp
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class UnsuccessfulResponseInterceptor : Interceptor {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val rsp = chain.proceed(chain.request())
|
||||||
|
if (rsp.isSuccessful) {
|
||||||
|
return rsp
|
||||||
|
}
|
||||||
|
throw HttpStatusException(rsp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.wikipedia.dataclient.okhttp.util;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
public final class HttpUrlUtil {
|
||||||
|
private static final List<String> RESTBASE_SEGMENT_IDENTIFIERS = Arrays.asList("rest_v1", "v1");
|
||||||
|
|
||||||
|
public static boolean isRestBase(@NonNull HttpUrl url) {
|
||||||
|
return !Collections.disjoint(url.encodedPathSegments(), RESTBASE_SEGMENT_IDENTIFIERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isMobileView(@NonNull HttpUrl url) {
|
||||||
|
return "mobileview".equals(url.queryParameter("action"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpUrlUtil() { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import okhttp3.CacheControl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic interface for Page content service.
|
||||||
|
* Usually we would use direct Retrofit Callbacks here but since we have two ways of
|
||||||
|
* getting to the data (MW API and RESTBase) we add this layer of indirection -- until we drop one.
|
||||||
|
*/
|
||||||
|
public interface PageClient {
|
||||||
|
/**
|
||||||
|
* Gets a page summary for a given title -- for link previews
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@NonNull <T extends PageSummary> Observable<T> summary(@NonNull WikiSite wiki,
|
||||||
|
@NonNull String title,
|
||||||
|
@Nullable String referrerUrl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the lead section and initial metadata of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title with prefix if necessary
|
||||||
|
* @param leadThumbnailWidth one of the bucket widths for the lead image
|
||||||
|
*/
|
||||||
|
@NonNull <T extends PageLead> Observable<Response<T>> lead(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@Nullable String referrerUrl,
|
||||||
|
@NonNull String title,
|
||||||
|
int leadThumbnailWidth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remaining sections of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@NonNull <T extends PageRemaining> Observable<Response<T>> sections(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the remaining sections request url of a given title.
|
||||||
|
*
|
||||||
|
* @param title the page title to be used including prefix
|
||||||
|
*/
|
||||||
|
@NonNull Request sectionsUrl(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.page.Page;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJI for loading the first stage of page content.
|
||||||
|
*/
|
||||||
|
public interface PageLead {
|
||||||
|
/** Note: before using this check that #hasError is false */
|
||||||
|
Page toPage(PageTitle title);
|
||||||
|
|
||||||
|
@NonNull String getLeadSectionContent();
|
||||||
|
|
||||||
|
@Nullable String getTitlePronunciationUrl();
|
||||||
|
@Nullable String getLeadImageUrl(int leadImageWidth);
|
||||||
|
@Nullable String getThumbUrl();
|
||||||
|
@Nullable String getDescription();
|
||||||
|
|
||||||
|
@Nullable Location getGeo();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main properties of a page
|
||||||
|
*/
|
||||||
|
public interface PageLeadProperties {
|
||||||
|
|
||||||
|
int getId();
|
||||||
|
|
||||||
|
@NonNull Namespace getNamespace();
|
||||||
|
|
||||||
|
long getRevision();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getLastModified();
|
||||||
|
|
||||||
|
int getLanguageCount();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getDisplayTitle();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getTitlePronunciationUrl();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Location getGeo();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getRedirected();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getNormalizedTitle();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getWikiBaseItem();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getDescriptionSource();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Nullable URL with no scheme. For example, foo.bar.com/ instead of
|
||||||
|
* http://foo.bar.com/.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
String getLeadImageUrl(int leadImageWidth);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getThumbUrl();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getLeadImageFileName();
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String getFirstAllowedEditorRole();
|
||||||
|
|
||||||
|
boolean isEditable();
|
||||||
|
|
||||||
|
boolean isMainPage();
|
||||||
|
|
||||||
|
boolean isDisambiguation();
|
||||||
|
|
||||||
|
@NonNull List<Section> getSections();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJI for loading remaining page content.
|
||||||
|
*/
|
||||||
|
public interface PageRemaining {
|
||||||
|
@NonNull List<Section> sections();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a summary of a page, useful for page previews.
|
||||||
|
*/
|
||||||
|
public interface PageSummary {
|
||||||
|
String TYPE_STANDARD = "standard";
|
||||||
|
String TYPE_DISAMBIGUATION = "disambiguation";
|
||||||
|
String TYPE_MAIN_PAGE = "mainpage";
|
||||||
|
String TYPE_NO_EXTRACT = "no-extract";
|
||||||
|
|
||||||
|
@NonNull String getType();
|
||||||
|
@Nullable String getTitle();
|
||||||
|
@Nullable String getDisplayTitle();
|
||||||
|
@Nullable String getConvertedTitle();
|
||||||
|
@Nullable String getExtract();
|
||||||
|
@Nullable String getExtractHtml();
|
||||||
|
@Nullable String getThumbnailUrl();
|
||||||
|
@NonNull Namespace getNamespace();
|
||||||
|
int getPageId();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package org.wikipedia.dataclient.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/** Protection settings for a page */
|
||||||
|
public class Protection {
|
||||||
|
@SuppressWarnings("MismatchedReadAndWriteOfArray") @NonNull private Set<String> edit = Collections.emptySet();
|
||||||
|
|
||||||
|
// TODO should send them all, but callers need to be updated, too, (future patch)
|
||||||
|
@Nullable
|
||||||
|
public String getFirstAllowedEditorRole() {
|
||||||
|
return edit.isEmpty() ? null : edit.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Set<String> getEditRoles() {
|
||||||
|
return Collections.unmodifiableSet(edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.wikipedia.dataclient.restbase;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class RbDefinition {
|
||||||
|
@Required @NonNull private Map<String, Usage[]> usagesByLang;
|
||||||
|
|
||||||
|
public RbDefinition(@NonNull Map<String, RbDefinition.Usage[]> usages) {
|
||||||
|
usagesByLang = usages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Usage[] getUsagesForLang(String langCode) {
|
||||||
|
return usagesByLang.get(langCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Usage {
|
||||||
|
@Required @NonNull private String partOfSpeech;
|
||||||
|
@Required @NonNull private Definition[] definitions;
|
||||||
|
|
||||||
|
public Usage(@NonNull String partOfSpeech, @NonNull Definition[] definitions) {
|
||||||
|
this.partOfSpeech = partOfSpeech;
|
||||||
|
this.definitions = definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getPartOfSpeech() {
|
||||||
|
return partOfSpeech;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public Definition[] getDefinitions() {
|
||||||
|
return definitions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Definition {
|
||||||
|
@Required @NonNull private String definition;
|
||||||
|
@Nullable private String[] examples;
|
||||||
|
|
||||||
|
public Definition(@NonNull String definition, @Nullable String[] examples) {
|
||||||
|
this.definition = definition;
|
||||||
|
this.examples = examples;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getDefinition() {
|
||||||
|
return definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String[] getExamples() {
|
||||||
|
return examples;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package org.wikipedia.dataclient.restbase;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class RbRelatedPages {
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<RbPageSummary> pages;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<RbPageSummary> getPages() {
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<RbPageSummary> getPages(int limit) {
|
||||||
|
List<RbPageSummary> list = new ArrayList<>();
|
||||||
|
if (getPages() != null) {
|
||||||
|
for (RbPageSummary page : getPages()) {
|
||||||
|
list.add(page);
|
||||||
|
if (limit == list.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.wikipedia.dataclient.restbase;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.ServiceError;
|
||||||
|
import org.wikipedia.json.GsonUnmarshaller;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for a RESTBase API error.
|
||||||
|
*/
|
||||||
|
public class RbServiceError extends BaseModel implements ServiceError {
|
||||||
|
@SuppressWarnings("unused") private String type;
|
||||||
|
@SuppressWarnings("unused") private String title;
|
||||||
|
@SuppressWarnings("unused") private String detail;
|
||||||
|
@SuppressWarnings("unused") private String method;
|
||||||
|
@SuppressWarnings("unused") private String uri;
|
||||||
|
|
||||||
|
public static RbServiceError create(@NonNull String rspBody) {
|
||||||
|
return GsonUnmarshaller.unmarshal(RbServiceError.class, rspBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String getTitle() {
|
||||||
|
return StringUtils.defaultString(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public String getDetails() {
|
||||||
|
return StringUtils.defaultString(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.wikipedia.dataclient.restbase.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.ServiceFactory;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.dataclient.page.PageClient;
|
||||||
|
import org.wikipedia.dataclient.page.PageSummary;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import okhttp3.CacheControl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
// todo: consolidate with MwPageClient or just use the Services directly!
|
||||||
|
/**
|
||||||
|
* Retrofit web service client for RESTBase Nodejs API.
|
||||||
|
*/
|
||||||
|
public class RbPageClient implements PageClient {
|
||||||
|
|
||||||
|
// todo: RbPageSummary should specify an @Required annotation that throws a JsonParseException
|
||||||
|
// when the body is null rather than requiring all clients to check for a null body. There
|
||||||
|
// may be some abandoned demo patches that already have this functionality. It should be
|
||||||
|
// part of the Gson augmentation package and eventually cut into a separate lib. Repeat
|
||||||
|
// everywhere a Response.body() == null check occurs that throws
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<? extends PageSummary> summary(@NonNull WikiSite wiki, @NonNull String title, @Nullable String referrerUrl) {
|
||||||
|
return ServiceFactory.getRest(wiki).getSummary(referrerUrl, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<Response<RbPageLead>> lead(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@Nullable String referrerUrl,
|
||||||
|
@NonNull String title,
|
||||||
|
int leadThumbnailWidth) {
|
||||||
|
return ServiceFactory.getRest(wiki).getLeadSection(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, referrerUrl, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Observable<Response<RbPageRemaining>> sections(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title) {
|
||||||
|
return ServiceFactory.getRest(wiki).getRemainingSections(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@NonNull @Override public Request sectionsUrl(@NonNull WikiSite wiki,
|
||||||
|
@Nullable CacheControl cacheControl,
|
||||||
|
@Nullable String saveOfflineHeader,
|
||||||
|
@NonNull String title) {
|
||||||
|
return ServiceFactory.getRest(wiki).getRemainingSectionsUrl(cacheControl == null ? null : cacheControl.toString(),
|
||||||
|
saveOfflineHeader, title).request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
package org.wikipedia.dataclient.restbase.page;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.JsonAdapter;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.page.PageLead;
|
||||||
|
import org.wikipedia.dataclient.page.PageLeadProperties;
|
||||||
|
import org.wikipedia.dataclient.page.Protection;
|
||||||
|
import org.wikipedia.page.GeoTypeAdapter;
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
import org.wikipedia.page.Page;
|
||||||
|
import org.wikipedia.page.PageProperties;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
import org.wikipedia.util.UriUtil;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.wikipedia.dataclient.Service.PREFERRED_THUMB_SIZE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for loading the first stage of page content.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class RbPageLead implements PageLead, PageLeadProperties {
|
||||||
|
private int ns;
|
||||||
|
private int id;
|
||||||
|
private long revision;
|
||||||
|
@Nullable private String lastmodified;
|
||||||
|
@Nullable private String displaytitle;
|
||||||
|
@Nullable private String redirected;
|
||||||
|
@Nullable private String normalizedtitle;
|
||||||
|
@Nullable @SerializedName("wikibase_item") private String wikiBaseItem;
|
||||||
|
@Nullable @SerializedName("pronunciation") private TitlePronunciation titlePronunciation;
|
||||||
|
@Nullable @JsonAdapter(GeoTypeAdapter.class) private Location geo;
|
||||||
|
private int languagecount;
|
||||||
|
private boolean editable;
|
||||||
|
private boolean mainpage;
|
||||||
|
private boolean disambiguation;
|
||||||
|
@Nullable private String description;
|
||||||
|
@Nullable @SerializedName("description_source") private String descriptionSource;
|
||||||
|
@Nullable private Image image;
|
||||||
|
@Nullable private Protection protection;
|
||||||
|
@Nullable private List<Section> sections;
|
||||||
|
|
||||||
|
/** Note: before using this check that #getMobileview != null */
|
||||||
|
@Override
|
||||||
|
public Page toPage(PageTitle title) {
|
||||||
|
return new Page(adjustPageTitle(title),
|
||||||
|
getSections(),
|
||||||
|
toPageProperties(),
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
PageTitle adjustPageTitle(PageTitle title) {
|
||||||
|
if (redirected != null) {
|
||||||
|
// Handle redirects properly.
|
||||||
|
title = new PageTitle(redirected, title.getWikiSite(), title.getThumbUrl());
|
||||||
|
} else if (normalizedtitle != null) {
|
||||||
|
// We care about the normalized title only if we were not redirected
|
||||||
|
title = new PageTitle(normalizedtitle, title.getWikiSite(), title.getThumbUrl());
|
||||||
|
}
|
||||||
|
title.setDescription(description);
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getLeadSectionContent() {
|
||||||
|
if (sections != null) {
|
||||||
|
return sections.get(0).getContent();
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converter */
|
||||||
|
private PageProperties toPageProperties() {
|
||||||
|
return new PageProperties(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull @Override public Namespace getNamespace() {
|
||||||
|
return Namespace.of(ns);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLastModified() {
|
||||||
|
return lastmodified;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getTitlePronunciationUrl() {
|
||||||
|
return titlePronunciation == null
|
||||||
|
? null
|
||||||
|
: UriUtil.resolveProtocolRelativeUrl(titlePronunciation.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Location getGeo() {
|
||||||
|
return geo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLanguageCount() {
|
||||||
|
return languagecount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getDisplayTitle() {
|
||||||
|
return displaytitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getRedirected() {
|
||||||
|
return redirected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getNormalizedTitle() {
|
||||||
|
return normalizedtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getWikiBaseItem() {
|
||||||
|
return wikiBaseItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getDescriptionSource() {
|
||||||
|
return descriptionSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLeadImageUrl(int leadImageWidth) {
|
||||||
|
return image != null ? image.getUrl(leadImageWidth) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getThumbUrl() {
|
||||||
|
return image != null ? image.getUrl(PREFERRED_THUMB_SIZE) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getLeadImageFileName() {
|
||||||
|
return image != null ? image.getFileName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public String getFirstAllowedEditorRole() {
|
||||||
|
return protection != null ? protection.getFirstAllowedEditorRole() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEditable() {
|
||||||
|
return editable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getEditRoles() {
|
||||||
|
return protection != null ? protection.getEditRoles() : Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMainPage() {
|
||||||
|
return mainpage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisambiguation() {
|
||||||
|
return disambiguation;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull public List<Section> getSections() {
|
||||||
|
return sections == null ? Collections.emptyList() : sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the lead image File: page name
|
||||||
|
*/
|
||||||
|
public static class TitlePronunciation {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String url;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the lead image File: page name
|
||||||
|
*/
|
||||||
|
public static class Image {
|
||||||
|
@SuppressWarnings("unused") @SerializedName("file") private String fileName;
|
||||||
|
@SuppressWarnings("unused") private ThumbUrls urls;
|
||||||
|
|
||||||
|
public String getFileName() {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getUrl(int width) {
|
||||||
|
return urls != null ? urls.get(width) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the lead image URLs
|
||||||
|
*/
|
||||||
|
public static class ThumbUrls {
|
||||||
|
private static final int SMALL = 320;
|
||||||
|
private static final int MEDIUM = 640;
|
||||||
|
private static final int LARGE = 800;
|
||||||
|
private static final int XL = 1024;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("320") private String small;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("640") private String medium;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("800") private String large;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("1024") private String xl;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String get(int width) {
|
||||||
|
switch (width) {
|
||||||
|
case SMALL:
|
||||||
|
return small;
|
||||||
|
case MEDIUM:
|
||||||
|
return medium;
|
||||||
|
case LARGE:
|
||||||
|
return large;
|
||||||
|
case XL:
|
||||||
|
return xl;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.wikipedia.dataclient.restbase.page;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.page.PageRemaining;
|
||||||
|
import org.wikipedia.page.Section;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for loading remaining page content.
|
||||||
|
*/
|
||||||
|
public class RbPageRemaining implements PageRemaining {
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Section> sections;
|
||||||
|
|
||||||
|
@NonNull @Override public List<Section> sections() {
|
||||||
|
if (sections == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
package org.wikipedia.dataclient.restbase.page;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.dataclient.page.PageSummary;
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
import org.wikipedia.page.Namespace;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A standardized page summary object constructed by RESTBase, used for link previews and as the
|
||||||
|
* base class for various feed content (see the FeedPageSummary class).
|
||||||
|
*
|
||||||
|
* N.B.: The "title" field here sent by RESTBase is the *normalized* page title. However, in the
|
||||||
|
* FeedPageSummary subclass, "title" becomes the un-normalized, raw title, and the normalized title
|
||||||
|
* is sent as "normalizedtitle".
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class RbPageSummary implements PageSummary {
|
||||||
|
@Nullable private String type;
|
||||||
|
@SuppressWarnings("NullableProblems") @Required @NonNull private String title;
|
||||||
|
@Nullable private String normalizedtitle;
|
||||||
|
@SuppressWarnings("NullableProblems") @NonNull private String displaytitle;
|
||||||
|
@Nullable private NamespaceContainer namespace;
|
||||||
|
@Nullable private String extract;
|
||||||
|
@Nullable @SerializedName("extract_html") private String extractHtml;
|
||||||
|
@Nullable private String description;
|
||||||
|
@Nullable private Thumbnail thumbnail;
|
||||||
|
@Nullable @SerializedName("originalimage") private Thumbnail originalImage;
|
||||||
|
@Nullable private String lang;
|
||||||
|
private int pageid;
|
||||||
|
@Nullable @SerializedName("wikibase_item") private String wikiBaseItem;
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public String getDisplayTitle() {
|
||||||
|
return displaytitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public String getConvertedTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public Namespace getNamespace() {
|
||||||
|
return namespace == null ? Namespace.MAIN : Namespace.of(namespace.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @NonNull
|
||||||
|
public String getType() {
|
||||||
|
return TextUtils.isEmpty(type) ? TYPE_STANDARD : type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getExtract() {
|
||||||
|
return extract;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getExtractHtml() {
|
||||||
|
return extractHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Nullable
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnail == null ? null : thumbnail.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getNormalizedTitle() {
|
||||||
|
return normalizedtitle == null ? title : normalizedtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getOriginalImageUrl() {
|
||||||
|
return originalImage == null ? null : originalImage.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getWikiBaseItem() {
|
||||||
|
return wikiBaseItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PageTitle getPageTitle(@NonNull WikiSite wiki) {
|
||||||
|
return new PageTitle(getTitle(), wiki, getThumbnailUrl(), getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPageId() {
|
||||||
|
return pageid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLang() {
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the thumbnail URL of the page
|
||||||
|
*/
|
||||||
|
private static class Thumbnail {
|
||||||
|
@SuppressWarnings("unused") private String source;
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NamespaceContainer {
|
||||||
|
@SuppressWarnings("unused") private int id;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String text;
|
||||||
|
|
||||||
|
public int id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public String toString() {
|
||||||
|
return getTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.wikipedia.dataclient.retrofit;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is RetrofitError converted to Retrofit 2
|
||||||
|
*/
|
||||||
|
public class RetrofitException extends RuntimeException {
|
||||||
|
public static RetrofitException httpError(Response<?> response) {
|
||||||
|
return httpError(response.raw().request().url().toString(), response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RetrofitException httpError(String url, Response<?> response) {
|
||||||
|
String message = response.code() + " " + response.message();
|
||||||
|
return new RetrofitException(message, url, response.code(), Kind.HTTP, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RetrofitException httpError(@NonNull okhttp3.Response response) {
|
||||||
|
String message = response.code() + " " + response.message();
|
||||||
|
return new RetrofitException(message, response.request().url().toString(), response.code(), Kind.HTTP,
|
||||||
|
null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RetrofitException networkError(IOException exception) {
|
||||||
|
return new RetrofitException(exception.getMessage(), null, null, Kind.NETWORK, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RetrofitException unexpectedError(Throwable exception) {
|
||||||
|
return new RetrofitException(exception.getMessage(), null, null, Kind.UNEXPECTED, exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Identifies the event kind which triggered a {@link RetrofitException}. */
|
||||||
|
public enum Kind {
|
||||||
|
/** An {@link IOException} occurred while communicating to the server. */
|
||||||
|
NETWORK,
|
||||||
|
/** A non-200 HTTP status code was received from the server. */
|
||||||
|
HTTP,
|
||||||
|
/**
|
||||||
|
* An internal error occurred while attempting to execute a request. It is best practice to
|
||||||
|
* re-throw this exception so your application crashes.
|
||||||
|
*/
|
||||||
|
UNEXPECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
@Nullable private final Integer code;
|
||||||
|
private final Kind kind;
|
||||||
|
|
||||||
|
RetrofitException(String message, String url, @Nullable Integer code, Kind kind, Throwable exception) {
|
||||||
|
super(message, exception);
|
||||||
|
this.url = url;
|
||||||
|
this.code = code;
|
||||||
|
this.kind = kind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The request URL which produced the error. */
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HTTP status code. */
|
||||||
|
@Nullable public Integer getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The event kind which triggered this error. */
|
||||||
|
public Kind getKind() {
|
||||||
|
return kind;
|
||||||
|
}
|
||||||
|
}
|
||||||
79
data-client/src/main/java/org/wikipedia/edit/Edit.java
Normal file
79
data-client/src/main/java/org/wikipedia/edit/Edit.java
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwPostResponse;
|
||||||
|
|
||||||
|
public class Edit extends MwPostResponse {
|
||||||
|
@SuppressWarnings("unused,") @Nullable private Result edit;
|
||||||
|
|
||||||
|
@Nullable public Result edit() {
|
||||||
|
return edit;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasEditResult() {
|
||||||
|
return edit != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Result {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String result;
|
||||||
|
@SuppressWarnings("unused") private int newrevid;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Captcha captcha;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String code;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String info;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String warning;
|
||||||
|
@SuppressWarnings("unused") @Nullable private String spamblacklist;
|
||||||
|
|
||||||
|
@Nullable String status() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int newRevId() {
|
||||||
|
return newrevid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean editSucceeded() {
|
||||||
|
return "Success".equals(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String captchaId() {
|
||||||
|
return captcha == null ? null : captcha.id();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasEditErrorCode() {
|
||||||
|
return code != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasCaptchaResponse() {
|
||||||
|
return captcha != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String code() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String info() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String warning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String spamblacklist() {
|
||||||
|
return spamblacklist;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasSpamBlacklistResponse() {
|
||||||
|
return spamblacklist != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Captcha {
|
||||||
|
@SuppressWarnings("unused") @Nullable private String id;
|
||||||
|
|
||||||
|
@Nullable String id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class EditAbuseFilterResult extends EditResult {
|
||||||
|
static final int TYPE_WARNING = 1;
|
||||||
|
static final int TYPE_ERROR = 2;
|
||||||
|
|
||||||
|
@Nullable private final String code;
|
||||||
|
@Nullable private final String info;
|
||||||
|
@Nullable private final String warning;
|
||||||
|
|
||||||
|
EditAbuseFilterResult(@Nullable String code, @Nullable String info, @Nullable String warning) {
|
||||||
|
super("Failure");
|
||||||
|
this.code = code;
|
||||||
|
this.info = info;
|
||||||
|
this.warning = warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EditAbuseFilterResult(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
code = in.readString();
|
||||||
|
info = in.readString();
|
||||||
|
warning = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getInfo() {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String getWarning() {
|
||||||
|
return warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeString(code);
|
||||||
|
dest.writeString(info);
|
||||||
|
dest.writeString(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getType() {
|
||||||
|
if (code != null && code.startsWith("abusefilter-warning")) {
|
||||||
|
return TYPE_WARNING;
|
||||||
|
} else if (code != null && code.startsWith("abusefilter-disallowed")) {
|
||||||
|
return TYPE_ERROR;
|
||||||
|
} else if (info != null && info.startsWith("Hit AbuseFilter")) {
|
||||||
|
// This case is here because, unfortunately, an admin can create an abuse filter which
|
||||||
|
// emits an arbitrary error code over the API.
|
||||||
|
// TODO: More properly handle the case where the AbuseFilter throws an arbitrary error.
|
||||||
|
// Oh, and, you know, also fix the AbuseFilter API to not throw arbitrary error codes.
|
||||||
|
return TYPE_ERROR;
|
||||||
|
} else {
|
||||||
|
// We have no understanding of what kind of abuse filter response we got. It's safest
|
||||||
|
// to simply treat these as an error.
|
||||||
|
return TYPE_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<EditAbuseFilterResult> CREATOR
|
||||||
|
= new Parcelable.Creator<EditAbuseFilterResult>() {
|
||||||
|
@Override
|
||||||
|
public EditAbuseFilterResult createFromParcel(Parcel in) {
|
||||||
|
return new EditAbuseFilterResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EditAbuseFilterResult[] newArray(int size) {
|
||||||
|
return new EditAbuseFilterResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
72
data-client/src/main/java/org/wikipedia/edit/EditClient.java
Normal file
72
data-client/src/main/java/org/wikipedia/edit/EditClient.java
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.wikipedia.captcha.CaptchaResult;
|
||||||
|
import org.wikipedia.dataclient.Service;
|
||||||
|
import org.wikipedia.dataclient.ServiceFactory;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
import org.wikipedia.page.PageTitle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
public class EditClient {
|
||||||
|
public interface Callback {
|
||||||
|
void success(@NonNull Call<Edit> call, @NonNull EditResult result);
|
||||||
|
void failure(@NonNull Call<Edit> call, @NonNull Throwable caught);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
public Call<Edit> request(@NonNull WikiSite wiki, @NonNull PageTitle title, int section,
|
||||||
|
@NonNull String text, @NonNull String token, @NonNull String summary,
|
||||||
|
@Nullable String baseTimeStamp, boolean loggedIn, @Nullable String captchaId,
|
||||||
|
@Nullable String captchaWord, @NonNull Callback cb) {
|
||||||
|
return request(ServiceFactory.get(wiki), title, section, text, token, summary,
|
||||||
|
baseTimeStamp, loggedIn, captchaId, captchaWord, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting @SuppressWarnings("checkstyle:parameternumber")
|
||||||
|
Call<Edit> request(@NonNull Service service, @NonNull PageTitle title, int section,
|
||||||
|
@NonNull String text, @NonNull String token, @NonNull String summary,
|
||||||
|
@Nullable String baseTimeStamp, boolean loggedIn, @Nullable String captchaId,
|
||||||
|
@Nullable String captchaWord, @NonNull final Callback cb) {
|
||||||
|
Call<Edit> call = service.postEditSubmit(title.getPrefixedText(), section, summary, loggedIn ? "user" : null,
|
||||||
|
text, baseTimeStamp, token, captchaId, captchaWord);
|
||||||
|
call.enqueue(new retrofit2.Callback<Edit>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NonNull Call<Edit> call, @NonNull Response<Edit> response) {
|
||||||
|
if (response.body().hasEditResult()) {
|
||||||
|
handleEditResult(response.body().edit(), call, cb);
|
||||||
|
} else {
|
||||||
|
cb.failure(call, new IOException("An unknown error occurred."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull Call<Edit> call, @NonNull Throwable t) {
|
||||||
|
cb.failure(call, t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return call;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleEditResult(@NonNull Edit.Result result, @NonNull Call<Edit> call,
|
||||||
|
@NonNull Callback cb) {
|
||||||
|
if (result.editSucceeded()) {
|
||||||
|
cb.success(call, new EditSuccessResult(result.newRevId()));
|
||||||
|
} else if (result.hasEditErrorCode()) {
|
||||||
|
cb.success(call, new EditAbuseFilterResult(result.code(), result.info(), result.warning()));
|
||||||
|
} else if (result.hasSpamBlacklistResponse()) {
|
||||||
|
cb.success(call, new EditSpamBlacklistResult(result.spamblacklist()));
|
||||||
|
} else if (result.hasCaptchaResponse()) {
|
||||||
|
cb.success(call, new CaptchaResult(result.captchaId()));
|
||||||
|
} else {
|
||||||
|
cb.failure(call, new IOException("Received unrecognized edit response"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
data-client/src/main/java/org/wikipedia/edit/EditResult.java
Normal file
32
data-client/src/main/java/org/wikipedia/edit/EditResult.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
public abstract class EditResult extends BaseModel implements Parcelable {
|
||||||
|
private final String result;
|
||||||
|
|
||||||
|
public EditResult(String result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EditResult(Parcel in) {
|
||||||
|
this.result = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class EditSpamBlacklistResult extends EditResult {
|
||||||
|
private final String domain;
|
||||||
|
public EditSpamBlacklistResult(String domain) {
|
||||||
|
super("Failure");
|
||||||
|
this.domain = domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EditSpamBlacklistResult(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
domain = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDomain() {
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
super.writeToParcel(dest, flags);
|
||||||
|
dest.writeString(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<EditSpamBlacklistResult> CREATOR
|
||||||
|
= new Parcelable.Creator<EditSpamBlacklistResult>() {
|
||||||
|
@Override
|
||||||
|
public EditSpamBlacklistResult createFromParcel(Parcel in) {
|
||||||
|
return new EditSpamBlacklistResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EditSpamBlacklistResult[] newArray(int size) {
|
||||||
|
return new EditSpamBlacklistResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.wikipedia.edit;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public class EditSuccessResult extends EditResult {
|
||||||
|
private final int revID;
|
||||||
|
public EditSuccessResult(int revID) {
|
||||||
|
super("Success");
|
||||||
|
this.revID = revID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EditSuccessResult(Parcel in) {
|
||||||
|
super(in);
|
||||||
|
revID = in.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRevID() {
|
||||||
|
return revID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<EditSuccessResult> CREATOR
|
||||||
|
= new Parcelable.Creator<EditSuccessResult>() {
|
||||||
|
@Override
|
||||||
|
public EditSuccessResult createFromParcel(Parcel in) {
|
||||||
|
return new EditSuccessResult(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EditSuccessResult[] newArray(int size) {
|
||||||
|
return new EditSuccessResult[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.wikipedia.edit.preview;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwPostResponse;
|
||||||
|
|
||||||
|
public class EditPreview extends MwPostResponse {
|
||||||
|
@SuppressWarnings("unused") @Nullable private Parse parse;
|
||||||
|
|
||||||
|
boolean hasPreviewResult() {
|
||||||
|
return parse != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String result() {
|
||||||
|
return parse != null ? parse.text() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Parse {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String title;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("pageid") private int pageId;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @NonNull private String text;
|
||||||
|
@NonNull String text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.wikipedia.feed.aggregated;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
import org.wikipedia.feed.image.FeaturedImage;
|
||||||
|
import org.wikipedia.feed.mostread.MostReadArticles;
|
||||||
|
import org.wikipedia.feed.news.NewsItem;
|
||||||
|
import org.wikipedia.feed.onthisday.OnThisDay;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AggregatedFeedContent {
|
||||||
|
@SuppressWarnings("unused") @Nullable private RbPageSummary tfa;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<NewsItem> news;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("mostread") @Nullable private MostReadArticles mostRead;
|
||||||
|
@SuppressWarnings("unused") @Nullable private FeaturedImage image;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<OnThisDay.Event> onthisday;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<OnThisDay.Event> onthisday() {
|
||||||
|
return onthisday;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public RbPageSummary tfa() {
|
||||||
|
return tfa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
List<NewsItem> news() {
|
||||||
|
return news;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MostReadArticles mostRead() {
|
||||||
|
return mostRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
FeaturedImage potd() {
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
package org.wikipedia.feed.announcement;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
import org.wikipedia.util.DateUtil;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
|
public class Announcement extends BaseModel {
|
||||||
|
public static final String SURVEY = "survey";
|
||||||
|
public static final String FUNDRAISING = "fundraising";
|
||||||
|
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String id;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String type;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("start_time") @Required @NonNull private String startTime;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @SerializedName("end_time") @Required @NonNull private String endTime;
|
||||||
|
@SuppressWarnings("unused") @NonNull private List<String> platforms = Collections.emptyList();
|
||||||
|
@SuppressWarnings("unused") @NonNull private List<String> countries = Collections.emptyList();
|
||||||
|
@SuppressWarnings("unused") @SerializedName("caption_HTML") @Nullable private String footerCaption;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("image_url") @Nullable private String imageUrl;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("image_height") @Nullable private String imageHeight;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("logged_in") @Nullable private Boolean loggedIn;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("reading_list_sync_enabled") @Nullable private Boolean readingListSyncEnabled;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Boolean beta;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("min_version") @Nullable private String minVersion;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("max_version") @Nullable private String maxVersion;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String text;
|
||||||
|
@SuppressWarnings("unused") @Nullable private Action action;
|
||||||
|
@SuppressWarnings("unused") @SerializedName("negative_text") @Nullable private String negativeText;
|
||||||
|
|
||||||
|
public Announcement() { }
|
||||||
|
|
||||||
|
public Announcement(@NonNull String id, @NonNull String text, @NonNull String imageUrl,
|
||||||
|
@NonNull Action action, @NonNull String negativeText) {
|
||||||
|
this.id = id;
|
||||||
|
this.text = text;
|
||||||
|
this.imageUrl = imageUrl;
|
||||||
|
this.action = action;
|
||||||
|
this.negativeText = negativeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String id() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String type() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Date startTime() {
|
||||||
|
try {
|
||||||
|
return DateUtil.iso8601DateParse(startTime);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Date endTime() {
|
||||||
|
try {
|
||||||
|
return DateUtil.iso8601DateParse(endTime);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull List<String> platforms() {
|
||||||
|
return platforms;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull List<String> countries() {
|
||||||
|
return countries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasAction() {
|
||||||
|
return action != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String actionTitle() {
|
||||||
|
return action != null ? action.title() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String actionUrl() {
|
||||||
|
return action != null ? action.url() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasFooterCaption() {
|
||||||
|
return !TextUtils.isEmpty(footerCaption);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String footerCaption() {
|
||||||
|
return defaultString(footerCaption);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasImageUrl() {
|
||||||
|
return !TextUtils.isEmpty(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String imageUrl() {
|
||||||
|
return defaultString(imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String imageHeight() {
|
||||||
|
return defaultString(imageHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String negativeText() {
|
||||||
|
return negativeText;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Boolean loggedIn() {
|
||||||
|
return loggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Boolean readingListSyncEnabled() {
|
||||||
|
return readingListSyncEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Boolean beta() {
|
||||||
|
return beta;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String minVersion() {
|
||||||
|
return minVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable String maxVersion() {
|
||||||
|
return maxVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Action {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String title;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String url;
|
||||||
|
|
||||||
|
public Action(@NonNull String title, @NonNull String url) {
|
||||||
|
this.title = title;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String title() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull String url() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package org.wikipedia.feed.announcement;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.wikipedia.model.BaseModel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AnnouncementList extends BaseModel {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") @SerializedName("announce") @NonNull private List<Announcement> items = Collections.emptyList();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
List<Announcement> items() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.wikipedia.feed.announcement;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class GeoIPCookie {
|
||||||
|
|
||||||
|
@NonNull private final String country;
|
||||||
|
@NonNull private final String region;
|
||||||
|
@NonNull private final String city;
|
||||||
|
@Nullable private final Location location;
|
||||||
|
|
||||||
|
GeoIPCookie(@NonNull String country, @NonNull String region, @NonNull String city, @Nullable Location location) {
|
||||||
|
this.country = country;
|
||||||
|
this.region = region;
|
||||||
|
this.city = city;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String country() {
|
||||||
|
return country;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String region() {
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String city() {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Location location() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.wikipedia.feed.announcement;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This currently supports the "v4" version of the GeoIP cookie.
|
||||||
|
For some info about the format and contents of the cookie:
|
||||||
|
https://phabricator.wikimedia.org/diffusion/ECNO/browse/master/resources/subscribing/ext.centralNotice.geoIP.js
|
||||||
|
*/
|
||||||
|
public final class GeoIPCookieUnmarshaller {
|
||||||
|
private static final String COOKIE_NAME = "GeoIP";
|
||||||
|
|
||||||
|
private enum Component {
|
||||||
|
COUNTRY, REGION, CITY, LATITUDE, LONGITUDE, VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static GeoIPCookie unmarshal() {
|
||||||
|
return unmarshal(SharedPreferenceCookieManager.getInstance().getCookieByName(COOKIE_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
@NonNull
|
||||||
|
static GeoIPCookie unmarshal(@Nullable String cookie) throws IllegalArgumentException {
|
||||||
|
if (TextUtils.isEmpty(cookie)) {
|
||||||
|
throw new IllegalArgumentException("Cookie is empty.");
|
||||||
|
}
|
||||||
|
String[] components = cookie.split(":");
|
||||||
|
if (components.length < Component.values().length) {
|
||||||
|
throw new IllegalArgumentException("Cookie is malformed.");
|
||||||
|
} else if (!components[Component.VERSION.ordinal()].equals("v4")) {
|
||||||
|
throw new IllegalArgumentException("Incorrect cookie version.");
|
||||||
|
}
|
||||||
|
Location location = null;
|
||||||
|
if (!TextUtils.isEmpty(components[Component.LATITUDE.ordinal()])
|
||||||
|
&& !TextUtils.isEmpty(components[Component.LONGITUDE.ordinal()])) {
|
||||||
|
location = new Location("");
|
||||||
|
try {
|
||||||
|
location.setLatitude(Double.parseDouble(components[Component.LATITUDE.ordinal()]));
|
||||||
|
location.setLongitude(Double.parseDouble(components[Component.LONGITUDE.ordinal()]));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("Location is malformed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new GeoIPCookie(components[Component.COUNTRY.ordinal()],
|
||||||
|
components[Component.REGION.ordinal()],
|
||||||
|
components[Component.CITY.ordinal()],
|
||||||
|
location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GeoIPCookieUnmarshaller() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.wikipedia.feed.configure;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class FeedAvailability {
|
||||||
|
@SerializedName("todays_featured_article") private List<String> featuredArticle;
|
||||||
|
@SerializedName("most_read") private List<String> mostRead;
|
||||||
|
@SerializedName("picture_of_the_day") private List<String> featuredPicture;
|
||||||
|
@SerializedName("in_the_news") private List<String> news;
|
||||||
|
@SerializedName("on_this_day") private List<String> onThisDay;
|
||||||
|
|
||||||
|
@NonNull public List<String> featuredArticle() {
|
||||||
|
return featuredArticle != null ? featuredArticle : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<String> mostRead() {
|
||||||
|
return mostRead != null ? mostRead : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<String> featuredPicture() {
|
||||||
|
return featuredPicture != null ? featuredPicture : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<String> news() {
|
||||||
|
return news != null ? news : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<String> onThisDay() {
|
||||||
|
return onThisDay != null ? onThisDay : Collections.emptyList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.wikipedia.feed.image;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.wikipedia.gallery.GalleryItem;
|
||||||
|
import org.wikipedia.gallery.ImageInfo;
|
||||||
|
import org.wikipedia.json.PostProcessingTypeAdapter;
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
|
||||||
|
public final class FeaturedImage extends GalleryItem implements PostProcessingTypeAdapter.PostProcessable {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String title;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private ImageInfo image;
|
||||||
|
|
||||||
|
private int age;
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String title() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postProcess() {
|
||||||
|
setTitle(title);
|
||||||
|
getOriginal().setSource(image.getSource());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.wikipedia.feed.model;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import static java.util.TimeZone.getTimeZone;
|
||||||
|
|
||||||
|
public class UtcDate {
|
||||||
|
@NonNull private Calendar cal;
|
||||||
|
@NonNull private String year;
|
||||||
|
@NonNull private String month;
|
||||||
|
@NonNull private String date;
|
||||||
|
|
||||||
|
public UtcDate(int age) {
|
||||||
|
this.cal = Calendar.getInstance(getTimeZone("UTC"));
|
||||||
|
cal.add(Calendar.DATE, -age);
|
||||||
|
this.year = Integer.toString(cal.get(Calendar.YEAR));
|
||||||
|
this.month = pad(Integer.toString(cal.get(Calendar.MONTH) + 1));
|
||||||
|
this.date = pad(Integer.toString(cal.get(Calendar.DATE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Calendar baseCalendar() {
|
||||||
|
return cal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String year() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String month() {
|
||||||
|
return month;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String date() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String pad(String value) {
|
||||||
|
if (value.length() == 1) {
|
||||||
|
return "0" + value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package org.wikipedia.feed.mostread;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class MostReadArticles {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private Date date;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private List<RbPageSummary> articles;
|
||||||
|
|
||||||
|
@NonNull public Date date() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<RbPageSummary> articles() {
|
||||||
|
return articles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.wikipedia.feed.news;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.wikipedia.dataclient.Service.PREFERRED_THUMB_SIZE;
|
||||||
|
import static org.wikipedia.util.ImageUrlUtil.getUrlForSize;
|
||||||
|
|
||||||
|
public final class NewsItem {
|
||||||
|
@SuppressWarnings("unused") @Required @Nullable private String story;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<RbPageSummary> links
|
||||||
|
= Collections.emptyList();
|
||||||
|
|
||||||
|
@NonNull String story() {
|
||||||
|
return StringUtils.defaultString(story);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<RbPageSummary> links() {
|
||||||
|
return links != null ? links : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public Uri thumb() {
|
||||||
|
Uri uri = getFirstImageUri(links());
|
||||||
|
return uri != null ? getUrlForSize(uri, PREFERRED_THUMB_SIZE) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable Uri featureImage() {
|
||||||
|
return getFirstImageUri(links());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate through the CardPageItems associated with the news story's links and return the first
|
||||||
|
* thumb URI found.
|
||||||
|
*/
|
||||||
|
@Nullable private Uri getFirstImageUri(List<RbPageSummary> links) {
|
||||||
|
for (RbPageSummary link : links) {
|
||||||
|
if (link == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String thumbnail = link.getThumbnailUrl();
|
||||||
|
if (thumbnail != null) {
|
||||||
|
return Uri.parse(thumbnail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package org.wikipedia.feed.onthisday;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.restbase.page.RbPageSummary;
|
||||||
|
import org.wikipedia.json.annotations.Required;
|
||||||
|
import org.wikipedia.util.StringUtil;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class OnThisDay {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Event> selected;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Event> events;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Event> births;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Event> deaths;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<Event> holidays;
|
||||||
|
|
||||||
|
@NonNull public List<Event> selectedEvents() {
|
||||||
|
return selected != null ? selected : Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public List<Event> events() {
|
||||||
|
ArrayList<Event> allEvents = new ArrayList<>();
|
||||||
|
if (events != null) {
|
||||||
|
allEvents.addAll(events);
|
||||||
|
}
|
||||||
|
if (births != null) {
|
||||||
|
allEvents.addAll(births);
|
||||||
|
}
|
||||||
|
if (deaths != null) {
|
||||||
|
allEvents.addAll(deaths);
|
||||||
|
}
|
||||||
|
if (holidays != null) {
|
||||||
|
allEvents.addAll(holidays);
|
||||||
|
}
|
||||||
|
Collections.sort(allEvents, (e1, e2) -> Integer.compare(e2.year(), e1.year()));
|
||||||
|
return allEvents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Event {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private String text;
|
||||||
|
@SuppressWarnings("unused") private int year;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Required @NonNull private List<RbPageSummary> pages;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public CharSequence text() {
|
||||||
|
List<String> pageTitles = new ArrayList<>();
|
||||||
|
for (RbPageSummary page : pages) {
|
||||||
|
pageTitles.add((StringUtil.fromHtml(StringUtils.defaultString(page.getNormalizedTitle()))).toString());
|
||||||
|
}
|
||||||
|
return StringUtil.boldenSubstrings(text, pageTitles);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int year() {
|
||||||
|
return year;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<RbPageSummary> pages() {
|
||||||
|
Iterator iterator = pages.iterator();
|
||||||
|
while ((iterator.hasNext())) {
|
||||||
|
if (iterator.next() == null) {
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(@Nullable List<Event> selected) {
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class ArtistInfo extends TextInfo {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String name;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable @SerializedName("user_page") private String userPage;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
data-client/src/main/java/org/wikipedia/gallery/ExtMetadata.java
Normal file
102
data-client/src/main/java/org/wikipedia/gallery/ExtMetadata.java
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ExtMetadata {
|
||||||
|
@SerializedName("DateTime") @Nullable private Values dateTime;
|
||||||
|
@SerializedName("ObjectName") @Nullable private Values objectName;
|
||||||
|
@SerializedName("CommonsMetadataExtension") @Nullable private Values commonsMetadataExtension;
|
||||||
|
@SerializedName("Categories") @Nullable private Values categories;
|
||||||
|
@SerializedName("Assessments") @Nullable private Values assessments;
|
||||||
|
@SerializedName("GPSLatitude") @Nullable private Values gpsLatitude;
|
||||||
|
@SerializedName("GPSLongitude") @Nullable private Values gpsLongitude;
|
||||||
|
@SerializedName("ImageDescription") @Nullable private Values imageDescription;
|
||||||
|
@SerializedName("DateTimeOriginal") @Nullable private Values dateTimeOriginal;
|
||||||
|
@SerializedName("Artist") @Nullable private Values artist;
|
||||||
|
@SerializedName("Credit") @Nullable private Values credit;
|
||||||
|
@SerializedName("Permission") @Nullable private Values permission;
|
||||||
|
@SerializedName("AuthorCount") @Nullable private Values authorCount;
|
||||||
|
@SerializedName("LicenseShortName") @Nullable private Values licenseShortName;
|
||||||
|
@SerializedName("UsageTerms") @Nullable private Values usageTerms;
|
||||||
|
@SerializedName("LicenseUrl") @Nullable private Values licenseUrl;
|
||||||
|
@SerializedName("AttributionRequired") @Nullable private Values attributionRequired;
|
||||||
|
@SerializedName("Copyrighted") @Nullable private Values copyrighted;
|
||||||
|
@SerializedName("Restrictions") @Nullable private Values restrictions;
|
||||||
|
@SerializedName("License") @Nullable private Values license;
|
||||||
|
|
||||||
|
@NonNull public String licenseShortName() {
|
||||||
|
return StringUtils.defaultString(licenseShortName == null ? null : licenseShortName.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String licenseUrl() {
|
||||||
|
return StringUtils.defaultString(licenseUrl == null ? null : licenseUrl.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String license() {
|
||||||
|
return StringUtils.defaultString(license == null ? null : license.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String imageDescription() {
|
||||||
|
return StringUtils.defaultString(imageDescription == null ? null : imageDescription.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String imageDescriptionSource() {
|
||||||
|
return StringUtils.defaultString(imageDescription == null ? null : imageDescription.source());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String objectName() {
|
||||||
|
return StringUtils.defaultString(objectName == null ? null : objectName.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String usageTerms() {
|
||||||
|
return StringUtils.defaultString(usageTerms == null ? null : usageTerms.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String dateTimeOriginal() {
|
||||||
|
return StringUtils.defaultString(dateTimeOriginal == null ? null : dateTimeOriginal.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String dateTime() {
|
||||||
|
return StringUtils.defaultString(dateTime == null ? null : dateTime.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String artist() {
|
||||||
|
return StringUtils.defaultString(artist == null ? null : artist.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getCategories() {
|
||||||
|
return StringUtils.defaultString(categories == null ? null : categories.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getGpsLatitude() {
|
||||||
|
return StringUtils.defaultString(gpsLatitude == null ? null : gpsLatitude.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getGpsLongitude() {
|
||||||
|
return StringUtils.defaultString(gpsLongitude == null ? null : gpsLongitude.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String credit() {
|
||||||
|
return StringUtils.defaultString(credit == null ? null : credit.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Values {
|
||||||
|
@Nullable private String value;
|
||||||
|
@Nullable private String source;
|
||||||
|
@Nullable private String hidden;
|
||||||
|
|
||||||
|
@Nullable public String value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public String source() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
data-client/src/main/java/org/wikipedia/gallery/Gallery.java
Normal file
35
data-client/src/main/java/org/wikipedia/gallery/Gallery.java
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Gallery {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String revision;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String tid;
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<GalleryItem> items;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<GalleryItem> getAllItems() {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public List<GalleryItem> getItems(@NonNull String... types) {
|
||||||
|
List<GalleryItem> list = new ArrayList<>();
|
||||||
|
if (items != null) {
|
||||||
|
for (GalleryItem item : items) {
|
||||||
|
if (item.isShowInGallery()) {
|
||||||
|
for (String type : types) {
|
||||||
|
if (item.getType().contains(type)) {
|
||||||
|
list.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
194
data-client/src/main/java/org/wikipedia/gallery/GalleryItem.java
Normal file
194
data-client/src/main/java/org/wikipedia/gallery/GalleryItem.java
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.wikipedia.dataclient.Service;
|
||||||
|
import org.wikipedia.util.ImageUrlUtil;
|
||||||
|
import org.wikipedia.util.StringUtil;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class GalleryItem implements Serializable {
|
||||||
|
public static final int PREFERRED_GALLERY_IMAGE_SIZE = 1280;
|
||||||
|
|
||||||
|
@SerializedName("section_id") private int sectionId;
|
||||||
|
@SuppressWarnings("NullableProblems") @NonNull private String type;
|
||||||
|
@Nullable @SerializedName("audio_type") private String audioType;
|
||||||
|
@Nullable private TextInfo caption;
|
||||||
|
private boolean showInGallery;
|
||||||
|
@SuppressWarnings("NullableProblems") @NonNull private Titles titles;
|
||||||
|
@Nullable private ImageInfo thumbnail;
|
||||||
|
@Nullable private ImageInfo original;
|
||||||
|
@Nullable private List<VideoInfo> sources;
|
||||||
|
@Nullable @SerializedName("file_page") private String filePage;
|
||||||
|
@Nullable private ArtistInfo artist;
|
||||||
|
private double duration;
|
||||||
|
@SuppressWarnings("NullableProblems") @NonNull private ImageLicense license;
|
||||||
|
@Nullable private TextInfo description;
|
||||||
|
@Nullable @SerializedName("wb_entity_id") private String entityId;
|
||||||
|
@Nullable @SerializedName("structured") private StructuredData structuredData;
|
||||||
|
|
||||||
|
public GalleryItem() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public GalleryItem(@NonNull String title) {
|
||||||
|
this.type = "*/*";
|
||||||
|
this.titles = new Titles(title, StringUtil.addUnderscores(title), title);
|
||||||
|
this.original = new ImageInfo();
|
||||||
|
this.thumbnail = new ImageInfo();
|
||||||
|
this.description = new TextInfo();
|
||||||
|
this.license = new ImageLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getType() {
|
||||||
|
return StringUtils.defaultString(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getAudioType() {
|
||||||
|
return StringUtils.defaultString(audioType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public TextInfo getCaption() {
|
||||||
|
return caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowInGallery() {
|
||||||
|
return showInGallery;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Titles getTitles() {
|
||||||
|
return titles;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTitle(@NonNull String title) {
|
||||||
|
titles = new Titles(title, StringUtil.addUnderscores(title), title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ImageInfo getThumbnail() {
|
||||||
|
if (thumbnail == null) {
|
||||||
|
thumbnail = new ImageInfo();
|
||||||
|
}
|
||||||
|
return thumbnail;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return getThumbnail().getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getPreferredSizedImageUrl() {
|
||||||
|
return ImageUrlUtil.getUrlForPreferredSize(getThumbnailUrl(), PREFERRED_GALLERY_IMAGE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ImageInfo getOriginal() {
|
||||||
|
if (original == null) {
|
||||||
|
original = new ImageInfo();
|
||||||
|
}
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public List<VideoInfo> getSources() {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public VideoInfo getOriginalVideoSource() {
|
||||||
|
// The getSources has different levels of source,
|
||||||
|
// should have an option that allows user to chose which quality to play
|
||||||
|
return sources == null || sources.size() == 0
|
||||||
|
? null : sources.get(sources.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getFilePage() {
|
||||||
|
// return the base url of Wiki Commons for WikiSite() if the file_page is null.
|
||||||
|
return filePage == null ? Service.COMMONS_URL : StringUtils.defaultString(filePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFilePage(@NonNull String filePage) {
|
||||||
|
this.filePage = filePage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public ArtistInfo getArtist() {
|
||||||
|
return artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArtist(@Nullable ArtistInfo artist) {
|
||||||
|
this.artist = artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public ImageLicense getLicense() {
|
||||||
|
return license;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLicense(@NonNull ImageLicense license) {
|
||||||
|
this.license = license;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public TextInfo getDescription() {
|
||||||
|
if (description == null) {
|
||||||
|
description = new TextInfo();
|
||||||
|
}
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Map<String, String> getStructuredCaptions() {
|
||||||
|
return (structuredData != null && structuredData.captions != null) ? structuredData.captions : Collections.emptyMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Titles implements Serializable {
|
||||||
|
@Nullable private String canonical;
|
||||||
|
@Nullable private String normalized;
|
||||||
|
@Nullable private String display;
|
||||||
|
|
||||||
|
Titles(@NonNull String display, @NonNull String canonical, @NonNull String normalized) {
|
||||||
|
this.display = display;
|
||||||
|
this.canonical = canonical;
|
||||||
|
this.normalized = normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getCanonical() {
|
||||||
|
return StringUtils.defaultString(canonical);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getNormalized() {
|
||||||
|
return StringUtils.defaultString(normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getDisplay() {
|
||||||
|
return StringUtils.defaultString(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class StructuredData implements Serializable {
|
||||||
|
@Nullable private HashMap<String, String> captions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for a standard image info object as returned by the API ImageInfo module
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ImageInfo implements Serializable {
|
||||||
|
private int size;
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
@Nullable private String source;
|
||||||
|
@SerializedName("thumburl") @Nullable private String thumbUrl;
|
||||||
|
@SerializedName("thumbwidth") private int thumbWidth;
|
||||||
|
@SerializedName("thumbheight") private int thumbHeight;
|
||||||
|
@SerializedName("url") @Nullable private String originalUrl;
|
||||||
|
@SerializedName("descriptionurl") @Nullable private String descriptionUrl;
|
||||||
|
@SerializedName("descriptionshorturl") @Nullable private String descriptionShortUrl;
|
||||||
|
@SerializedName("mime") @Nullable private String mimeType;
|
||||||
|
@SerializedName("extmetadata")@Nullable private ExtMetadata metadata;
|
||||||
|
@Nullable private String user;
|
||||||
|
@Nullable private String timestamp;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getSource() {
|
||||||
|
return StringUtils.defaultString(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSource(@Nullable String source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSize() {
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getMimeType() {
|
||||||
|
return StringUtils.defaultString(mimeType, "*/*");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getThumbUrl() {
|
||||||
|
return StringUtils.defaultString(thumbUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getOriginalUrl() {
|
||||||
|
return StringUtils.defaultString(originalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getUser() {
|
||||||
|
return StringUtils.defaultString(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getTimestamp() {
|
||||||
|
return StringUtils.defaultString(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable public ExtMetadata getMetadata() {
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
|
public class ImageLicense implements Serializable {
|
||||||
|
private static final String CREATIVE_COMMONS_PREFIX = "cc";
|
||||||
|
private static final String PUBLIC_DOMAIN_PREFIX = "pd";
|
||||||
|
private static final String CC_BY_SA = "ccbysa";
|
||||||
|
|
||||||
|
@NonNull @SerializedName("type") private final String license;
|
||||||
|
@NonNull @SerializedName("code") private final String licenseShortName;
|
||||||
|
@NonNull @SerializedName("url") private final String licenseUrl;
|
||||||
|
|
||||||
|
public ImageLicense(@NonNull ExtMetadata metadata) {
|
||||||
|
this.license = metadata.license();
|
||||||
|
this.licenseShortName = metadata.licenseShortName();
|
||||||
|
this.licenseUrl = metadata.licenseUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageLicense(@NonNull String license, @NonNull String licenseShortName, @NonNull String licenseUrl) {
|
||||||
|
this.license = license;
|
||||||
|
this.licenseShortName = licenseShortName;
|
||||||
|
this.licenseUrl = licenseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImageLicense() {
|
||||||
|
this("", "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getLicenseName() {
|
||||||
|
return license;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getLicenseShortName() {
|
||||||
|
return licenseShortName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull public String getLicenseUrl() {
|
||||||
|
return licenseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLicenseCC() {
|
||||||
|
return defaultString(license).toLowerCase(Locale.ENGLISH).startsWith(CREATIVE_COMMONS_PREFIX)
|
||||||
|
|| defaultString(licenseShortName).toLowerCase(Locale.ENGLISH).startsWith(CREATIVE_COMMONS_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLicensePD() {
|
||||||
|
return defaultString(license).toLowerCase(Locale.ENGLISH).startsWith(PUBLIC_DOMAIN_PREFIX)
|
||||||
|
|| defaultString(licenseShortName).toLowerCase(Locale.ENGLISH).startsWith(PUBLIC_DOMAIN_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isLicenseCCBySa() {
|
||||||
|
return defaultString(license).toLowerCase(Locale.ENGLISH).replace("-", "").startsWith(CC_BY_SA)
|
||||||
|
|| defaultString(licenseShortName).toLowerCase(Locale.ENGLISH).replace("-", "").startsWith(CC_BY_SA);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasLicenseInfo() {
|
||||||
|
return !(license.isEmpty() && licenseShortName.isEmpty() && licenseUrl.isEmpty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class TextInfo implements Serializable {
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String html;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String text;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String lang;
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getHtml() {
|
||||||
|
return StringUtils.defaultString(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getText() {
|
||||||
|
return StringUtils.defaultString(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getLang() {
|
||||||
|
return StringUtils.defaultString(lang);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.wikipedia.gallery;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson POJO for a standard video info object as returned by the API VideoInfo module
|
||||||
|
*/
|
||||||
|
public class VideoInfo extends ImageInfo {
|
||||||
|
@SuppressWarnings("unused") @Nullable private List<String> codecs;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable private String name;
|
||||||
|
@SuppressWarnings("unused,NullableProblems") @Nullable @SerializedName("short_name") private String shortName;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package org.wikipedia.json;
|
||||||
|
|
||||||
|
import com.google.gson.TypeAdapter;
|
||||||
|
import com.google.gson.stream.JsonReader;
|
||||||
|
import com.google.gson.stream.JsonWriter;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
public class CookieManagerTypeAdapter extends TypeAdapter<SharedPreferenceCookieManager> {
|
||||||
|
@Override public void write(JsonWriter out, SharedPreferenceCookieManager cookies) throws IOException {
|
||||||
|
Map<String, List<Cookie>> map = cookies.getCookieJar();
|
||||||
|
out.beginObject();
|
||||||
|
for (String key : map.keySet()) {
|
||||||
|
out.name(key).beginArray();
|
||||||
|
for (Cookie cookie : map.get(key)) {
|
||||||
|
out.value(cookie.toString());
|
||||||
|
}
|
||||||
|
out.endArray();
|
||||||
|
}
|
||||||
|
out.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public SharedPreferenceCookieManager read(JsonReader in) throws IOException {
|
||||||
|
Map<String, List<Cookie>> map = new HashMap<>();
|
||||||
|
in.beginObject();
|
||||||
|
while (in.hasNext()) {
|
||||||
|
String key = in.nextName();
|
||||||
|
List<Cookie> list = new ArrayList<>();
|
||||||
|
map.put(key, list);
|
||||||
|
in.beginArray();
|
||||||
|
HttpUrl url = HttpUrl.parse(WikiSite.DEFAULT_SCHEME + "://" + key);
|
||||||
|
while (in.hasNext()) {
|
||||||
|
String str = in.nextString();
|
||||||
|
if (url != null) {
|
||||||
|
list.add(Cookie.parse(url, str));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.endArray();
|
||||||
|
}
|
||||||
|
in.endObject();
|
||||||
|
return new SharedPreferenceCookieManager(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package org.wikipedia.json;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
public final class GsonMarshaller {
|
||||||
|
public static String marshal(@Nullable Object object) {
|
||||||
|
return marshal(GsonUtil.getDefaultGson(), object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String marshal(@NonNull Gson gson, @Nullable Object object) {
|
||||||
|
return gson.toJson(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GsonMarshaller() { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.wikipedia.json;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
public final class GsonUnmarshaller {
|
||||||
|
/** @return Unmarshalled object. */
|
||||||
|
public static <T> T unmarshal(Class<T> clazz, @Nullable String json) {
|
||||||
|
return unmarshal(GsonUtil.getDefaultGson(), clazz, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Unmarshalled collection of objects. */
|
||||||
|
public static <T> T unmarshal(TypeToken<T> typeToken, @Nullable String json) {
|
||||||
|
return unmarshal(GsonUtil.getDefaultGson(), typeToken, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Unmarshalled object. */
|
||||||
|
public static <T> T unmarshal(@NonNull Gson gson, Class<T> clazz, @Nullable String json) {
|
||||||
|
return gson.fromJson(json, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Unmarshalled collection of objects. */
|
||||||
|
public static <T> T unmarshal(@NonNull Gson gson, TypeToken<T> typeToken, @Nullable String json) {
|
||||||
|
// From the manual: "Fairly hideous... Unfortunately, no way to get around this in Java".
|
||||||
|
return gson.fromJson(json, typeToken.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private GsonUnmarshaller() { }
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue