├── .github
├── FUNDING.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .project
├── LICENSE
├── README.md
├── app.gif
├── app
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── gotify
│ │ ├── MarkwonFactory.java
│ │ ├── MissedMessageUtil.java
│ │ ├── NotificationSupport.java
│ │ ├── SSLSettings.java
│ │ ├── Settings.java
│ │ ├── Utils.java
│ │ ├── api
│ │ ├── Api.java
│ │ ├── ApiException.java
│ │ ├── Callback.java
│ │ ├── CertUtils.java
│ │ └── ClientFactory.java
│ │ ├── init
│ │ ├── BootCompletedReceiver.java
│ │ └── InitializationActivity.java
│ │ ├── log
│ │ ├── Format.java
│ │ ├── Log.java
│ │ ├── LogsActivity.java
│ │ └── UncaughtExceptionHandler.java
│ │ ├── login
│ │ ├── AdvancedDialog.java
│ │ └── LoginActivity.java
│ │ ├── messages
│ │ ├── Extras.java
│ │ ├── ListMessageAdapter.java
│ │ ├── MessagesActivity.java
│ │ └── provider
│ │ │ ├── ApplicationHolder.java
│ │ │ ├── MessageDeletion.java
│ │ │ ├── MessageFacade.java
│ │ │ ├── MessageImageCombiner.java
│ │ │ ├── MessageRequester.java
│ │ │ ├── MessageState.java
│ │ │ ├── MessageStateHolder.java
│ │ │ └── MessageWithImage.java
│ │ ├── picasso
│ │ ├── PicassoDataRequestHandler.java
│ │ └── PicassoHandler.java
│ │ ├── service
│ │ ├── Constants.kt
│ │ ├── MessagingDatabase.kt
│ │ ├── PushNotification.kt
│ │ ├── RegisterBroadcastReceiver.kt
│ │ ├── WebSocketConnection.java
│ │ └── WebSocketService.java
│ │ ├── settings
│ │ ├── SettingsActivity.java
│ │ └── ThemeHelper.java
│ │ └── sharing
│ │ └── ShareActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_gotify.png
│ ├── drawable-mdpi
│ └── ic_gotify.png
│ ├── drawable-xhdpi
│ └── ic_gotify.png
│ ├── drawable-xxhdpi
│ └── ic_gotify.png
│ ├── drawable-xxxhdpi
│ └── ic_gotify.png
│ ├── drawable
│ ├── gotify.png
│ ├── ic_alarm.xml
│ ├── ic_bug_report.xml
│ ├── ic_dashboard.xml
│ ├── ic_delete.xml
│ ├── ic_info.xml
│ ├── ic_placeholder.xml
│ ├── ic_power_setting.xml
│ ├── ic_refresh.xml
│ ├── ic_send.xml
│ ├── ic_settings.xml
│ └── side_nav_bar.xml
│ ├── layout
│ ├── activity_login.xml
│ ├── activity_logs.xml
│ ├── activity_messages.xml
│ ├── activity_share.xml
│ ├── advanced_settings_dialog.xml
│ ├── app_bar_drawer.xml
│ ├── message_item.xml
│ ├── nav_header_drawer.xml
│ ├── settings_activity.xml
│ └── splash.xml
│ ├── menu
│ ├── logs_action.xml
│ ├── messages_action.xml
│ └── messages_menu.xml
│ ├── mipmap-anydpi-v26
│ └── ic_launcher.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_foreground.png
│ ├── values-notnight
│ └── colors.xml
│ ├── values-v21
│ └── styles.xml
│ ├── values
│ ├── arrays.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── network_security_config.xml
│ └── root_preferences.xml
├── build.gradle
├── client
├── .gitignore
├── .swagger-codegen-ignore
├── .swagger-codegen
│ └── VERSION
├── .travis.yml
├── README.md
├── build.gradle
├── build.sbt
├── docs
│ ├── Application.md
│ ├── ApplicationApi.md
│ ├── Client.md
│ ├── ClientApi.md
│ ├── Error.md
│ ├── Health.md
│ ├── HealthApi.md
│ ├── Message.md
│ ├── MessageApi.md
│ ├── PagedMessages.md
│ ├── Paging.md
│ ├── PluginApi.md
│ ├── PluginConf.md
│ ├── User.md
│ ├── UserApi.md
│ ├── UserPass.md
│ ├── UserWithPass.md
│ ├── VersionApi.md
│ └── VersionInfo.md
├── git_push.sh
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom.xml
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── com
│ │ └── github
│ │ └── gotify
│ │ └── client
│ │ ├── ApiClient.java
│ │ ├── CollectionFormats.java
│ │ ├── JSON.java
│ │ ├── StringUtil.java
│ │ ├── api
│ │ ├── ApplicationApi.java
│ │ ├── ClientApi.java
│ │ ├── HealthApi.java
│ │ ├── MessageApi.java
│ │ ├── PluginApi.java
│ │ ├── UserApi.java
│ │ └── VersionApi.java
│ │ ├── auth
│ │ ├── ApiKeyAuth.java
│ │ ├── HttpBasicAuth.java
│ │ ├── OAuth.java
│ │ ├── OAuthFlow.java
│ │ └── OAuthOkHttpClient.java
│ │ └── model
│ │ ├── Application.java
│ │ ├── Client.java
│ │ ├── Error.java
│ │ ├── Health.java
│ │ ├── Message.java
│ │ ├── PagedMessages.java
│ │ ├── Paging.java
│ │ ├── PluginConf.java
│ │ ├── User.java
│ │ ├── UserPass.java
│ │ ├── UserWithPass.java
│ │ └── VersionInfo.java
│ └── test
│ └── java
│ └── com
│ └── github
│ └── gotify
│ └── client
│ └── api
│ ├── ApplicationApiTest.java
│ ├── ClientApiTest.java
│ ├── HealthApiTest.java
│ ├── MessageApiTest.java
│ ├── PluginApiTest.java
│ ├── UserApiTest.java
│ └── VersionApiTest.java
├── download-badge.png
├── fastlane
└── metadata
│ └── android
│ └── en-US
│ ├── changelogs
│ ├── 10.txt
│ ├── 11.txt
│ ├── 7.txt
│ ├── 8.txt
│ └── 9.txt
│ ├── full_description.txt
│ ├── images
│ └── phoneScreenshots
│ │ └── 1.jpg
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lint.xml
├── settings.gradle
└── swagger.config.json
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: jmattheis
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: S1m
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: https://jmattheis.de/donate
13 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 |
3 | name: Build
4 |
5 | jobs:
6 | check:
7 | name: Check
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 | - uses: actions/setup-java@v1
12 | with:
13 | java-version: 1.8
14 | - if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
15 | run: ./gradlew build --stacktrace
16 | - if: ${{ startsWith(github.ref, 'refs/tags/v') }}
17 | run: |
18 | export RELEASE_STORE_FILE=$(pwd)/gotfy-release-key.jks
19 | echo $RELEASE_KEY | base64 -d > $RELEASE_STORE_FILE
20 | ./gradlew -Psign build --stacktrace
21 | cp app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/Gotify.apk
22 | env:
23 | RELEASE_KEY: ${{ secrets.RELEASE_KEY }}
24 | RELEASE_STORE_PASSWORD: ${{ secrets.STOREPASS }}
25 | RELEASE_KEY_ALIAS: unifiedpush
26 | RELEASE_KEY_PASSWORD: ${{ secrets.KEYPASS }}
27 | - if: ${{ startsWith(github.ref, 'refs/tags/v') }}
28 | uses: svenstaro/upload-release-action@v2
29 | with:
30 | repo_token: ${{ secrets.GITHUB_TOKEN }}
31 | file: app/build/outputs/apk/release/Gotify.apk
32 | tag: ${{ github.ref }}
33 | overwrite: true
34 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | build/
7 | /captures
8 | .externalNativeBuild
9 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | android
4 | Project android created by Buildship.
5 |
6 |
7 |
8 |
9 | org.eclipse.buildship.core.gradleprojectbuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.buildship.core.gradleprojectnature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Gotify
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated
2 |
3 | We have stop maintaining this fork. If you wish to install an unifiedpush distributor, give a look at . The most similar to gotify at this moment is [ntfy](https://ntfy.sh).
4 |
5 | # Gotify-UP Android [![Build Status][github-action-badge]][github-action] [![FOSSA Status][fossa-badge]][fossa] [![latest release version][release-badge]][release] [![F-Droid][fdroid-badge]][fdroid]
6 |
7 |
8 |
9 | Gotify Android connects to [gotify/server](https://github.com/gotify/server) and shows push notifications on new messages.
10 |
11 | ## Features
12 |
13 | * show push notifications on new messages
14 | * view and delete messages
15 |
16 | ## Installation
17 |
18 | Download the apk or get the app via F-Droid or Google Play.
19 |
20 | [][fdroid]
21 | [][release]
22 |
23 | Google Play and the Google Play logo are trademarks of Google LLC.
24 |
25 | ### Disable battery optimization
26 |
27 | By default Android kills long running apps as they drain the battery. With enabled battery optimization, Gotify will be killed and you wont receive any notifications.
28 |
29 | Here is one way to disable battery optimization for Gotify.
30 |
31 | * Open "Settings"
32 | * Search for "Battery Optimization"
33 | * Find "Gotify-UP" and disable battery optimization
34 |
35 | See also https://dontkillmyapp.com for phone manufacturer specific instructions to disable battery optimizations.
36 |
37 | ### Minimize the Gotify foreground notification
38 |
39 | *Only possible for Android version >= 8*
40 |
41 | The foreground notification with content like `Listening to https://push.yourdomain.eu` can be manually minimized to be less intrusive:
42 |
43 | * Open Settings -> Apps -> Gotify-UP
44 | * Click Notifications
45 | * Click on `Gotify foreground notification`
46 | * Select a different "Behavior" or "Importance" (depends on your android version)
47 | * Restart Gotify
48 |
49 | ## Message Priorities
50 |
51 | | Notification | Gotify Priority|
52 | |- |-|
53 | | - | 0 |
54 | | Icon in notification bar | 1 - 3 |
55 | | Icon in notification bar + Sound | 4 - 7 |
56 | | Icon in notification bar + Sound + Vibration | 8 - 10 |
57 |
58 | ## Building
59 |
60 | Execute the following command to build the apk.
61 | ```bash
62 | $ ./gradlew build
63 | ```
64 |
65 | ## Update client
66 |
67 | * Run `./gradlew generateSwaggerCode`
68 | * Discard changes to `client/build.gradle` (newer versions of dependencies)
69 | * Fix compile error in `client/src/main/java/com/github/gotify/client/auth/OAuthOkHttpClient.java` (caused by an updated dependency)
70 | * Delete `client/settings.gradle` (client is a gradle sub project and must not have a settings.gradle)
71 | * Commit changes
72 |
73 | ## Versioning
74 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the
75 | [tags on this repository](https://github.com/UnifiedPush/gotify-android/tags).
76 |
77 | ## License
78 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
79 |
80 | [github-action-badge]: https://github.com/UnifiedPush/gotify-android/workflows/Build/badge.svg
81 | [github-action]: https://github.com/UnifiedPush/gotify-android/actions?query=workflow%3ABuild
82 | [fdroid-badge]: https://img.shields.io/f-droid/v/com.github.gotify.up.svg
83 | [fdroid]: https://f-droid.org/de/packages/com.github.gotify.up/
84 | [fossa-badge]: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fgotify%2Fandroid.svg?type=shield
85 | [fossa]: https://app.fossa.io/projects/git%2Bgithub.com%2Fgotify%2Fandroid
86 | [release-badge]: https://img.shields.io/github/release/UnifiedPush/gotify-android.svg
87 | [release]: https://github.com/UnifiedPush/gotify-android/releases/latest
88 |
89 |
--------------------------------------------------------------------------------
/app.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app.gif
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.diffplug.gradle.spotless" version "3.26.1"
3 | }
4 | apply plugin: 'com.android.application'
5 | apply plugin: 'kotlin-android-extensions'
6 | apply plugin: 'kotlin-android'
7 |
8 | android {
9 | compileSdkVersion 30
10 | defaultConfig {
11 | applicationId "com.github.gotify.up"
12 | minSdkVersion 19
13 | targetSdkVersion 30
14 | versionCode 11
15 | versionName "2.2.0-UP2"
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | vectorDrawables.useSupportLibrary = true
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | compileOptions {
26 | sourceCompatibility = '1.8'
27 | targetCompatibility = '1.8'
28 | }
29 | lintOptions {
30 | disable 'GoogleAppIndexingWarning'
31 | lintConfig file('../lint.xml')
32 | }
33 | packagingOptions {
34 | exclude 'META-INF/DEPENDENCIES'
35 | }
36 | }
37 |
38 | if (project.hasProperty('sign')) {
39 | android {
40 | signingConfigs {
41 | release {
42 | storeFile file(System.getenv("RELEASE_STORE_FILE"))
43 | storePassword System.getenv("RELEASE_STORE_PASSWORD")
44 | keyAlias System.getenv("RELEASE_KEY_ALIAS")
45 | keyPassword System.getenv("RELEASE_KEY_PASSWORD")
46 | }
47 | }
48 | }
49 | android.buildTypes.release.signingConfig android.signingConfigs.release
50 | }
51 |
52 | dependencies {
53 | implementation project(':client')
54 | implementation fileTree(dir: 'libs', include: ['*.jar'])
55 | implementation 'androidx.appcompat:appcompat:1.2.0'
56 | implementation 'com.google.android.material:material:1.3.0'
57 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
58 | implementation 'androidx.legacy:legacy-support-v4:1.0.0'
59 | implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
60 | implementation 'androidx.preference:preference:1.1.1'
61 |
62 | implementation 'com.jakewharton:butterknife:10.2.3'
63 | annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
64 | implementation 'com.hypertrack:hyperlog:0.0.10'
65 | implementation 'com.squareup.picasso:picasso:2.71828'
66 | implementation 'io.noties.markwon:core:4.6.2'
67 | implementation 'io.noties.markwon:image-picasso:4.6.2'
68 | implementation 'io.noties.markwon:image:4.6.2'
69 | implementation 'io.noties.markwon:ext-tables:4.6.2'
70 | implementation "androidx.core:core-ktx:1.6.0"
71 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
72 | implementation 'io.noties.markwon:ext-strikethrough:4.6.2'
73 | }
74 |
75 | configurations {
76 | all {
77 | exclude group: 'org.json', module: 'json'
78 | }
79 | }
80 |
81 | spotless {
82 | java {
83 | target '**/*.java'
84 | googleJavaFormat().aosp()
85 | removeUnusedImports()
86 | importOrder('', 'static *')
87 | }
88 | }
89 | repositories {
90 | mavenCentral()
91 | }
92 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
36 |
41 |
45 |
49 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/MissedMessageUtil.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify;
2 |
3 | import com.github.gotify.api.Api;
4 | import com.github.gotify.api.ApiException;
5 | import com.github.gotify.api.Callback;
6 | import com.github.gotify.client.api.MessageApi;
7 | import com.github.gotify.client.model.Message;
8 | import com.github.gotify.client.model.PagedMessages;
9 | import com.github.gotify.log.Log;
10 | import java.util.ArrayList;
11 | import java.util.Collections;
12 | import java.util.List;
13 |
14 | import static com.github.gotify.api.Callback.call;
15 |
16 | public class MissedMessageUtil {
17 | static final long NO_MESSAGES = 0;
18 |
19 | private final MessageApi api;
20 |
21 | public MissedMessageUtil(MessageApi api) {
22 | this.api = api;
23 | }
24 |
25 | public void lastReceivedMessage(Callback.SuccessCallback successCallback) {
26 | api.getMessages(1, 0L)
27 | .enqueue(
28 | call(
29 | (messages) -> {
30 | if (messages.getMessages().size() == 1) {
31 | successCallback.onSuccess(
32 | messages.getMessages().get(0).getId());
33 | } else {
34 | successCallback.onSuccess(NO_MESSAGES);
35 | }
36 | },
37 | (e) -> {}));
38 | }
39 |
40 | public List missingMessages(long till) {
41 | List result = new ArrayList<>();
42 | try {
43 |
44 | Long since = null;
45 | while (true) {
46 | PagedMessages pagedMessages = Api.execute(api.getMessages(10, since));
47 | List messages = pagedMessages.getMessages();
48 | List filtered = filter(messages, till);
49 | result.addAll(filtered);
50 | if (messages.size() != filtered.size()
51 | || messages.size() == 0
52 | || pagedMessages.getPaging().getNext() == null) {
53 | break;
54 | }
55 | since = pagedMessages.getPaging().getSince();
56 | }
57 | } catch (ApiException e) {
58 | Log.e("cannot retrieve missing messages", e);
59 | }
60 | Collections.reverse(result);
61 | return result;
62 | }
63 |
64 | private List filter(List messages, long till) {
65 | List result = new ArrayList<>();
66 |
67 | for (Message message : messages) {
68 | if (message.getId() > till) {
69 | result.add(message);
70 | } else {
71 | break;
72 | }
73 | }
74 |
75 | return result;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/SSLSettings.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify;
2 |
3 | public class SSLSettings {
4 | public boolean validateSSL;
5 | public String cert;
6 |
7 | public SSLSettings(boolean validateSSL, String cert) {
8 | this.validateSSL = validateSSL;
9 | this.cert = cert;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/Settings.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import com.github.gotify.client.model.User;
6 |
7 | public class Settings {
8 | private final SharedPreferences sharedPreferences;
9 |
10 | public Settings(Context context) {
11 | sharedPreferences = context.getSharedPreferences("gotify", Context.MODE_PRIVATE);
12 | }
13 |
14 | public void url(String url) {
15 | sharedPreferences.edit().putString("url", url).apply();
16 | }
17 |
18 | public String url() {
19 | return sharedPreferences.getString("url", null);
20 | }
21 |
22 | public boolean tokenExists() {
23 | return token() != null;
24 | }
25 |
26 | public String token() {
27 | return sharedPreferences.getString("token", null);
28 | }
29 |
30 | public void token(String token) {
31 | sharedPreferences.edit().putString("token", token).apply();
32 | }
33 |
34 | public void clear() {
35 | url(null);
36 | token(null);
37 | validateSSL(true);
38 | cert(null);
39 | }
40 |
41 | public void user(String name, boolean admin) {
42 | sharedPreferences.edit().putString("username", name).putBoolean("admin", admin).apply();
43 | }
44 |
45 | public User user() {
46 | String username = sharedPreferences.getString("username", null);
47 | boolean admin = sharedPreferences.getBoolean("admin", false);
48 | if (username != null) {
49 | return new User().name(username).admin(admin);
50 | } else {
51 | return new User().name("UNKNOWN").admin(false);
52 | }
53 | }
54 |
55 | public String serverVersion() {
56 | return sharedPreferences.getString("version", "UNKNOWN");
57 | }
58 |
59 | public void serverVersion(String version) {
60 | sharedPreferences.edit().putString("version", version).apply();
61 | }
62 |
63 | private boolean validateSSL() {
64 | return sharedPreferences.getBoolean("validateSSL", true);
65 | }
66 |
67 | public void validateSSL(boolean validateSSL) {
68 | sharedPreferences.edit().putBoolean("validateSSL", validateSSL).apply();
69 | }
70 |
71 | private String cert() {
72 | return sharedPreferences.getString("cert", null);
73 | }
74 |
75 | public void cert(String cert) {
76 | sharedPreferences.edit().putString("cert", cert).apply();
77 | }
78 |
79 | public SSLSettings sslSettings() {
80 | return new SSLSettings(validateSSL(), cert());
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/Utils.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify;
2 |
3 | import android.app.Activity;
4 | import android.content.res.Resources;
5 | import android.graphics.Bitmap;
6 | import android.graphics.drawable.BitmapDrawable;
7 | import android.graphics.drawable.Drawable;
8 | import android.text.format.DateUtils;
9 | import android.view.View;
10 | import androidx.annotation.NonNull;
11 | import com.github.gotify.client.JSON;
12 | import com.github.gotify.log.Log;
13 | import com.google.android.material.snackbar.Snackbar;
14 | import com.google.gson.Gson;
15 | import com.squareup.picasso.Picasso;
16 | import com.squareup.picasso.Target;
17 | import java.io.BufferedReader;
18 | import java.io.IOException;
19 | import java.io.InputStream;
20 | import java.io.InputStreamReader;
21 | import java.net.MalformedURLException;
22 | import java.net.URI;
23 | import java.net.URISyntaxException;
24 | import java.net.URL;
25 | import okio.Buffer;
26 | import org.threeten.bp.OffsetDateTime;
27 |
28 | public class Utils {
29 | public static final Gson JSON = new JSON().getGson();
30 |
31 | public static void showSnackBar(Activity activity, String message) {
32 | View rootView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
33 | Snackbar.make(rootView, message, Snackbar.LENGTH_SHORT).show();
34 | }
35 |
36 | public static int longToInt(long value) {
37 | return (int) (value % Integer.MAX_VALUE);
38 | }
39 |
40 | public static String dateToRelative(OffsetDateTime data) {
41 | long time = data.toInstant().toEpochMilli();
42 | long now = System.currentTimeMillis();
43 | return DateUtils.getRelativeTimeSpanString(time, now, DateUtils.MINUTE_IN_MILLIS)
44 | .toString();
45 | }
46 |
47 | public static String resolveAbsoluteUrl(String baseURL, String target) {
48 | if (target == null) {
49 | return null;
50 | }
51 | try {
52 | URI targetUri = new URI(target);
53 | if (targetUri.isAbsolute()) {
54 | return target;
55 | }
56 | return new URL(new URL(baseURL), target).toString();
57 | } catch (MalformedURLException | URISyntaxException e) {
58 | Log.e("Could not resolve absolute url", e);
59 | return target;
60 | }
61 | }
62 |
63 | public static Target toDrawable(Resources resources, DrawableReceiver drawableReceiver) {
64 | return new Target() {
65 | @Override
66 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
67 | drawableReceiver.loaded(new BitmapDrawable(resources, bitmap));
68 | }
69 |
70 | @Override
71 | public void onBitmapFailed(Exception e, Drawable errorDrawable) {
72 | Log.e("Bitmap failed", e);
73 | }
74 |
75 | @Override
76 | public void onPrepareLoad(Drawable placeHolderDrawable) {}
77 | };
78 | }
79 |
80 | public static String readFileFromStream(@NonNull InputStream inputStream) {
81 | StringBuilder sb = new StringBuilder();
82 | String currentLine;
83 |
84 | try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
85 | while ((currentLine = reader.readLine()) != null) {
86 | sb.append(currentLine).append("\n");
87 | }
88 | } catch (IOException e) {
89 | throw new IllegalArgumentException("failed to read input");
90 | }
91 |
92 | return sb.toString();
93 | }
94 |
95 | public interface DrawableReceiver {
96 | void loaded(Drawable drawable);
97 | }
98 |
99 | public static InputStream stringToInputStream(String str) {
100 | if (str == null) return null;
101 | return new Buffer().writeUtf8(str).inputStream();
102 | }
103 |
104 | public static T first(T[] data) {
105 | if (data.length != 1) {
106 | throw new IllegalArgumentException("must be one element");
107 | }
108 | return data[0];
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/api/Api.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.api;
2 |
3 | import java.io.IOException;
4 | import retrofit2.Call;
5 | import retrofit2.Response;
6 |
7 | public class Api {
8 | public static T execute(Call call) throws ApiException {
9 | try {
10 | Response response = call.execute();
11 |
12 | if (response.isSuccessful()) {
13 | return response.body();
14 | } else {
15 | throw new ApiException(response);
16 | }
17 | } catch (IOException e) {
18 | throw new ApiException(e);
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/api/ApiException.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.api;
2 |
3 | import java.io.IOException;
4 | import java.util.Locale;
5 | import retrofit2.Response;
6 |
7 | public final class ApiException extends Exception {
8 |
9 | private String body;
10 | private int code;
11 |
12 | ApiException(Response> response) {
13 | super("Api Error", null);
14 | try {
15 | this.body = response.errorBody() != null ? response.errorBody().string() : "";
16 | } catch (IOException e) {
17 | this.body = "Error while getting error body :(";
18 | }
19 | this.code = response.code();
20 | }
21 |
22 | ApiException(Throwable cause) {
23 | super("Request failed.", cause);
24 | this.body = "";
25 | this.code = 0;
26 | }
27 |
28 | public String body() {
29 | return body;
30 | }
31 |
32 | public int code() {
33 | return code;
34 | }
35 |
36 | @Override
37 | public String toString() {
38 | return String.format(
39 | Locale.ENGLISH,
40 | "Code(%d) Response: %s",
41 | code(),
42 | body().substring(0, Math.min(body().length(), 200)));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/api/Callback.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.api;
2 |
3 | import android.app.Activity;
4 | import com.github.gotify.log.Log;
5 | import retrofit2.Call;
6 | import retrofit2.Response;
7 |
8 | public class Callback {
9 | private final SuccessCallback onSuccess;
10 | private final ErrorCallback onError;
11 |
12 | private Callback(SuccessCallback onSuccess, ErrorCallback onError) {
13 | this.onSuccess = onSuccess;
14 | this.onError = onError;
15 | }
16 |
17 | public static retrofit2.Callback callInUI(
18 | Activity context, SuccessCallback onSuccess, ErrorCallback onError) {
19 | return call(
20 | (data) -> context.runOnUiThread(() -> onSuccess.onSuccess(data)),
21 | (e) -> context.runOnUiThread(() -> onError.onError(e)));
22 | }
23 |
24 | public static retrofit2.Callback call() {
25 | return call((e) -> {}, (e) -> {});
26 | }
27 |
28 | public static retrofit2.Callback call(
29 | SuccessCallback onSuccess, ErrorCallback onError) {
30 | return new RetrofitCallback<>(merge(of(onSuccess, onError), errorCallback()));
31 | }
32 |
33 | private static Callback of(SuccessCallback onSuccess, ErrorCallback onError) {
34 | return new Callback<>(onSuccess, onError);
35 | }
36 |
37 | private static Callback errorCallback() {
38 | return new Callback<>((ignored) -> {}, (error) -> Log.e("Error while api call", error));
39 | }
40 |
41 | private static Callback merge(Callback left, Callback right) {
42 | return new Callback<>(
43 | (data) -> {
44 | left.onSuccess.onSuccess(data);
45 | right.onSuccess.onSuccess(data);
46 | },
47 | (error) -> {
48 | left.onError.onError(error);
49 | right.onError.onError(error);
50 | });
51 | }
52 |
53 | public interface SuccessCallback {
54 | void onSuccess(T data);
55 | }
56 |
57 | public interface ErrorCallback {
58 | void onError(ApiException t);
59 | }
60 |
61 | private static final class RetrofitCallback implements retrofit2.Callback {
62 |
63 | private Callback callback;
64 |
65 | private RetrofitCallback(Callback callback) {
66 | this.callback = callback;
67 | }
68 |
69 | @Override
70 | public void onResponse(Call call, Response response) {
71 | if (response.isSuccessful()) {
72 | callback.onSuccess.onSuccess(response.body());
73 | } else {
74 | callback.onError.onError(new ApiException(response));
75 | }
76 | }
77 |
78 | @Override
79 | public void onFailure(Call call, Throwable t) {
80 | callback.onError.onError(new ApiException(t));
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/api/CertUtils.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.api;
2 |
3 | import android.annotation.SuppressLint;
4 | import com.github.gotify.SSLSettings;
5 | import com.github.gotify.Utils;
6 | import com.github.gotify.log.Log;
7 | import java.io.IOException;
8 | import java.security.GeneralSecurityException;
9 | import java.security.KeyStore;
10 | import java.security.SecureRandom;
11 | import java.security.cert.Certificate;
12 | import java.security.cert.CertificateFactory;
13 | import java.security.cert.X509Certificate;
14 | import java.util.Collection;
15 | import javax.net.ssl.KeyManager;
16 | import javax.net.ssl.SSLContext;
17 | import javax.net.ssl.TrustManager;
18 | import javax.net.ssl.TrustManagerFactory;
19 | import javax.net.ssl.X509TrustManager;
20 | import okhttp3.OkHttpClient;
21 |
22 | public class CertUtils {
23 | private static final X509TrustManager trustAll =
24 | new X509TrustManager() {
25 | @SuppressLint("TrustAllX509TrustManager")
26 | @Override
27 | public void checkClientTrusted(X509Certificate[] chain, String authType) {}
28 |
29 | @SuppressLint("TrustAllX509TrustManager")
30 | @Override
31 | public void checkServerTrusted(X509Certificate[] chain, String authType) {}
32 |
33 | @Override
34 | public X509Certificate[] getAcceptedIssuers() {
35 | return new X509Certificate[] {};
36 | }
37 | };
38 |
39 | public static Certificate parseCertificate(String cert) {
40 | try {
41 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
42 |
43 | return certificateFactory.generateCertificate(Utils.stringToInputStream(cert));
44 | } catch (Exception e) {
45 | throw new IllegalArgumentException("certificate is invalid");
46 | }
47 | }
48 |
49 | public static void applySslSettings(OkHttpClient.Builder builder, SSLSettings settings) {
50 | // Modified from ApiClient.applySslSettings in the client package.
51 |
52 | try {
53 | if (!settings.validateSSL) {
54 | SSLContext context = SSLContext.getInstance("TLS");
55 | context.init(
56 | new KeyManager[] {}, new TrustManager[] {trustAll}, new SecureRandom());
57 | builder.sslSocketFactory(context.getSocketFactory(), trustAll);
58 | builder.hostnameVerifier((a, b) -> true);
59 | return;
60 | }
61 |
62 | if (settings.cert != null) {
63 | TrustManager[] trustManagers = certToTrustManager(settings.cert);
64 |
65 | if (trustManagers != null && trustManagers.length > 0) {
66 | SSLContext context = SSLContext.getInstance("TLS");
67 | context.init(new KeyManager[] {}, trustManagers, new SecureRandom());
68 | builder.sslSocketFactory(
69 | context.getSocketFactory(), (X509TrustManager) trustManagers[0]);
70 | }
71 | }
72 | } catch (Exception e) {
73 | // We shouldn't have issues since the cert is verified on login.
74 | Log.e("Failed to apply SSL settings", e);
75 | }
76 | }
77 |
78 | private static TrustManager[] certToTrustManager(String cert) throws GeneralSecurityException {
79 | CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
80 | Collection extends Certificate> certificates =
81 | certificateFactory.generateCertificates(Utils.stringToInputStream(cert));
82 | if (certificates.isEmpty()) {
83 | throw new IllegalArgumentException("expected non-empty set of trusted certificates");
84 | }
85 | KeyStore caKeyStore = newEmptyKeyStore();
86 | int index = 0;
87 | for (Certificate certificate : certificates) {
88 | String certificateAlias = "ca" + Integer.toString(index++);
89 | caKeyStore.setCertificateEntry(certificateAlias, certificate);
90 | }
91 | TrustManagerFactory trustManagerFactory =
92 | TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
93 | trustManagerFactory.init(caKeyStore);
94 | return trustManagerFactory.getTrustManagers();
95 | }
96 |
97 | private static KeyStore newEmptyKeyStore() throws GeneralSecurityException {
98 | try {
99 | KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
100 | keyStore.load(null, null);
101 | return keyStore;
102 | } catch (IOException e) {
103 | throw new AssertionError(e);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/api/ClientFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.api;
2 |
3 | import com.github.gotify.SSLSettings;
4 | import com.github.gotify.Settings;
5 | import com.github.gotify.client.ApiClient;
6 | import com.github.gotify.client.api.UserApi;
7 | import com.github.gotify.client.api.VersionApi;
8 | import com.github.gotify.client.auth.ApiKeyAuth;
9 | import com.github.gotify.client.auth.HttpBasicAuth;
10 |
11 | public class ClientFactory {
12 | public static com.github.gotify.client.ApiClient unauthorized(
13 | String baseUrl, SSLSettings sslSettings) {
14 | return defaultClient(new String[0], baseUrl + "/", sslSettings);
15 | }
16 |
17 | public static ApiClient basicAuth(
18 | String baseUrl, SSLSettings sslSettings, String username, String password) {
19 | ApiClient client = defaultClient(new String[] {"basicAuth"}, baseUrl + "/", sslSettings);
20 | HttpBasicAuth auth = (HttpBasicAuth) client.getApiAuthorizations().get("basicAuth");
21 | auth.setUsername(username);
22 | auth.setPassword(password);
23 | return client;
24 | }
25 |
26 | public static ApiClient clientToken(String baseUrl, SSLSettings sslSettings, String token) {
27 | ApiClient client =
28 | defaultClient(new String[] {"clientTokenHeader"}, baseUrl + "/", sslSettings);
29 | ApiKeyAuth tokenAuth = (ApiKeyAuth) client.getApiAuthorizations().get("clientTokenHeader");
30 | tokenAuth.setApiKey(token);
31 | return client;
32 | }
33 |
34 | public static VersionApi versionApi(String baseUrl, SSLSettings sslSettings) {
35 | return unauthorized(baseUrl, sslSettings).createService(VersionApi.class);
36 | }
37 |
38 | public static UserApi userApiWithToken(Settings settings) {
39 | return clientToken(settings.url(), settings.sslSettings(), settings.token())
40 | .createService(UserApi.class);
41 | }
42 |
43 | private static ApiClient defaultClient(
44 | String[] authentications, String baseUrl, SSLSettings sslSettings) {
45 | ApiClient client = new ApiClient(authentications);
46 | CertUtils.applySslSettings(client.getOkBuilder(), sslSettings);
47 | client.getAdapterBuilder().baseUrl(baseUrl);
48 | return client;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/init/BootCompletedReceiver.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.init;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.os.Build;
7 | import com.github.gotify.Settings;
8 | import com.github.gotify.service.WebSocketService;
9 |
10 | public class BootCompletedReceiver extends BroadcastReceiver {
11 |
12 | @Override
13 | public void onReceive(Context context, Intent intent) {
14 | Settings settings = new Settings(context);
15 |
16 | if (!settings.tokenExists()) {
17 | return;
18 | }
19 |
20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
21 | context.startForegroundService(new Intent(context, WebSocketService.class));
22 | } else {
23 | context.startService(new Intent(context, WebSocketService.class));
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/log/Format.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.log;
2 |
3 | import android.content.Context;
4 | import com.hypertrack.hyperlog.LogFormat;
5 | import java.util.Locale;
6 |
7 | public class Format extends LogFormat {
8 | Format(Context context) {
9 | super(context);
10 | }
11 |
12 | @Override
13 | public String getFormattedLogMessage(
14 | String logLevelName,
15 | String tag,
16 | String message,
17 | String timeStamp,
18 | String senderName,
19 | String osVersion,
20 | String deviceUUID) {
21 | return String.format(Locale.ENGLISH, "%s %s: %s", timeStamp, logLevelName, message);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/log/Log.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.log;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 | import com.hypertrack.hyperlog.HyperLog;
6 | import java.util.Collections;
7 | import java.util.List;
8 |
9 | public class Log {
10 | private static String TAG = "gotify";
11 |
12 | public static void init(Context content) {
13 | HyperLog.initialize(content, new Format(content));
14 | HyperLog.setLogLevel(android.util.Log.INFO); // TODO configurable
15 | }
16 |
17 | public static String get() {
18 | List logs = HyperLog.getDeviceLogsAsStringList(false);
19 | Collections.reverse(logs);
20 | return TextUtils.join("\n", logs.subList(0, Math.min(200, logs.size())));
21 | }
22 |
23 | public static void e(String message) {
24 | HyperLog.e(TAG, message);
25 | }
26 |
27 | public static void e(String message, Throwable e) {
28 | HyperLog.e(TAG, message + '\n' + android.util.Log.getStackTraceString(e));
29 | }
30 |
31 | public static void i(String message) {
32 | HyperLog.i(TAG, message);
33 | }
34 |
35 | public static void i(String message, Throwable e) {
36 | HyperLog.i(TAG, message + '\n' + android.util.Log.getStackTraceString(e));
37 | }
38 |
39 | public static void w(String message) {
40 | HyperLog.w(TAG, message);
41 | }
42 |
43 | public static void w(String message, Throwable e) {
44 | HyperLog.w(TAG, message + '\n' + android.util.Log.getStackTraceString(e));
45 | }
46 |
47 | public static void clear() {
48 | HyperLog.deleteLogs();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/log/LogsActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.log;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.os.AsyncTask;
7 | import android.os.Bundle;
8 | import android.os.Handler;
9 | import android.view.Menu;
10 | import android.view.MenuItem;
11 | import android.widget.TextView;
12 | import androidx.appcompat.app.ActionBar;
13 | import androidx.appcompat.app.AppCompatActivity;
14 | import com.github.gotify.R;
15 | import com.github.gotify.Utils;
16 |
17 | public class LogsActivity extends AppCompatActivity {
18 |
19 | private Handler handler = new Handler();
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | setContentView(R.layout.activity_logs);
25 | Log.i("Entering " + getClass().getSimpleName());
26 | updateLogs();
27 | setSupportActionBar(findViewById(R.id.toolbar));
28 | ActionBar actionBar = getSupportActionBar();
29 | if (actionBar != null) {
30 | actionBar.setDisplayHomeAsUpEnabled(true);
31 | actionBar.setDisplayShowCustomEnabled(true);
32 | }
33 | }
34 |
35 | private void updateLogs() {
36 | new RefreshLogs().execute();
37 | if (!isDestroyed()) {
38 | handler.postDelayed(this::updateLogs, 5000);
39 | }
40 | }
41 |
42 | @Override
43 | public boolean onCreateOptionsMenu(Menu menu) {
44 | getMenuInflater().inflate(R.menu.logs_action, menu);
45 | return super.onCreateOptionsMenu(menu);
46 | }
47 |
48 | @Override
49 | public boolean onOptionsItemSelected(MenuItem item) {
50 | if (item.getItemId() == android.R.id.home) {
51 | finish();
52 | }
53 | if (item.getItemId() == R.id.action_delete_logs) {
54 | Log.clear();
55 | }
56 | if (item.getItemId() == R.id.action_copy_logs) {
57 | TextView content = findViewById(R.id.log_content);
58 | ClipboardManager clipboardManager =
59 | (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
60 | ClipData clipData = ClipData.newPlainText("GotifyLog", content.getText().toString());
61 | clipboardManager.setPrimaryClip(clipData);
62 | Utils.showSnackBar(this, getString(R.string.logs_copied));
63 | }
64 | return super.onOptionsItemSelected(item);
65 | }
66 |
67 | class RefreshLogs extends AsyncTask {
68 |
69 | @Override
70 | protected String doInBackground(Void... voids) {
71 | return com.github.gotify.log.Log.get();
72 | }
73 |
74 | @Override
75 | protected void onPostExecute(String s) {
76 | TextView content = findViewById(R.id.log_content);
77 | if (content.getSelectionStart() == content.getSelectionEnd()) {
78 | content.setText(s);
79 | }
80 | super.onPostExecute(s);
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/log/UncaughtExceptionHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.log;
2 |
3 | public class UncaughtExceptionHandler {
4 | public static void registerCurrentThread() {
5 | Thread.setDefaultUncaughtExceptionHandler((t, e) -> Log.e("uncaught exception", e));
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/login/AdvancedDialog.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.login;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.Context;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.CheckBox;
9 | import android.widget.CompoundButton;
10 | import android.widget.TextView;
11 | import androidx.annotation.Nullable;
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 | import com.github.gotify.R;
15 |
16 | class AdvancedDialog {
17 |
18 | private Context context;
19 | private ViewHolder holder;
20 | private CompoundButton.OnCheckedChangeListener onCheckedChangeListener;
21 | private Runnable onClickSelectCaCertificate;
22 | private Runnable onClickRemoveCaCertificate;
23 |
24 | AdvancedDialog(Context context) {
25 | this.context = context;
26 | }
27 |
28 | AdvancedDialog onDisableSSLChanged(
29 | CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {
30 | this.onCheckedChangeListener = onCheckedChangeListener;
31 | return this;
32 | }
33 |
34 | AdvancedDialog onClickSelectCaCertificate(Runnable onClickSelectCaCertificate) {
35 | this.onClickSelectCaCertificate = onClickSelectCaCertificate;
36 | return this;
37 | }
38 |
39 | AdvancedDialog onClickRemoveCaCertificate(Runnable onClickRemoveCaCertificate) {
40 | this.onClickRemoveCaCertificate = onClickRemoveCaCertificate;
41 | return this;
42 | }
43 |
44 | AdvancedDialog show(boolean disableSSL, @Nullable String selectedCertificate) {
45 |
46 | View dialogView =
47 | LayoutInflater.from(context).inflate(R.layout.advanced_settings_dialog, null);
48 | holder = new ViewHolder(dialogView);
49 | holder.disableSSL.setChecked(disableSSL);
50 | holder.disableSSL.setOnCheckedChangeListener(onCheckedChangeListener);
51 |
52 | if (selectedCertificate == null) {
53 | showSelectCACertificate();
54 | } else {
55 | showRemoveCACertificate(selectedCertificate);
56 | }
57 |
58 | new AlertDialog.Builder(context)
59 | .setView(dialogView)
60 | .setTitle(R.string.advanced_settings)
61 | .setPositiveButton(context.getString(R.string.done), (ignored, ignored2) -> {})
62 | .show();
63 | return this;
64 | }
65 |
66 | private void showSelectCACertificate() {
67 | holder.toggleCaCert.setText(R.string.select_ca_certificate);
68 | holder.toggleCaCert.setOnClickListener((a) -> onClickSelectCaCertificate.run());
69 | holder.selectedCaCertificate.setText(R.string.no_certificate_selected);
70 | }
71 |
72 | void showRemoveCACertificate(String certificate) {
73 | holder.toggleCaCert.setText(R.string.remove_ca_certificate);
74 | holder.toggleCaCert.setOnClickListener(
75 | (a) -> {
76 | showSelectCACertificate();
77 | onClickRemoveCaCertificate.run();
78 | });
79 | holder.selectedCaCertificate.setText(certificate);
80 | }
81 |
82 | class ViewHolder {
83 | @BindView(R.id.disableSSL)
84 | CheckBox disableSSL;
85 |
86 | @BindView(R.id.toggle_ca_cert)
87 | Button toggleCaCert;
88 |
89 | @BindView(R.id.seleceted_ca_cert)
90 | TextView selectedCaCertificate;
91 |
92 | ViewHolder(View view) {
93 | ButterKnife.bind(this, view);
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/Extras.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages;
2 |
3 | import com.github.gotify.client.model.Message;
4 | import java.util.Map;
5 |
6 | public final class Extras {
7 | private Extras() {}
8 |
9 | public static boolean useMarkdown(Message message) {
10 | return useMarkdown(message.getExtras());
11 | }
12 |
13 | public static boolean useMarkdown(Map extras) {
14 | if (extras == null) {
15 | return false;
16 | }
17 |
18 | Object display = extras.get("client::display");
19 | if (!(display instanceof Map)) {
20 | return false;
21 | }
22 |
23 | return "text/markdown".equals(((Map) display).get("contentType"));
24 | }
25 |
26 | public static T getNestedValue(Class clazz, Message message, String... keys) {
27 | return getNestedValue(clazz, message.getExtras(), keys);
28 | }
29 |
30 | public static T getNestedValue(Class clazz, Map extras, String... keys) {
31 | Object value = extras;
32 |
33 | for (String key : keys) {
34 | if (value == null) {
35 | return null;
36 | }
37 |
38 | if (!(value instanceof Map, ?>)) {
39 | return null;
40 | }
41 |
42 | value = ((Map, ?>) value).get(key);
43 | }
44 |
45 | if (!clazz.isInstance(value)) {
46 | return null;
47 | }
48 |
49 | return clazz.cast(value);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/ApplicationHolder.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import android.app.Activity;
4 | import com.github.gotify.Utils;
5 | import com.github.gotify.api.ApiException;
6 | import com.github.gotify.api.Callback;
7 | import com.github.gotify.client.ApiClient;
8 | import com.github.gotify.client.api.ApplicationApi;
9 | import com.github.gotify.client.model.Application;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | public class ApplicationHolder {
14 | private List state;
15 | private Runnable onUpdate;
16 | private Runnable onUpdateFailed;
17 | private Activity activity;
18 | private ApiClient client;
19 |
20 | public ApplicationHolder(Activity activity, ApiClient client) {
21 | this.activity = activity;
22 | this.client = client;
23 | }
24 |
25 | public void requestIfMissing() {
26 | if (state == null) {
27 | request();
28 | }
29 | }
30 |
31 | public void request() {
32 | client.createService(ApplicationApi.class)
33 | .getApps()
34 | .enqueue(Callback.callInUI(activity, this::onReceiveApps, this::onFailedApps));
35 | }
36 |
37 | private void onReceiveApps(List apps) {
38 | state = apps;
39 | if (onUpdate != null) onUpdate.run();
40 | }
41 |
42 | private void onFailedApps(ApiException e) {
43 | Utils.showSnackBar(activity, "Could not request applications, see logs.");
44 | if (onUpdateFailed != null) onUpdateFailed.run();
45 | }
46 |
47 | public List get() {
48 | return state == null ? Collections.emptyList() : state;
49 | }
50 |
51 | public void onUpdate(Runnable runnable) {
52 | this.onUpdate = runnable;
53 | }
54 |
55 | public void onUpdateFailed(Runnable runnable) {
56 | this.onUpdateFailed = runnable;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageDeletion.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.client.model.Message;
4 |
5 | public final class MessageDeletion {
6 | private final Message message;
7 | private final int allPosition;
8 | private final int appPosition;
9 |
10 | public MessageDeletion(Message message, int allPosition, int appPosition) {
11 | this.message = message;
12 | this.allPosition = allPosition;
13 | this.appPosition = appPosition;
14 | }
15 |
16 | public int getAllPosition() {
17 | return allPosition;
18 | }
19 |
20 | public int getAppPosition() {
21 | return appPosition;
22 | }
23 |
24 | public Message getMessage() {
25 | return message;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageFacade.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.client.api.MessageApi;
4 | import com.github.gotify.client.model.Message;
5 | import com.github.gotify.client.model.PagedMessages;
6 | import java.util.List;
7 |
8 | public class MessageFacade {
9 | private final ApplicationHolder applicationHolder;
10 | private final MessageRequester requester;
11 | private final MessageStateHolder state;
12 | private final MessageImageCombiner combiner;
13 |
14 | public MessageFacade(MessageApi api, ApplicationHolder applicationHolder) {
15 | this.applicationHolder = applicationHolder;
16 | this.requester = new MessageRequester(api);
17 | this.combiner = new MessageImageCombiner();
18 | this.state = new MessageStateHolder();
19 | }
20 |
21 | public synchronized List get(long appId) {
22 | return combiner.combine(state.state(appId).messages, applicationHolder.get());
23 | }
24 |
25 | public synchronized void addMessages(List messages) {
26 | for (Message message : messages) {
27 | state.newMessage(message);
28 | }
29 | }
30 |
31 | public synchronized List loadMore(long appId) {
32 | MessageState state = this.state.state(appId);
33 | if (state.hasNext || !state.loaded) {
34 | PagedMessages pagedMessages = requester.loadMore(state);
35 | this.state.newMessages(appId, pagedMessages);
36 | }
37 | return get(appId);
38 | }
39 |
40 | public synchronized void loadMoreIfNotPresent(long appId) {
41 | MessageState state = this.state.state(appId);
42 | if (!state.loaded) {
43 | loadMore(appId);
44 | }
45 | }
46 |
47 | public synchronized void clear() {
48 | this.state.clear();
49 | }
50 |
51 | public long getLastReceivedMessage() {
52 | return state.getLastReceivedMessage();
53 | }
54 |
55 | public synchronized void deleteLocal(Message message) {
56 | // If there is already a deletion pending, that one should be executed before scheduling the
57 | // next deletion.
58 | if (this.state.deletionPending()) commitDelete();
59 | this.state.deleteMessage(message);
60 | }
61 |
62 | public synchronized void commitDelete() {
63 | if (this.state.deletionPending()) {
64 | MessageDeletion deletion = this.state.purgePendingDeletion();
65 | this.requester.asyncRemoveMessage(deletion.getMessage());
66 | }
67 | }
68 |
69 | public synchronized MessageDeletion undoDeleteLocal() {
70 | return this.state.undoPendingDeletion();
71 | }
72 |
73 | public synchronized boolean deleteAll(long appId) {
74 | boolean success = this.requester.deleteAll(appId);
75 | this.state.deleteAll(appId);
76 | return success;
77 | }
78 |
79 | public synchronized boolean canLoadMore(long appId) {
80 | return state.state(appId).hasNext;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageImageCombiner.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.client.model.Application;
4 | import com.github.gotify.client.model.Message;
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | public class MessageImageCombiner {
11 |
12 | List combine(List messages, List applications) {
13 | Map appIdToImage = appIdToImage(applications);
14 |
15 | List result = new ArrayList<>();
16 |
17 | for (Message message : messages) {
18 | MessageWithImage messageWithImage = new MessageWithImage();
19 |
20 | messageWithImage.message = message;
21 | messageWithImage.image = appIdToImage.get(message.getAppid());
22 |
23 | result.add(messageWithImage);
24 | }
25 |
26 | return result;
27 | }
28 |
29 | public static Map appIdToImage(List applications) {
30 | Map map = new ConcurrentHashMap<>();
31 | for (Application app : applications) {
32 | map.put(app.getId(), app.getImage());
33 | }
34 | return map;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageRequester.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.api.Api;
4 | import com.github.gotify.api.ApiException;
5 | import com.github.gotify.api.Callback;
6 | import com.github.gotify.client.api.MessageApi;
7 | import com.github.gotify.client.model.Message;
8 | import com.github.gotify.client.model.PagedMessages;
9 | import com.github.gotify.log.Log;
10 |
11 | class MessageRequester {
12 | private static final Integer LIMIT = 100;
13 | private MessageApi messageApi;
14 |
15 | MessageRequester(MessageApi messageApi) {
16 | this.messageApi = messageApi;
17 | }
18 |
19 | PagedMessages loadMore(MessageState state) {
20 | try {
21 | Log.i("Loading more messages for " + state.appId);
22 | if (MessageState.ALL_MESSAGES == state.appId) {
23 | return Api.execute(messageApi.getMessages(LIMIT, state.nextSince));
24 | } else {
25 | return Api.execute(messageApi.getAppMessages(state.appId, LIMIT, state.nextSince));
26 | }
27 | } catch (ApiException apiException) {
28 | Log.e("failed requesting messages", apiException);
29 | return null;
30 | }
31 | }
32 |
33 | void asyncRemoveMessage(Message message) {
34 | Log.i("Removing message with id " + message.getId());
35 | messageApi.deleteMessage(message.getId()).enqueue(Callback.call());
36 | }
37 |
38 | boolean deleteAll(Long appId) {
39 | try {
40 | Log.i("Deleting all messages for " + appId);
41 | if (MessageState.ALL_MESSAGES == appId) {
42 | Api.execute(messageApi.deleteMessages());
43 | } else {
44 | Api.execute(messageApi.deleteAppMessages(appId));
45 | }
46 | return true;
47 | } catch (ApiException e) {
48 | Log.e("Could not delete messages", e);
49 | return false;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageState.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.client.model.Message;
4 | import java.util.ArrayList;
5 | import java.util.List;
6 |
7 | public class MessageState {
8 | public static final long ALL_MESSAGES = -1;
9 |
10 | long appId;
11 | boolean loaded;
12 | boolean hasNext;
13 | long nextSince = 0;
14 | List messages = new ArrayList<>();
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/messages/provider/MessageWithImage.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.messages.provider;
2 |
3 | import com.github.gotify.client.model.Message;
4 |
5 | public class MessageWithImage {
6 | public Message message;
7 | public String image;
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/picasso/PicassoDataRequestHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.picasso;
2 |
3 | import android.graphics.Bitmap;
4 | import android.graphics.BitmapFactory;
5 | import android.util.Base64;
6 | import com.github.gotify.log.Log;
7 | import com.squareup.picasso.Picasso;
8 | import com.squareup.picasso.Request;
9 | import com.squareup.picasso.RequestHandler;
10 |
11 | /**
12 | * Adapted from https://github.com/square/picasso/issues/1395#issuecomment-220929377 By
13 | * https://github.com/SmartDengg
14 | */
15 | public class PicassoDataRequestHandler extends RequestHandler {
16 |
17 | private static final String DATA_SCHEME = "data";
18 |
19 | @Override
20 | public boolean canHandleRequest(Request data) {
21 | String scheme = data.uri.getScheme();
22 | return DATA_SCHEME.equalsIgnoreCase(scheme);
23 | }
24 |
25 | @Override
26 | public Result load(Request request, int networkPolicy) {
27 | String uri = request.uri.toString();
28 | String imageDataBytes = uri.substring(uri.indexOf(",") + 1);
29 | byte[] bytes = Base64.decode(imageDataBytes.getBytes(), Base64.DEFAULT);
30 | Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
31 |
32 | if (bitmap == null) {
33 | String show = uri.length() > 50 ? uri.substring(0, 49) + "..." : uri;
34 | RuntimeException malformed = new RuntimeException("Malformed data uri: " + show);
35 | Log.e("Could not load image", malformed);
36 | throw malformed;
37 | }
38 |
39 | return new Result(bitmap, Picasso.LoadedFrom.NETWORK);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/picasso/PicassoHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.picasso;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.BitmapFactory;
6 | import com.github.gotify.R;
7 | import com.github.gotify.Settings;
8 | import com.github.gotify.Utils;
9 | import com.github.gotify.api.Callback;
10 | import com.github.gotify.api.CertUtils;
11 | import com.github.gotify.api.ClientFactory;
12 | import com.github.gotify.client.api.ApplicationApi;
13 | import com.github.gotify.log.Log;
14 | import com.github.gotify.messages.provider.MessageImageCombiner;
15 | import com.squareup.picasso.OkHttp3Downloader;
16 | import com.squareup.picasso.Picasso;
17 | import java.io.File;
18 | import java.io.IOException;
19 | import java.util.Map;
20 | import java.util.concurrent.ConcurrentHashMap;
21 | import okhttp3.Cache;
22 | import okhttp3.OkHttpClient;
23 |
24 | public class PicassoHandler {
25 |
26 | private static final int PICASSO_CACHE_SIZE = 50 * 1024 * 1024; // 50 MB
27 | private static final String PICASSO_CACHE_SUBFOLDER = "picasso-cache";
28 |
29 | private Context context;
30 | private Settings settings;
31 |
32 | private Cache picassoCache;
33 |
34 | private Picasso picasso;
35 | private Map appIdToAppImage = new ConcurrentHashMap<>();
36 |
37 | public PicassoHandler(Context context, Settings settings) {
38 | this.context = context;
39 | this.settings = settings;
40 |
41 | picassoCache =
42 | new Cache(
43 | new File(context.getCacheDir(), PICASSO_CACHE_SUBFOLDER),
44 | PICASSO_CACHE_SIZE);
45 | picasso = makePicasso();
46 | }
47 |
48 | private Picasso makePicasso() {
49 | OkHttpClient.Builder builder = new OkHttpClient.Builder();
50 | builder.cache(picassoCache);
51 | CertUtils.applySslSettings(builder, settings.sslSettings());
52 | OkHttp3Downloader downloader = new OkHttp3Downloader(builder.build());
53 | return new Picasso.Builder(context)
54 | .addRequestHandler(new PicassoDataRequestHandler())
55 | .downloader(downloader)
56 | .build();
57 | }
58 |
59 | public Bitmap getIcon(Long appId) {
60 | if (appId == -1) {
61 | return BitmapFactory.decodeResource(context.getResources(), R.drawable.gotify);
62 | }
63 |
64 | try {
65 | return picasso.load(
66 | Utils.resolveAbsoluteUrl(
67 | settings.url() + "/", appIdToAppImage.get(appId)))
68 | .get();
69 | } catch (IOException e) {
70 | Log.e("Could not load image for notification", e);
71 | }
72 | return BitmapFactory.decodeResource(context.getResources(), R.drawable.gotify);
73 | }
74 |
75 | public void updateAppIds() {
76 | ClientFactory.clientToken(settings.url(), settings.sslSettings(), settings.token())
77 | .createService(ApplicationApi.class)
78 | .getApps()
79 | .enqueue(
80 | Callback.call(
81 | (apps) -> {
82 | appIdToAppImage.clear();
83 | appIdToAppImage.putAll(MessageImageCombiner.appIdToImage(apps));
84 | },
85 | (t) -> {
86 | appIdToAppImage.clear();
87 | }));
88 | }
89 |
90 | public Picasso get() {
91 | return picasso;
92 | }
93 |
94 | public void evict() throws IOException {
95 | picassoCache.evictAll();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/service/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.github.gotify.service
2 |
3 | /**
4 | * Constants as defined on the specs
5 | * https://github.com/UnifiedPush/UP-spec/blob/main/specifications.md
6 | */
7 |
8 | const val ACTION_NEW_ENDPOINT = "org.unifiedpush.android.connector.NEW_ENDPOINT"
9 | const val ACTION_REGISTRATION_FAILED = "org.unifiedpush.android.connector.REGISTRATION_FAILED"
10 | const val ACTION_REGISTRATION_REFUSED = "org.unifiedpush.android.connector.REGISTRATION_REFUSED"
11 | const val ACTION_UNREGISTERED = "org.unifiedpush.android.connector.UNREGISTERED"
12 | const val ACTION_MESSAGE = "org.unifiedpush.android.connector.MESSAGE"
13 |
14 | const val ACTION_REGISTER = "org.unifiedpush.android.distributor.REGISTER"
15 | const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER"
16 | const val ACTION_MESSAGE_ACK = "org.unifiedpush.android.distributor.MESSAGE_ACK"
17 |
18 | const val EXTRA_APPLICATION = "application"
19 | const val EXTRA_TOKEN = "token"
20 | const val EXTRA_ENDPOINT = "endpoint"
21 | const val EXTRA_MESSAGE = "message"
22 | const val EXTRA_MESSAGE_ID = "id"
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/service/PushNotification.kt:
--------------------------------------------------------------------------------
1 | package com.github.gotify.service
2 |
3 |
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 |
8 | /**
9 | * These functions are used to send messages to other apps
10 | */
11 |
12 | fun sendMessage(context: Context, token: String, message: String){
13 | val application = getApp(context, token)
14 | if (application.isNullOrBlank()) {
15 | return
16 | }
17 | val broadcastIntent = Intent()
18 | broadcastIntent.`package` = application
19 | broadcastIntent.action = ACTION_MESSAGE
20 | broadcastIntent.putExtra(EXTRA_TOKEN, token)
21 | broadcastIntent.putExtra(EXTRA_MESSAGE, message)
22 | context.sendBroadcast(broadcastIntent)
23 | }
24 |
25 | fun sendEndpoint(context: Context, token: String, endpoint: String) {
26 | val application = getApp(context, token)
27 | if (application.isNullOrBlank()) {
28 | return
29 | }
30 | val broadcastIntent = Intent()
31 | broadcastIntent.`package` = application
32 | broadcastIntent.action = ACTION_NEW_ENDPOINT
33 | broadcastIntent.putExtra(EXTRA_TOKEN, token)
34 | broadcastIntent.putExtra(EXTRA_ENDPOINT, endpoint)
35 | context.sendBroadcast(broadcastIntent)
36 | }
37 |
38 | fun sendUnregistered(context: Context, token: String){
39 | val application = getApp(context, token)
40 | if (application.isNullOrBlank()) {
41 | return
42 | }
43 | val broadcastIntent = Intent()
44 | broadcastIntent.`package` = application
45 | broadcastIntent.action = ACTION_UNREGISTERED
46 | broadcastIntent.putExtra(EXTRA_TOKEN, token)
47 | context.sendBroadcast(broadcastIntent)
48 | }
49 |
50 | fun sendRegistrationFailed(context: Context, application: String, token: String, message: String){
51 | val broadcastIntent = Intent()
52 | broadcastIntent.`package` = application
53 | broadcastIntent.action = ACTION_REGISTRATION_FAILED
54 | broadcastIntent.putExtra(EXTRA_TOKEN, token)
55 | broadcastIntent.putExtra(EXTRA_MESSAGE, message)
56 | context.sendBroadcast(broadcastIntent)
57 | }
58 |
59 | fun sendRegistrationRefused(context: Context, application: String, token: String, message: String) {
60 | val broadcastIntent = Intent()
61 | broadcastIntent.`package` = application
62 | broadcastIntent.action = ACTION_REGISTRATION_REFUSED
63 | broadcastIntent.putExtra(EXTRA_TOKEN, token)
64 | broadcastIntent.putExtra(EXTRA_MESSAGE, message)
65 | context.sendBroadcast(broadcastIntent)
66 | }
67 |
68 | fun getApp(context: Context, token: String): String?{
69 | val db = MessagingDatabase(context)
70 | val application = db.getPackageName(token)
71 | db.close()
72 | if (application.isBlank()) {
73 | Log.w("getApp", "No app found for $token")
74 | return null
75 | }
76 | return application
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/service/RegisterBroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.github.gotify.service
2 |
3 | import android.content.BroadcastReceiver
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.util.Log
7 | import com.github.gotify.Settings
8 | import com.github.gotify.api.Api
9 | import com.github.gotify.api.ApiException
10 | import com.github.gotify.api.ClientFactory
11 | import com.github.gotify.client.api.ApplicationApi
12 | import java.text.SimpleDateFormat
13 | import java.util.Date
14 | import kotlin.concurrent.thread
15 |
16 | /**
17 | * THIS SERVICE IS USED BY OTHER APPS TO REGISTER
18 | */
19 |
20 | class RegisterBroadcastReceiver: BroadcastReceiver() {
21 |
22 | private lateinit var settings: Settings
23 |
24 | private fun registerApp(context: Context?, db: MessagingDatabase, application: String, connectorToken: String) {
25 | if (application.isBlank()) {
26 | Log.w("RegisterService","Trying to register an app without packageName")
27 | return
28 | }
29 | Log.i("RegisterService","registering $application token: $connectorToken")
30 | // The app is registered with the same token : we re-register it
31 | // the client may need its endpoint again
32 | if (db.isRegistered(connectorToken)) {
33 | Log.i("RegisterService","$application already registered")
34 | return
35 | }
36 |
37 | val app = createApp(application)
38 | if(app == null){
39 | val message = "Cannot create a new app to register"
40 | Log.w("RegisterService", message)
41 | sendRegistrationFailed(context!!,application,connectorToken,message)
42 | return
43 | }
44 | db.registerApp(application, app.id, app.token, connectorToken)
45 | }
46 |
47 | private fun createApp(appName: String): com.github.gotify.client.model.Application? {
48 | val client = ClientFactory.clientToken(settings.url(), settings.sslSettings(), settings.token())
49 | val app = com.github.gotify.client.model.Application()
50 | app.name = appName
51 | val date = SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(Date())
52 | app.description = "(auto) $date"
53 | try {
54 | Log.i("RegisterService","Creating app")
55 | return Api.execute(client.createService(ApplicationApi::class.java).createApp(app))
56 | } catch (e: ApiException) {
57 | Log.e("RegisterService","Could not create app.", e)
58 | }
59 | return null
60 | }
61 |
62 | private fun deleteApp(db: MessagingDatabase, token: String) {
63 | val client = ClientFactory.clientToken(settings.url(), settings.sslSettings(), settings.token())
64 | try {
65 | val appId = db.getAppId(token)
66 | Log.i("RegisterService","Deleting app with appId=$appId")
67 | Api.execute(client.createService(ApplicationApi::class.java).deleteApp(appId))
68 | db.unregisterApp(token)
69 | } catch (e: ApiException) {
70 | Log.e("RegisterService","Could not delete app.", e)
71 | }
72 | }
73 |
74 | override fun onReceive(context: Context?, intent: Intent?) {
75 | settings = Settings(context)
76 | when (intent!!.action) {
77 | ACTION_REGISTER ->{
78 | val db = MessagingDatabase(context!!)
79 | Log.i("Register","REGISTER")
80 | val connectorToken = intent.getStringExtra(EXTRA_TOKEN)?: ""
81 | val application = intent.getStringExtra(EXTRA_APPLICATION)?: ""
82 | thread(start = true) {
83 | registerApp(context, db, application, connectorToken)
84 | Log.i("RegisterService","Registration is finished")
85 | }.join()
86 | val token = db.getGotifyToken(connectorToken)
87 | db.close()
88 | val endpoint = settings.url() +
89 | "/UP?token=$token"
90 | sendEndpoint(context, connectorToken, endpoint)
91 | }
92 | ACTION_UNREGISTER ->{
93 | Log.i("Register","UNREGISTER")
94 | val token = intent.getStringExtra(EXTRA_TOKEN)?: ""
95 | thread(start = true) {
96 | val db = MessagingDatabase(context!!)
97 | deleteApp(db, token)
98 | db.close()
99 | Log.i("RegisterService","Unregistration is finished")
100 | }
101 | sendUnregistered(context!!, token)
102 | }
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/settings/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.settings;
2 |
3 | import android.content.SharedPreferences;
4 | import android.os.Bundle;
5 | import android.view.MenuItem;
6 | import androidx.annotation.NonNull;
7 | import androidx.appcompat.app.ActionBar;
8 | import androidx.appcompat.app.AppCompatActivity;
9 | import androidx.preference.PreferenceFragmentCompat;
10 | import androidx.preference.PreferenceManager;
11 | import com.github.gotify.R;
12 |
13 | public class SettingsActivity extends AppCompatActivity
14 | implements SharedPreferences.OnSharedPreferenceChangeListener {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.settings_activity);
20 | getSupportFragmentManager()
21 | .beginTransaction()
22 | .replace(R.id.settings, new SettingsFragment())
23 | .commit();
24 | setSupportActionBar(findViewById(R.id.toolbar));
25 | ActionBar actionBar = getSupportActionBar();
26 | if (actionBar != null) {
27 | actionBar.setDisplayHomeAsUpEnabled(true);
28 | actionBar.setDisplayShowCustomEnabled(true);
29 | }
30 | SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
31 | sharedPreferences.registerOnSharedPreferenceChangeListener(this);
32 | }
33 |
34 | @Override
35 | public boolean onOptionsItemSelected(@NonNull MenuItem item) {
36 | if (item.getItemId() == android.R.id.home) {
37 | finish();
38 | }
39 | return super.onOptionsItemSelected(item);
40 | }
41 |
42 | @Override
43 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
44 | if (getString(R.string.setting_key_theme).equals(key)) {
45 | ThemeHelper.setTheme(
46 | this, sharedPreferences.getString(key, getString(R.string.theme_default)));
47 | }
48 | }
49 |
50 | public static class SettingsFragment extends PreferenceFragmentCompat {
51 | @Override
52 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
53 | setPreferencesFromResource(R.xml.root_preferences, rootKey);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/gotify/settings/ThemeHelper.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.settings;
2 |
3 | import android.content.Context;
4 | import android.os.Build;
5 | import androidx.appcompat.app.AppCompatDelegate;
6 | import com.github.gotify.R;
7 |
8 | public final class ThemeHelper {
9 | private ThemeHelper() {}
10 |
11 | public static void setTheme(Context context, String newTheme) {
12 | AppCompatDelegate.setDefaultNightMode(ofKey(context, newTheme));
13 | }
14 |
15 | private static int ofKey(Context context, String newTheme) {
16 | if (context.getString(R.string.theme_dark).equals(newTheme)) {
17 | return AppCompatDelegate.MODE_NIGHT_YES;
18 | }
19 | if (context.getString(R.string.theme_light).equals(newTheme)) {
20 | return AppCompatDelegate.MODE_NIGHT_NO;
21 | }
22 | if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
23 | return AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY;
24 | }
25 | return AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable-hdpi/ic_gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable-mdpi/ic_gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable-xhdpi/ic_gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable-xxhdpi/ic_gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable-xxxhdpi/ic_gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/drawable/gotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_alarm.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bug_report.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_dashboard.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_placeholder.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_power_setting.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_refresh.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_send.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/side_nav_bar.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_logs.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
19 |
20 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_messages.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
20 |
21 |
27 |
28 |
32 |
33 |
39 |
40 |
41 |
47 |
48 |
62 |
63 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
86 |
87 |
88 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/advanced_settings_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_bar_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/message_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
31 |
32 |
41 |
42 |
43 |
54 |
55 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
28 |
29 |
38 |
39 |
48 |
49 |
57 |
58 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/settings_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
19 |
20 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/logs_action.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/messages_action.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/messages_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values-notnight/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #000000
5 | #3867d6
6 | #1c49b4
7 | #434343
8 | #E74C3C
9 | #FFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @string/theme_light
5 | @string/theme_dark
6 | @string/theme_default
7 |
8 | Light
9 | Dark
10 | System Default
11 |
12 |
13 | @string/time_format_entry_absolute
14 | @string/time_format_entry_relative
15 |
16 | Absolute time
17 | Relative time
18 |
19 | @string/time_format_value_absolute
20 | @string/time_format_value_relative
21 |
22 | time_format_absolute
23 | time_format_relative
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #FFFFFF
5 | #3867d6
6 | #1c49b4
7 | #797979
8 | #E74C3C
9 | #FFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 8dp
6 | 196dp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #00436D
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
13 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
11 |
12 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.5.10'
5 |
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:4.0.2'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | plugins {
20 | id 'org.hidetake.swagger.generator' version '2.14.0'
21 | }
22 |
23 | ext {
24 | gotifyVersion = 'master'
25 | specLocation = "$buildDir/gotify.spec.json"
26 | }
27 |
28 | allprojects {
29 | repositories {
30 | google()
31 | jcenter()
32 | }
33 | }
34 |
35 | task clean(type: Delete) {
36 | delete rootProject.buildDir
37 | }
38 |
39 | static def download(String url, String filename ) {
40 | new URL( url ).openConnection().with { conn ->
41 | new File( filename ).withOutputStream { out ->
42 | conn.inputStream.with { inp ->
43 | out << inp
44 | inp.close()
45 | }
46 | }
47 | }
48 | }
49 | task downloadSpec {
50 | inputs.property 'version', gotifyVersion
51 | doFirst {
52 | buildDir.mkdirs()
53 | download("https://raw.githubusercontent.com/gotify/server/${gotifyVersion}/docs/spec.json", specLocation)
54 | }
55 | }
56 |
57 | swaggerSources {
58 | gotify {
59 | inputFile = specLocation as File
60 | code {
61 | configFile = "$projectDir/swagger.config.json" as File
62 | language = 'java'
63 | outputDir = "$projectDir/client" as File
64 | }
65 | }
66 | }
67 |
68 | dependencies {
69 | swaggerCodegen 'io.swagger:swagger-codegen-cli:2.3.1'
70 | }
71 |
72 | generateSwaggerCode.dependsOn downloadSpec
73 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 |
3 | # Mobile Tools for Java (J2ME)
4 | .mtj.tmp/
5 |
6 | # Package Files #
7 | *.jar
8 | *.war
9 | *.ear
10 |
11 | # exclude jar for gradle wrapper
12 | !gradle/wrapper/*.jar
13 |
14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
15 | hs_err_pid*
16 |
17 | # build files
18 | **/target
19 | target
20 | .gradle
21 | build
22 |
--------------------------------------------------------------------------------
/client/.swagger-codegen-ignore:
--------------------------------------------------------------------------------
1 | # Swagger Codegen Ignore
2 | # Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen
3 |
4 | # Use this file to prevent files from being overwritten by the generator.
5 | # The patterns follow closely to .gitignore or .dockerignore.
6 |
7 | # As an example, the C# client generator defines ApiClient.cs.
8 | # You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line:
9 | #ApiClient.cs
10 |
11 | # You can match any string of characters against a directory, file or extension with a single asterisk (*):
12 | #foo/*/qux
13 | # The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14 |
15 | # You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16 | #foo/**/qux
17 | # This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18 |
19 | # You can also negate patterns with an exclamation (!).
20 | # For example, you can ignore all files in a docs folder with the file extension .md:
21 | #docs/*.md
22 | # Then explicitly reverse the ignore rule for a single file:
23 | #!docs/README.md
24 |
--------------------------------------------------------------------------------
/client/.swagger-codegen/VERSION:
--------------------------------------------------------------------------------
1 | 2.3.1
--------------------------------------------------------------------------------
/client/.travis.yml:
--------------------------------------------------------------------------------
1 | #
2 | # Generated by: https://github.com/swagger-api/swagger-codegen.git
3 | #
4 | language: java
5 | jdk:
6 | - oraclejdk8
7 | - oraclejdk7
8 | before_install:
9 | # ensure gradlew has proper permission
10 | - chmod a+x ./gradlew
11 | script:
12 | # test using maven
13 | - mvn test
14 | # uncomment below to test using gradle
15 | # - gradle test
16 | # uncomment below to test using sbt
17 | # - sbt test
18 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # swagger-java-client
2 |
3 | ## Requirements
4 |
5 | Building the API client library requires [Maven](https://maven.apache.org/) to be installed.
6 |
7 | ## Installation & Usage
8 |
9 | To install the API client library to your local Maven repository, simply execute:
10 |
11 | ```shell
12 | mvn install
13 | ```
14 |
15 | To deploy it to a remote Maven repository instead, configure the settings of the repository and execute:
16 |
17 | ```shell
18 | mvn deploy
19 | ```
20 |
21 | Refer to the [official documentation](https://maven.apache.org/plugins/maven-deploy-plugin/usage.html) for more information.
22 |
23 | After the client library is installed/deployed, you can use it in your Maven project by adding the following to your *pom.xml*:
24 |
25 | ```xml
26 |
27 | io.swagger
28 | swagger-java-client
29 | 1.0.0
30 | compile
31 |
32 |
33 | ```
34 |
35 | ## Author
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/client/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'idea'
2 | apply plugin: 'eclipse'
3 |
4 | group = 'io.swagger'
5 | version = '1.0.0'
6 |
7 | buildscript {
8 | repositories {
9 | google()
10 | jcenter()
11 | }
12 | dependencies {
13 | classpath 'com.android.tools.build:gradle:4.0.0'
14 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
15 | }
16 | }
17 |
18 | repositories {
19 | jcenter()
20 | }
21 |
22 |
23 | if(hasProperty('target') && target == 'android') {
24 |
25 | apply plugin: 'com.android.library'
26 | apply plugin: 'com.github.dcendents.android-maven'
27 |
28 | android {
29 | compileSdkVersion 28
30 | defaultConfig {
31 | minSdkVersion 19
32 | targetSdkVersion 28
33 | }
34 | compileOptions {
35 | sourceCompatibility JavaVersion.VERSION_1_7
36 | targetCompatibility JavaVersion.VERSION_1_7
37 | }
38 |
39 | // Rename the aar correctly
40 | libraryVariants.all { variant ->
41 | variant.outputs.each { output ->
42 | def outputFile = output.outputFile
43 | if (outputFile != null && outputFile.name.endsWith('.aar')) {
44 | def fileName = "${project.name}-${variant.baseName}-${version}.aar"
45 | output.outputFile = new File(outputFile.parent, fileName)
46 | }
47 | }
48 | }
49 |
50 | dependencies {
51 | provided 'javax.annotation:jsr250-api:1.0'
52 | }
53 | }
54 |
55 | afterEvaluate {
56 | android.libraryVariants.all { variant ->
57 | def task = project.tasks.create "jar${variant.name.capitalize()}", Jar
58 | task.description = "Create jar artifact for ${variant.name}"
59 | task.dependsOn variant.javaCompile
60 | task.from variant.javaCompile.destinationDir
61 | task.destinationDir = project.file("${project.buildDir}/outputs/jar")
62 | task.archiveName = "${project.name}-${variant.baseName}-${version}.jar"
63 | artifacts.add('archives', task);
64 | }
65 | }
66 |
67 | task sourcesJar(type: Jar) {
68 | from android.sourceSets.main.java.srcDirs
69 | archiveClassifier = 'sources'
70 | }
71 |
72 | artifacts {
73 | archives sourcesJar
74 | }
75 |
76 | } else {
77 |
78 | apply plugin: 'java'
79 | apply plugin: 'maven'
80 |
81 | sourceCompatibility = JavaVersion.VERSION_1_7
82 | targetCompatibility = JavaVersion.VERSION_1_7
83 |
84 | install {
85 | repositories.mavenInstaller {
86 | pom.artifactId = 'swagger-java-client'
87 | }
88 | }
89 |
90 | task execute(type:JavaExec) {
91 | main = System.getProperty('mainClass')
92 | classpath = sourceSets.main.runtimeClasspath
93 | }
94 | }
95 |
96 | ext {
97 | oltu_version = "1.0.2"
98 | retrofit_version = "2.5.0"
99 | swagger_annotations_version = "1.5.15"
100 | junit_version = "4.13"
101 | threetenbp_version = "1.4.4"
102 | json_fire_version = "1.8.4"
103 | }
104 |
105 | dependencies {
106 | compile "com.squareup.retrofit2:retrofit:$retrofit_version"
107 | compile "com.squareup.retrofit2:converter-scalars:$retrofit_version"
108 | compile "com.squareup.retrofit2:converter-gson:$retrofit_version"
109 | compile "io.swagger:swagger-annotations:$swagger_annotations_version"
110 | compile ("org.apache.oltu.oauth2:org.apache.oltu.oauth2.client:$oltu_version") {
111 | exclude group:'org.apache.oltu.oauth2' , module: 'org.apache.oltu.oauth2.common'
112 | }
113 | compile "io.gsonfire:gson-fire:$json_fire_version"
114 | compile "org.threeten:threetenbp:$threetenbp_version"
115 | testCompile "junit:junit:$junit_version"
116 | compile group: 'javax.annotation', name: 'javax.annotation-api', version: '1.3.2'
117 | }
118 |
--------------------------------------------------------------------------------
/client/build.sbt:
--------------------------------------------------------------------------------
1 | lazy val root = (project in file(".")).
2 | settings(
3 | organization := "io.swagger",
4 | name := "swagger-java-client",
5 | version := "1.0.0",
6 | scalaVersion := "2.11.4",
7 | scalacOptions ++= Seq("-feature"),
8 | javacOptions in compile ++= Seq("-Xlint:deprecation"),
9 | publishArtifact in (Compile, packageDoc) := false,
10 | resolvers += Resolver.mavenLocal,
11 | libraryDependencies ++= Seq(
12 | "com.squareup.retrofit2" % "retrofit" % "2.3.0" % "compile",
13 | "com.squareup.retrofit2" % "converter-scalars" % "2.3.0" % "compile",
14 | "com.squareup.retrofit2" % "converter-gson" % "2.3.0" % "compile",
15 | "io.swagger" % "swagger-annotations" % "1.5.15" % "compile",
16 | "org.apache.oltu.oauth2" % "org.apache.oltu.oauth2.client" % "1.0.1" % "compile",
17 | "org.threeten" % "threetenbp" % "1.3.5" % "compile",
18 | "io.gsonfire" % "gson-fire" % "1.8.0" % "compile",
19 | "junit" % "junit" % "4.12" % "test",
20 | "com.novocode" % "junit-interface" % "0.11" % "test"
21 | )
22 | )
23 |
--------------------------------------------------------------------------------
/client/docs/Application.md:
--------------------------------------------------------------------------------
1 |
2 | # Application
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **description** | **String** | The description of the application. |
8 | **id** | **Long** | The application id. |
9 | **image** | **String** | The image of the application. |
10 | **internal** | **Boolean** | Whether the application is an internal application. Internal applications should not be deleted. |
11 | **name** | **String** | The application name. This is how the application should be displayed to the user. |
12 | **token** | **String** | The application token. Can be used as `appToken`. See Authentication. |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/client/docs/Client.md:
--------------------------------------------------------------------------------
1 |
2 | # Client
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **id** | **Long** | The client id. |
8 | **name** | **String** | The client name. This is how the client should be displayed to the user. |
9 | **token** | **String** | The client token. Can be used as `clientToken`. See Authentication. |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/docs/Error.md:
--------------------------------------------------------------------------------
1 |
2 | # Error
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **error** | **String** | The general error message |
8 | **errorCode** | **Long** | The http error code. |
9 | **errorDescription** | **String** | The http error code. |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/docs/Health.md:
--------------------------------------------------------------------------------
1 |
2 | # Health
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **database** | **String** | The health of the database connection. |
8 | **health** | **String** | The health of the overall application. |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/docs/HealthApi.md:
--------------------------------------------------------------------------------
1 | # HealthApi
2 |
3 | All URIs are relative to *http://localhost*
4 |
5 | Method | HTTP request | Description
6 | ------------- | ------------- | -------------
7 | [**getHealth**](HealthApi.md#getHealth) | **GET** health | Get health information.
8 |
9 |
10 |
11 | # **getHealth**
12 | > Health getHealth()
13 |
14 | Get health information.
15 |
16 | ### Example
17 | ```java
18 | // Import classes:
19 | //import com.github.gotify.client.ApiException;
20 | //import com.github.gotify.client.api.HealthApi;
21 |
22 |
23 | HealthApi apiInstance = new HealthApi();
24 | try {
25 | Health result = apiInstance.getHealth();
26 | System.out.println(result);
27 | } catch (ApiException e) {
28 | System.err.println("Exception when calling HealthApi#getHealth");
29 | e.printStackTrace();
30 | }
31 | ```
32 |
33 | ### Parameters
34 | This endpoint does not need any parameter.
35 |
36 | ### Return type
37 |
38 | [**Health**](Health.md)
39 |
40 | ### Authorization
41 |
42 | No authorization required
43 |
44 | ### HTTP request headers
45 |
46 | - **Content-Type**: application/json
47 | - **Accept**: application/json
48 |
49 |
--------------------------------------------------------------------------------
/client/docs/Message.md:
--------------------------------------------------------------------------------
1 |
2 | # Message
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **appid** | **Long** | The application id that send this message. |
8 | **date** | [**OffsetDateTime**](OffsetDateTime.md) | The date the message was created. |
9 | **extras** | **Map<String, Object>** | The extra data sent along the message. The extra fields are stored in a key-value scheme. Only accepted in CreateMessage requests with application/json content-type. The keys should be in the following format: <top-namespace>::[<sub-namespace>::]<action> These namespaces are reserved and might be used in the official clients: gotify android ios web server client. Do not use them for other purposes. | [optional]
10 | **id** | **Long** | The message id. |
11 | **message** | **String** | The message. Markdown (excluding html) is allowed. |
12 | **priority** | **Long** | The priority of the message. | [optional]
13 | **title** | **String** | The title of the message. | [optional]
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/client/docs/PagedMessages.md:
--------------------------------------------------------------------------------
1 |
2 | # PagedMessages
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **messages** | [**List<Message>**](Message.md) | The messages. |
8 | **paging** | [**Paging**](Paging.md) | |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/client/docs/Paging.md:
--------------------------------------------------------------------------------
1 |
2 | # Paging
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **limit** | **Long** | The limit of the messages for the current request. |
8 | **next** | **String** | The request url for the next page. Empty/Null when no next page is available. | [optional]
9 | **since** | **Long** | The ID of the last message returned in the current request. Use this as alternative to the next link. |
10 | **size** | **Long** | The amount of messages that got returned in the current request. |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/client/docs/PluginConf.md:
--------------------------------------------------------------------------------
1 |
2 | # PluginConf
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **author** | **String** | The author of the plugin. | [optional]
8 | **capabilities** | **List<String>** | Capabilities the plugin provides |
9 | **enabled** | **Boolean** | Whether the plugin instance is enabled. |
10 | **id** | **Long** | The plugin id. |
11 | **license** | **String** | The license of the plugin. | [optional]
12 | **modulePath** | **String** | The module path of the plugin. |
13 | **name** | **String** | The plugin name. |
14 | **token** | **String** | The user name. For login. |
15 | **website** | **String** | The website of the plugin. | [optional]
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/client/docs/User.md:
--------------------------------------------------------------------------------
1 |
2 | # User
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **admin** | **Boolean** | If the user is an administrator. | [optional]
8 | **id** | **Long** | The user id. |
9 | **name** | **String** | The user name. For login. |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/docs/UserPass.md:
--------------------------------------------------------------------------------
1 |
2 | # UserPass
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **pass** | **String** | The user password. For login. |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/client/docs/UserWithPass.md:
--------------------------------------------------------------------------------
1 |
2 | # UserWithPass
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **admin** | **Boolean** | If the user is an administrator. | [optional]
8 | **id** | **Long** | The user id. |
9 | **name** | **String** | The user name. For login. |
10 | **pass** | **String** | The user password. For login. |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/client/docs/VersionApi.md:
--------------------------------------------------------------------------------
1 | # VersionApi
2 |
3 | All URIs are relative to *http://localhost*
4 |
5 | Method | HTTP request | Description
6 | ------------- | ------------- | -------------
7 | [**getVersion**](VersionApi.md#getVersion) | **GET** version | Get version information.
8 |
9 |
10 |
11 | # **getVersion**
12 | > VersionInfo getVersion()
13 |
14 | Get version information.
15 |
16 | ### Example
17 | ```java
18 | // Import classes:
19 | //import com.github.gotify.client.ApiException;
20 | //import com.github.gotify.client.api.VersionApi;
21 |
22 |
23 | VersionApi apiInstance = new VersionApi();
24 | try {
25 | VersionInfo result = apiInstance.getVersion();
26 | System.out.println(result);
27 | } catch (ApiException e) {
28 | System.err.println("Exception when calling VersionApi#getVersion");
29 | e.printStackTrace();
30 | }
31 | ```
32 |
33 | ### Parameters
34 | This endpoint does not need any parameter.
35 |
36 | ### Return type
37 |
38 | [**VersionInfo**](VersionInfo.md)
39 |
40 | ### Authorization
41 |
42 | No authorization required
43 |
44 | ### HTTP request headers
45 |
46 | - **Content-Type**: application/json
47 | - **Accept**: application/json
48 |
49 |
--------------------------------------------------------------------------------
/client/docs/VersionInfo.md:
--------------------------------------------------------------------------------
1 |
2 | # VersionInfo
3 |
4 | ## Properties
5 | Name | Type | Description | Notes
6 | ------------ | ------------- | ------------- | -------------
7 | **buildDate** | **String** | The date on which this binary was built. |
8 | **commit** | **String** | The git commit hash on which this binary was built. |
9 | **version** | **String** | The current version. |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/client/git_push.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
3 | #
4 | # Usage example: /bin/sh ./git_push.sh wing328 swagger-petstore-perl "minor update"
5 |
6 | git_user_id=$1
7 | git_repo_id=$2
8 | release_note=$3
9 |
10 | if [ "$git_user_id" = "" ]; then
11 | git_user_id="GIT_USER_ID"
12 | echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
13 | fi
14 |
15 | if [ "$git_repo_id" = "" ]; then
16 | git_repo_id="GIT_REPO_ID"
17 | echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
18 | fi
19 |
20 | if [ "$release_note" = "" ]; then
21 | release_note="Minor update"
22 | echo "[INFO] No command line input provided. Set \$release_note to $release_note"
23 | fi
24 |
25 | # Initialize the local directory as a Git repository
26 | git init
27 |
28 | # Adds the files in the local repository and stages them for commit.
29 | git add .
30 |
31 | # Commits the tracked changes and prepares them to be pushed to a remote repository.
32 | git commit -m "$release_note"
33 |
34 | # Sets the new remote
35 | git_remote=`git remote`
36 | if [ "$git_remote" = "" ]; then # git remote not defined
37 |
38 | if [ "$GIT_TOKEN" = "" ]; then
39 | echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
40 | git remote add origin https://github.com/${git_user_id}/${git_repo_id}.git
41 | else
42 | git remote add origin https://${git_user_id}:${GIT_TOKEN}@github.com/${git_user_id}/${git_repo_id}.git
43 | fi
44 |
45 | fi
46 |
47 | git pull origin master
48 |
49 | # Pushes (Forces) the changes in the local repository up to the remote repository
50 | echo "Git pushing to https://github.com/${git_user_id}/${git_repo_id}.git"
51 | git push origin master 2>&1 | grep -v 'To https'
52 |
53 |
--------------------------------------------------------------------------------
/client/gradle.properties:
--------------------------------------------------------------------------------
1 | # Uncomment to build for Android
2 | #target = android
--------------------------------------------------------------------------------
/client/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnifiedPush/gotify-android/567b4d3b8c1b02a9cab03a4348683936e775d1dd/client/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/client/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue May 17 23:08:05 CST 2016
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-bin.zip
7 |
--------------------------------------------------------------------------------
/client/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/client/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/client/src/main/java/com/github/gotify/client/CollectionFormats.java:
--------------------------------------------------------------------------------
1 | package com.github.gotify.client;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 |
6 | public class CollectionFormats {
7 |
8 | public static class CSVParams {
9 |
10 | protected List params;
11 |
12 | public CSVParams() {
13 | }
14 |
15 | public CSVParams(List params) {
16 | this.params = params;
17 | }
18 |
19 | public CSVParams(String... params) {
20 | this.params = Arrays.asList(params);
21 | }
22 |
23 | public List getParams() {
24 | return params;
25 | }
26 |
27 | public void setParams(List params) {
28 | this.params = params;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return StringUtil.join(params.toArray(new String[0]), ",");
34 | }
35 |
36 | }
37 |
38 | public static class SSVParams extends CSVParams {
39 |
40 | public SSVParams() {
41 | }
42 |
43 | public SSVParams(List params) {
44 | super(params);
45 | }
46 |
47 | public SSVParams(String... params) {
48 | super(params);
49 | }
50 |
51 | @Override
52 | public String toString() {
53 | return StringUtil.join(params.toArray(new String[0]), " ");
54 | }
55 | }
56 |
57 | public static class TSVParams extends CSVParams {
58 |
59 | public TSVParams() {
60 | }
61 |
62 | public TSVParams(List params) {
63 | super(params);
64 | }
65 |
66 | public TSVParams(String... params) {
67 | super(params);
68 | }
69 |
70 | @Override
71 | public String toString() {
72 | return StringUtil.join( params.toArray(new String[0]), "\t");
73 | }
74 | }
75 |
76 | public static class PIPESParams extends CSVParams {
77 |
78 | public PIPESParams() {
79 | }
80 |
81 | public PIPESParams(List params) {
82 | super(params);
83 | }
84 |
85 | public PIPESParams(String... params) {
86 | super(params);
87 | }
88 |
89 | @Override
90 | public String toString() {
91 | return StringUtil.join(params.toArray(new String[0]), "|");
92 | }
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/client/src/main/java/com/github/gotify/client/StringUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Gotify REST-API.
3 | * This is the documentation of the Gotify REST-API. # Authentication In Gotify there are two token types: __clientToken__: a client is something that receives message and manages stuff like creating new tokens or delete messages. (f.ex this token should be used for an android app) __appToken__: an application is something that sends messages (f.ex. this token should be used for a shell script) The token can be either transmitted through a header named `X-Gotify-Key` or a query parameter named `token`. There is also the possibility to authenticate through basic auth, this should only be used for creating a clientToken. \\--- Found a bug or have some questions? [Create an issue on GitHub](https://github.com/gotify/server/issues)
4 | *
5 | * OpenAPI spec version: 2.0.1
6 | *
7 | *
8 | * NOTE: This class is auto generated by the swagger code generator program.
9 | * https://github.com/swagger-api/swagger-codegen.git
10 | * Do not edit the class manually.
11 | */
12 |
13 |
14 | package com.github.gotify.client;
15 |
16 | @javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2020-06-24T18:39:01.386+02:00")
17 | public class StringUtil {
18 | /**
19 | * Check if the given array contains the given value (with case-insensitive comparison).
20 | *
21 | * @param array The array
22 | * @param value The value to search
23 | * @return true if the array contains the value
24 | */
25 | public static boolean containsIgnoreCase(String[] array, String value) {
26 | for (String str : array) {
27 | if (value == null && str == null) return true;
28 | if (value != null && value.equalsIgnoreCase(str)) return true;
29 | }
30 | return false;
31 | }
32 |
33 | /**
34 | * Join an array of strings with the given separator.
35 | *
36 | * Note: This might be replaced by utility method from commons-lang or guava someday
37 | * if one of those libraries is added as dependency.
38 | *