7 Commits

13 changed files with 446 additions and 10 deletions

View File

@ -1,7 +1,8 @@
# Bookwyrm-android # Bookwyrm-android
A crappy attempt at creating an Android application for Bookwyrm. Basically, it is just bookwyrm put into a 'webview' element. An Android application for Bookwyrm. Basically, it is just bookwyrm put into a 'webview' element.
What it does? It enables you to use BookWyrm on your Android phone without having to use a browser to go to it every time. What it does? It enables you to use BookWyrm on your Android phone without having to use a browser to go to it every time.
Currently, it is not able to open links yet, but maybe I will add that sooner or later. It can also open links to userpages of Bookwyrm users.
This application works on: Android 6 and above. This application works on: Android 6 and above.
And if you want to know, I am `@StoryDragon@wyrms.de` on BookWyrm.

View File

@ -9,8 +9,8 @@ android {
applicationId "nl.privacydragon.bookwyrm" applicationId "nl.privacydragon.bookwyrm"
minSdk 23 minSdk 23
targetSdk 31 targetSdk 31
versionCode 2 versionCode 6
versionName "1.1.0" versionName "1.2.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
} }

View File

@ -11,8 +11,8 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 2, "versionCode": 6,
"versionName": "1.1.0", "versionName": "1.2.3",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
], ],

View File

@ -1,7 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="nl.privacydragon.bookwyrm"> package="nl.privacydragon.bookwyrm">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher_wyrm" android:icon="@mipmap/ic_launcher_wyrm"
@ -9,6 +12,231 @@
android:roundIcon="@mipmap/ic_launcher_wyrm_round" android:roundIcon="@mipmap/ic_launcher_wyrm_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Bookwyrm"> android:theme="@style/Theme.Bookwyrm">
<activity
android:name=".HandlerActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="wyrms.de"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.social"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="book.dansmonorage.blue"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="yyyyy.club"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.mxhdr.net"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="ziurkes.group.lt"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.solarpunk.moe"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="reading.taks.garden"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.spaceling.sh"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="lire.boitam.eu"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="kirja.casa"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.badwolfbay.games"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.unexist.dev"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.cincodenada.com"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.underscore.world"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.tardis.pw"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="tankie.ml"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="masstoc.io"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="books.mennisch.net"
android:pathPrefix="/user/" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="bookwyrm.pt"
android:pathPrefix="/user/" />
</intent-filter>
</activity>
<activity <activity
android:name=".StartActivity" android:name=".StartActivity"
android:exported="false" /> android:exported="false" />

View File

@ -0,0 +1,191 @@
package nl.privacydragon.bookwyrm;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.view.KeyEvent;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
public class HandlerActivity extends AppCompatActivity {
WebView myWebView;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start);
// ATTENTION: This was auto-generated to handle app links.
Intent appLinkIntent = getIntent();
String appLinkAction = appLinkIntent.getAction();
Uri appLinkData = appLinkIntent.getData();
// End of auto-generated stuff
myWebView = (WebView) findViewById(R.id.webview);
myWebView.getSettings().setJavaScriptEnabled(true);
//The user credentials are stored in the shared preferences, so first they have to be read from there.
String defaultValue = "none";
SharedPreferences sharedPref = HandlerActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
String server = sharedPref.getString(getString(R.string.server), defaultValue);
SharedPreferences sharedPrefName = HandlerActivity.this.getSharedPreferences(getString(R.string.name), Context.MODE_PRIVATE);
String name = sharedPrefName.getString(getString(R.string.name), defaultValue);
SharedPreferences sharedPrefPass = HandlerActivity.this.getSharedPreferences(getString(R.string.pw), Context.MODE_PRIVATE);
String pass = sharedPrefPass.getString(getString(R.string.pw), defaultValue);
SharedPreferences sharedPrefMagic = HandlerActivity.this.getSharedPreferences(getString(R.string.q), Context.MODE_PRIVATE);
String codeMagic = sharedPrefMagic.getString(getString(R.string.q), defaultValue);
//If there is nothing configured yet, the user should be redirected to the main screen for logging in.
if (server == "none") {
startActivity(new Intent(HandlerActivity.this, nl.privacydragon.bookwyrm.MainActivity.class));
}
String pathMaybe = appLinkData.getPath();
String toGoServer = "bla";
if (pathMaybe.contains("user")) {
//If the path contains 'user', it is a user profile, unless it is followed by something like 'review'.
if (pathMaybe.contains("review") || pathMaybe.contains("generatednote") || pathMaybe.contains("quotation") || pathMaybe.contains("comment") ) {
toGoServer = "https://" + appLinkData.getHost() + pathMaybe;
}
else {
String notAtUser = pathMaybe.substring(pathMaybe.indexOf("user") + 5); //This line gets the username.
String atUser = notAtUser + "@" + appLinkData.getHost(); //This appends @[HOST] to the string, so we have the full username thing needed.
toGoServer = "https://" + server + "/user/" + atUser;
}
} else {
startActivity(new Intent(HandlerActivity.this, nl.privacydragon.bookwyrm.StartActivity.class));
}
//Then all the decryption stuff has to happen. There are a lot of try-catch stuff, because apparently that seems to be needed.
//First get the keystore thing.
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
} catch (KeyStoreException e) {
e.printStackTrace();
}
//Then, load it. or something. To make sure that it can be used.
try {
keyStore.load(null);
} catch (CertificateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//Next, retrieve the key to be used for the decryption.
Key DragonLikeKey = null;
try {
DragonLikeKey = keyStore.getKey("BookWyrm", null);
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
}
//Do something with getting the/a cipher or something.
Cipher c = null;
try {
c = Cipher.getInstance("AES/GCM/NoPadding");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
//And then initiating the cipher, so it can be used.
try {
c.init(Cipher.DECRYPT_MODE, DragonLikeKey, new GCMParameterSpec(128, codeMagic.getBytes()));
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
//Decrypt the password!
byte[] truePass = null;
try {
truePass = c.doFinal(Base64.decode(pass, Base64.DEFAULT));
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
}
//Convert the decrypted password back to a string.
String passw = new String(truePass, StandardCharsets.UTF_8);
//A webviewclient thing is needed for some stuff. To automatically log in, the credentials are put in the form by the javascript that is loaded once the page is fully loaded. Then it is automatically submitted if the current page is the login page.
String finalToGoServer = toGoServer;
myWebView.setWebViewClient(new HandlerActivity.MyWebViewClient() {
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:(function() { document.getElementById('id_password').value = '" + passw + "'; ;})()");
view.loadUrl("javascript:(function() { document.getElementById('id_localname').value = '" + name + "'; ;})()");
view.loadUrl("javascript:(function() { if (window.location.href == '" + finalToGoServer + "' && !/(review|generatednote|quotation|comment)/i.test(window.location.href)) { document.getElementsByName(\"login\")[0].submit();} ;})()");
view.loadUrl("javascript:(function() { if (window.location.href == 'https://" + server + "') { document.getElementsByName(\"login\")[0].submit();} ;})()");
view.loadUrl("javascript:(function() { if (/(review|generatednote|quotation|comment)/i.test(window.location.href)) { document.getElementsByClassName(\"block\")[0].innerHTML = ` <a href=\"https://"+ server +"\" class=\"button\" data-back=\"\">\n" +
" <span class=\"icon icon-arrow-left\" aria-hidden=\"true\"></span>\n" +
" <span><b>Back to homeserver</b></span>\n" +
" </a>`;} ;})()");
}
});
//Here, load the login page of the server. That actually does all that is needed.
myWebView.loadUrl(toGoServer);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
//Here is code to make sure that links of the bookwyrm server are handled withing the webview client, instead of having it open in the default browser.
//Yes, I used the web for this too.
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
// ATTENTION: This was auto-generated to handle app links.
Intent appLinkIntent = getIntent();
String appLinkAction = appLinkIntent.getAction();
Uri appLinkData = appLinkIntent.getData();
// End of auto-generated stuff
String strangeHost = appLinkData.getHost();
SharedPreferences sharedPref = HandlerActivity.this.getSharedPreferences(getString(R.string.server), Context.MODE_PRIVATE);
String defaultValue = "none";
String server = sharedPref.getString(getString(R.string.server), defaultValue);
if (server.equals(request.getUrl().getHost())) {
//If the server is the same as the bookwyrm, load it in the webview.
return false;
} else if (strangeHost.equals(request.getUrl().getHost())) {
return false;
}
// Otherwise, it should go to the default browser instead.
Intent intent = new Intent(Intent.ACTION_VIEW, request.getUrl());
startActivity(intent);
return true;
}
}
}

View File

@ -7,6 +7,7 @@ import android.content.SharedPreferences;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.util.Base64; import android.util.Base64;
import android.view.KeyEvent;
import android.webkit.WebResourceRequest; import android.webkit.WebResourceRequest;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
@ -29,13 +30,13 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.GCMParameterSpec;
public class StartActivity extends AppCompatActivity { public class StartActivity extends AppCompatActivity {
WebView myWebView;
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start); setContentView(R.layout.activity_start);
WebView myWebView = (WebView) findViewById(R.id.webview); myWebView = (WebView) findViewById(R.id.webview);
myWebView.getSettings().setJavaScriptEnabled(true); myWebView.getSettings().setJavaScriptEnabled(true);
//The user credentials are stored in the shared preferences, so first they have to be read from there. //The user credentials are stored in the shared preferences, so first they have to be read from there.
String defaultValue = "none"; String defaultValue = "none";
@ -118,6 +119,17 @@ public class StartActivity extends AppCompatActivity {
//Here, load the login page of the server. That actually does all that is needed. //Here, load the login page of the server. That actually does all that is needed.
myWebView.loadUrl("https://" + server + "/login"); myWebView.loadUrl("https://" + server + "/login");
} }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Check if the key event was the Back button and if there's history
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
myWebView.goBack();
return true;
}
// If it wasn't the Back key or there's no web page history, bubble up to the default
// system behavior (probably exit the activity)
return super.onKeyDown(keyCode, event);
}
//Here is code to make sure that links of the bookwyrm server are handled withing the webview client, instead of having it open in the default browser. //Here is code to make sure that links of the bookwyrm server are handled withing the webview client, instead of having it open in the default browser.
//Yes, I used the web for this too. //Yes, I used the web for this too.
private class MyWebViewClient extends WebViewClient { private class MyWebViewClient extends WebViewClient {

View File

@ -0,0 +1,3 @@
Initial release for F-Droid.
* Added support for four more BookWyrm instances.

View File

@ -1,2 +1,3 @@
This is a, probably crappy, Android client for BookWyrm. This is an Android client for BookWyrm.
It is just BookWyrm put into a webview element, nothing special. It is just BookWyrm put into a webview element, nothing special.
<b>This application is <u>not</u> an official client!</b> (An official client does not exist yet, as far as I know)

View File

Before

Width:  |  Height:  |  Size: 302 KiB

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB