Add support for Moebooru-, Gelbooru- and Zerochan-based imageboards, and Wallhaven. Restructure how board type preference is saved. Restructure README.

This commit is contained in:
Kevin Alberts 2024-12-28 01:40:28 +01:00
parent 291307b121
commit 799151a720
6 changed files with 412 additions and 39 deletions

View file

@ -5,7 +5,12 @@ This is a [Projectivy](https://xdaforums.com/t/app-android-tv-projectivy-launche
## Repository layout
- /booru : code for the Booru plugin service and its setting activity
- /api : api used to communicate with Projectivy through AIDL
# Screenshots
![screenshot](./.github/readme-images/background.png)
![screenshot](./.github/readme-images/settings.png)
# Usage
- Download the `booru-release.apk` from the releases tab and install it on your TV
- Navigate to Projectivy -> Launcher settings -> Appearance -> Wallpaper -> Launcher wallpaper
@ -16,10 +21,39 @@ This is a [Projectivy](https://xdaforums.com/t/app-android-tv-projectivy-launche
- Search query: The search query from which images are pulled. This is the same as you would fill in on the imageboard website's search box. Check the help pages of your booru for guidance. (i.e. the [cheatsheet](https://danbooru.donmai.us/wiki_pages/help%3Acheatsheet) for Danbooru) All tag types should work normally (including ordering, ratio, and other metatags).
- Username and API Key: These are optional. Some boorus have limitations on how many tags can be searched anonymously or by standard users. For example, Danbooru allows 2 tags for logged out and normal users, but 6 for premium accounts. Fill in these fields to authenticate.
# Screenshots
![screenshot](./.github/readme-images/background.png)
## Settings for common boorus
| Booru | Type | URL | Query guide | Username | API Key |
|-----------------------------------------------------------------------------------|-----------|----------------------------------------------------------------------------|---------------------------------------------------------------------------|--------------------|----------------------------------------------------------------------------------------|
| [Danbooru](https://danbooru.donmai.us) <br>Anime, NSFW and SFW variants | Danbooru | `https://danbooru.donmai.us` (NSFW)<br>`https://safebooru.donmai.us` (SFW) | [cheatsheet](https://danbooru.donmai.us/wiki_pages/help%3Acheatsheet) | Optional, username | Optional, API key. Found on [profile](https://danbooru.donmai.us/profile) |
| [Konachan](https://konachan.com/) <br>Anime wallpapers, NSFW and SFW variants | Moebooru | `https://konachan.com` (NSFW)<br>`https://konachan.net` (SFW) | [cheatsheet](https://konachan.net/help/cheatsheet) | Optional, username | Optional, hashed password. See [api docs, `Logging in`](https://konachan.net/help/api) |
| [Yande.re](https://yande.re) <br>Anime, contains NSFW | Moebooru | `https://yande.re` | [cheatsheet](https://yande.re/wiki/show?title=cheat_sheet_extended) | Optional, username | Optional, hashed password. See [api docs, `Logging in`](https://yande.re/help/api) |
| [Gelbooru](https://gelbooru.com) <br>Anime, contains NSFW | Gelbooru | `https://gelbooru.com` | [cheatsheet](https://gelbooru.com/index.php?page=wiki&s=&s=view&id=26263) | Optional, username | Optional, API key. Found in account options |
| [Safebooru](https://safebooru.org) <br>Anime, SFW | Gelbooru | `https://safebooru.org` | See `Gelbooru` | Optional, username | Optional, API key. Found in account options |
| [Zerochan](https://www.zerochan.net) (*) <br>Anime, SFW | Zerochan | `https://www.zerochan.net` | Tag names, comma separated. I.e. `Genshin Impact,Lumine` | Optional, username | *empty* |
| [Asiachan](https://kpop.asiachan.com) (*) <br>IRL, K-Pop, SFW | Zerochan | `https://kpop.asiachan.com` | Tag names, comma separated. I.e. `BTS,Park Jimin` | Optional, username | *empty* |
| [Wallhaven](https://wallhaven.cc) <br>General wallpapers, SFW (NSFW with API key) | Wallhaven | `https://wallhaven.cc` | [API docs](https://wallhaven.cc/help/api#search) | *empty* | Optional, found in account settings |
![screenshot](./.github/readme-images/settings.png)
**(*)** Support for Zerochan-type boards is pretty unstable due to how these sites handle requests. Don't expect these to be very stable/usable.
<details>
<summary>Other known boorus by type</summary>
### Other known boorus by type
These are some other boorus listed by their software, sorted into 'SFW' and 'NSFW'.
Take the 'SFW' tag with a grain of salt, you know how the internet works...
| Content rating | Type | Known boorus |
|----------------|--------------------------|----------------------------------------------------------------------------------------|
| SFW | Danbooru | `yukkuri.shiteitte.net`, `e926.net` |
| | Moebooru | `sakugabooru.com`, `img.genshiken-itb.org` |
| | Gelbooru | `*.booru.org (some)` |
| NSFW | Danbooru | `yukkuri.shiteitte.net`, `booru.allthefallen.moe`, `e621.net` |
| | Gelbooru | `*.booru.org`, `xbooru.com`, `rule34.xxx`, `tbib.org`, `hypnohub.net`, `realbooru.com` |
| | Shimmie2 *(unsupported)* | `rule34.paheal.net`, `rule34hentai.net`, `fanservice.fan` |
| | Sankaku *(unsupported)* | `chan.sankakucomplex.com`, `idol.sankakucomplex.com` |
</details>
# Note
This plugin is provided as an open-source project and is distributed "as is." While the author may offer voluntary support, there is no guarantee of availability or resolution. The author is not responsible for any damages, data loss, or issues arising from the use of this plugin. Use at your own risk.

View file

@ -13,8 +13,8 @@ android {
applicationId = "nl.kurocon.plugin.wallpaperprovider.booru"
minSdk = 23
targetSdk = 35
versionCode = 1
versionName = "1.0"
versionCode = 2
versionName = "1.1"
}

View file

@ -1,39 +1,63 @@
package nl.kurocon.plugin.wallpaperprovider.booru
import android.content.Context
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.DANBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.GELBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.MOEBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.WALLHAVEN
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.ZEROCHAN
import okhttp3.OkHttpClient
import okhttp3.Request
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.IOException
import java.net.URLEncoder
class BooruAPI(context: Context) {
private val client = OkHttpClient()
private var booruPluginUserAgent = "ProjectivyBooruWallpaperPlugin"
init {
PreferencesManager.init(context)
val pluginName = context.getString(R.string.plugin_name)
val pluginVersion = context.getString(R.string.plugin_version)
booruPluginUserAgent = "$pluginName/$pluginVersion"
}
@Throws(IOException::class)
fun getImageUrls(limit: Int = 20): List<BooruImage> {
return when (PreferencesManager.booruType) {
"danbooru" -> getDanbooruImageUrls(limit = limit)
DANBOORU -> getDanbooruImageUrls(limit = limit)
GELBOORU -> getGelbooruImageUrls(limit = limit)
MOEBOORU -> getMoebooruImageUrls(limit = limit)
ZEROCHAN -> getZerochanImageUrls(limit = limit)
WALLHAVEN -> getWallhavenImageUrls(limit = limit)
else -> getDanbooruImageUrls(limit = limit)
}
}
private fun getRequestBuilder(url: String, addAuth: Boolean = false): Request.Builder {
// Add default headers (User-Agent) and set URL
var builder = Request.Builder()
.url(url)
.addHeader("User-Agent", booruPluginUserAgent)
if (addAuth && (PreferencesManager.booruUserId.isNotEmpty() && PreferencesManager.booruApiKey.isNotEmpty())) {
builder = builder.addHeader(
"Authorization",
okhttp3.Credentials.basic(PreferencesManager.booruUserId, PreferencesManager.booruApiKey)
)
}
return builder
}
@Throws(IOException::class)
fun getDanbooruImageUrls(limit: Int = 20): List<BooruImage> {
val tags = PreferencesManager.booruTagSearch
val encodedTags = URLEncoder.encode(tags, "UTF-8")
val url = "${PreferencesManager.booruUrl}/posts.json?tags=$encodedTags&limit=$limit"
var requestBuilder = Request.Builder()
.url(url)
if (PreferencesManager.booruUserId.isNotEmpty() && PreferencesManager.booruApiKey.isNotEmpty()) {
requestBuilder = requestBuilder.addHeader("Authorization", okhttp3.Credentials.basic(PreferencesManager.booruUserId, PreferencesManager.booruApiKey))
}
val request = requestBuilder.build()
val request = getRequestBuilder(url, addAuth=true).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
@ -44,7 +68,7 @@ class BooruAPI(context: Context) {
if (errorMessage.isNotEmpty()) {
throw IOException("Error ${response.code}. $errorMessage")
}
throw IOException("Unexpected code ${response.code} - ${response.body}")
throw IOException("Error ${response.code} - ${response.body}")
}
val responseBody = response.body?.string() ?: throw IOException("Response body is null")
@ -65,6 +89,222 @@ class BooruAPI(context: Context) {
return images
}
}
@Throws(IOException::class)
fun getMoebooruImageUrls(limit: Int = 20): List<BooruImage> {
val tags = PreferencesManager.booruTagSearch
val encodedTags = URLEncoder.encode(tags, "UTF-8")
var url = "${PreferencesManager.booruUrl}/post.json?tags=$encodedTags&limit=$limit"
if (PreferencesManager.booruUserId.isNotEmpty() && PreferencesManager.booruApiKey.isNotEmpty()) {
val encUsername = URLEncoder.encode(PreferencesManager.booruUserId, "UTF-8")
val encApiKey = URLEncoder.encode(PreferencesManager.booruApiKey, "UTF-8")
url = "$url&login=$encUsername&password_hash=$encApiKey"
}
val request = getRequestBuilder(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// Try to parse error message from body
val responseBody = response.body?.string() ?: throw IOException("Unexpected code ${response.code}, and response body is empty")
val jsonBody = JSONObject(responseBody)
val errorMessage = jsonBody.optString("message")
if (errorMessage.isNotEmpty()) {
throw IOException("Error ${response.code}. $errorMessage")
}
throw IOException("Error ${response.code} - ${response.body}")
}
val responseBody = response.body?.string() ?: throw IOException("Response body is null")
val jsonArray = JSONArray(responseBody)
// Parse JSON response into BooruImage instances
val images = mutableListOf<BooruImage>()
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val imageUrl = jsonObject.optString("file_url")
val title = jsonObject.optString("tag_string")
val author = jsonObject.optString("tag_string_artist")
val sourceUri = jsonObject.optString("source")
if (imageUrl.isNotEmpty()) {
images.add(BooruImage(imageUrl, title, author, sourceUri))
}
}
return images
}
}
@Throws(IOException::class)
fun getGelbooruImageUrls(limit: Int = 20): List<BooruImage> {
val tags = PreferencesManager.booruTagSearch
val encodedTags = URLEncoder.encode(tags, "UTF-8")
var url = "${PreferencesManager.booruUrl}/index.php?page=dapi&s=post&q=index&tags=$encodedTags&limit=$limit&json=1"
if (PreferencesManager.booruUserId.isNotEmpty() && PreferencesManager.booruApiKey.isNotEmpty()) {
val encUsername = URLEncoder.encode(PreferencesManager.booruUserId, "UTF-8")
val encApiKey = URLEncoder.encode(PreferencesManager.booruApiKey, "UTF-8")
url = "$url&api_key=$encApiKey&user_id=$encUsername"
}
val request = getRequestBuilder(url).build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// Try to parse error message from body
val responseBody = response.body?.string() ?: throw IOException("Unexpected code ${response.code}, and response body is empty")
val jsonBody = JSONObject(responseBody)
val errorMessage = jsonBody.optString("message")
if (errorMessage.isNotEmpty()) {
throw IOException("Error ${response.code}. $errorMessage")
}
throw IOException("Error ${response.code} - ${response.body}")
}
val responseBody = response.body?.string() ?: throw IOException("Response body is null")
val jsonBody: JSONObject
try {
jsonBody = JSONObject(responseBody)
} catch (e: Exception) {
// gelbooru returns xml response if request was denied for some reason
// i.e. user hit a rate limit because he didn't include api key
throw IOException("Error. Unexpected response. You might be rate limited.")
}
// Parse JSON response into BooruImage instances
val images = mutableListOf<BooruImage>()
val jsonArray = jsonBody.getJSONArray("post")
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val imageUrl = jsonObject.optString("file_url")
val title = jsonObject.optString("tags")
// Gelbooru does not have an easily accessible Artist tag. Link to the source in a best-effort way.
val author = jsonObject.optString("source")
val sourceUri = jsonObject.optString("source")
if (imageUrl.isNotEmpty()) {
images.add(BooruImage(imageUrl, title, author, sourceUri))
}
}
return images
}
}
@Throws(IOException::class)
fun getZerochanImageUrls(limit: Int = 20): List<BooruImage> {
val tags = PreferencesManager.booruTagSearch
val encodedTags = URLEncoder.encode(tags, "UTF-8")
val url = "${PreferencesManager.booruUrl}/$encodedTags?json&l=$limit"
var requestBuilder = getRequestBuilder(url)
// Zerochan has no authentication, but asks to put the username in the User-Agent string.
// This header is already added by the `getRequestBuilder`, so need to override it.
if (PreferencesManager.booruUserId.isNotEmpty()) {
requestBuilder = requestBuilder.header(
"User-Agent", "$booruPluginUserAgent - ${PreferencesManager.booruUserId}"
)
}
val request = requestBuilder.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// Try to parse error message from body
val responseBody = response.body?.string() ?: throw IOException("Unexpected code ${response.code}, and response body is empty")
val jsonBody = JSONObject(responseBody)
val errorMessage = jsonBody.optString("message")
if (errorMessage.isNotEmpty()) {
throw IOException("Error ${response.code}. $errorMessage")
}
throw IOException("Error ${response.code} - ${response.body}")
}
val responseBody = response.body?.string() ?: throw IOException("Response body is null")
val jsonArray = JSONObject(responseBody).getJSONArray("items")
// Parse JSON response into BooruImage instances
val images = mutableListOf<BooruImage>()
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val imageId = jsonObject.optInt("id")
if (imageId != 0) {
// Do a second request for the complete image information, because
// zerochan does not include the full image link in the list results...
var requestBuilder2 =
getRequestBuilder("${PreferencesManager.booruUrl}/$imageId?json")
if (PreferencesManager.booruUserId.isNotEmpty()) {
requestBuilder2 = requestBuilder2.header(
"User-Agent",
"$booruPluginUserAgent - ${PreferencesManager.booruUserId}"
)
}
val request2 = requestBuilder2.build()
client.newCall(request2).execute().use { response2 ->
if (response2.isSuccessful) {
val responseBody2 = response2.body?.string()
?: throw IOException("Response body is null")
val jsonBody: JSONObject
try {
jsonBody = JSONObject(responseBody2)
val imageUrl = jsonBody.optString("full")
val title1 = jsonBody.optString("primary")
val title2 = jsonBody.optJSONArray("tags")?.join(" ")
val title = "$title1 - $title2"
// Zerochan does not have an easily accessible Artist tag. Link to the source in a best-effort way.
val author = jsonBody.optString("source")
val sourceUri = jsonBody.optString("source")
if (imageUrl.isNotEmpty()) {
images.add(BooruImage(imageUrl, title, author, sourceUri))
}
} catch (e: JSONException) {
// Invalid response, do nothing (probably rate limited, HTML output)
}
}
}
}
}
return images
}
}
@Throws(IOException::class)
fun getWallhavenImageUrls(limit: Int = 20): List<BooruImage> {
val tags = PreferencesManager.booruTagSearch
val encodedTags = URLEncoder.encode(tags, "UTF-8")
var url = "${PreferencesManager.booruUrl}/api/v1/search?q=$encodedTags&sorting=random"
val requestBuilder = getRequestBuilder(url)
if (PreferencesManager.booruApiKey.isNotEmpty()) {
// Users can authenticate by including their API key either in a request URL by appending
// ?apikey=<API KEY>, or by including the X-API-Key: <API KEY> header with the request.
// API key grants access to NSFW images (but the purity=111 flag is not set by this plugin, default is SFW, 100).
requestBuilder.addHeader("X-API-Key", PreferencesManager.booruApiKey)
}
val request = requestBuilder.build()
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
// Try to parse error message from body
val responseBody = response.body?.string() ?: throw IOException("Unexpected code ${response.code}, and response body is empty")
val jsonBody = JSONObject(responseBody)
val errorMessage = jsonBody.optString("message")
if (errorMessage.isNotEmpty()) {
throw IOException("Error ${response.code}. $errorMessage")
}
throw IOException("Error ${response.code} - ${response.body}")
}
val responseBody = response.body?.string() ?: throw IOException("Response body is null")
val jsonBody = JSONObject(responseBody)
val jsonArray = jsonBody.getJSONArray("data")
// Parse JSON response into BooruImage instances
val images = mutableListOf<BooruImage>()
for (i in 0 until jsonArray.length()) {
val jsonObject = jsonArray.getJSONObject(i)
val imageUrl = jsonObject.optString("path")
val title = jsonObject.optString("id")
val author = jsonObject.optString("source")
val sourceUri = jsonObject.optString("source")
if (imageUrl.isNotEmpty()) {
images.add(BooruImage(imageUrl, title, author, sourceUri))
}
}
return images
}
}
}
class BooruImage(

View file

@ -2,13 +2,40 @@ package nl.kurocon.plugin.wallpaperprovider.booru
import android.content.Context
import android.content.SharedPreferences
import androidx.annotation.IntDef
import androidx.preference.PreferenceManager
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.ToNumberPolicy
import com.google.gson.reflect.TypeToken
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.DANBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.MOEBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.GELBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.ZEROCHAN
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.WALLHAVEN
object PreferencesManager {
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.SOURCE)
@IntDef(DANBOORU, MOEBOORU, GELBOORU, ZEROCHAN, WALLHAVEN)
annotation class BooruType {
companion object {
const val DANBOORU = 1
const val MOEBOORU = 2
const val GELBOORU = 3
const val ZEROCHAN = 4
const val WALLHAVEN = 5
}
}
private val BOORU_TYPE_NAMES = mapOf(
DANBOORU to "Danbooru",
MOEBOORU to "Moebooru",
GELBOORU to "Gelbooru",
ZEROCHAN to "Zerochan",
WALLHAVEN to "Wallhaven",
)
private const val BOORU_URL_KEY = "booru_url_key"
private const val BOORU_TYPE_KEY = "booru_type_key"
private const val BOORU_TAG_SEARCH_KEY = "booru_tag_search_key"
@ -27,6 +54,10 @@ object PreferencesManager {
editor.apply()
}
fun getBooruName(type: @BooruType Int): String {
return BOORU_TYPE_NAMES[type]!!
}
operator fun set(key: String, value: Any?) =
when (value) {
is String? -> preferences.edit { it.putString(key, value) }
@ -40,21 +71,30 @@ object PreferencesManager {
inline operator fun <reified T : Any> get(
key: String,
defaultValue: T? = null
): T =
when (T::class) {
String::class -> preferences.getString(key, defaultValue as String? ?: "") as T
Int::class -> preferences.getInt(key, defaultValue as? Int ?: -1) as T
Boolean::class -> preferences.getBoolean(key, defaultValue as? Boolean ?: false) as T
Float::class -> preferences.getFloat(key, defaultValue as? Float ?: -1f) as T
Long::class -> preferences.getLong(key, defaultValue as? Long ?: -1) as T
else -> throw UnsupportedOperationException("Not yet implemented")
): T {
try {
when (T::class) {
String::class -> return preferences.getString(key, defaultValue as String? ?: "") as T
Int::class -> return preferences.getInt(key, defaultValue as? Int ?: -1) as T
Boolean::class -> return preferences.getBoolean(
key,
defaultValue as? Boolean ?: false
) as T
Float::class -> return preferences.getFloat(key, defaultValue as? Float ?: -1f) as T
Long::class -> return preferences.getLong(key, defaultValue as? Long ?: -1) as T
else -> throw UnsupportedOperationException("Not yet implemented")
}
} catch (e: ClassCastException) {
return defaultValue as T
}
}
var booruUrl: String
get () = PreferencesManager[BOORU_URL_KEY, "https://danbooru.donmai.us"]
set(value) { PreferencesManager[BOORU_URL_KEY] = value }
var booruType: String
get () = PreferencesManager[BOORU_TYPE_KEY, "danbooru"]
var booruType: @BooruType Int
get () = PreferencesManager[BOORU_TYPE_KEY, DANBOORU]
set(value) { PreferencesManager[BOORU_TYPE_KEY] = value }
var booruTagSearch: String
get () = PreferencesManager[BOORU_TAG_SEARCH_KEY, "ratio:16:9 rating:general order:random"]
@ -99,4 +139,4 @@ object PreferencesManager {
}
return true
}
}
}

View file

@ -6,6 +6,11 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.leanback.app.GuidedStepSupportFragment
import androidx.leanback.widget.GuidanceStylist.Guidance
import androidx.leanback.widget.GuidedAction
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.DANBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.GELBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.MOEBOORU
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.ZEROCHAN
import nl.kurocon.plugin.wallpaperprovider.booru.PreferencesManager.BooruType.Companion.WALLHAVEN
import kotlin.CharSequence
class SettingsFragment : GuidedStepSupportFragment() {
@ -32,20 +37,50 @@ class SettingsFragment : GuidedStepSupportFragment() {
.build()
actions.add(actionBooruUrl)
// Booru Type setting (choice menu with only Danbooru currently)
val booruTypeSubActions: MutableList<GuidedAction> = mutableListOf();
val typeDanbooruSubAction = GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_DANBOORU)
.title(R.string.setting_booru_type_subtype_danbooru_title)
.description(R.string.setting_booru_type_subtype_danbooru_description)
.build()
booruTypeSubActions.add(typeDanbooruSubAction)
// Booru Type subaction list (choice menu)
val booruTypeSubActions: MutableList<GuidedAction> = mutableListOf(
// Danbooru
GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_DANBOORU)
.title(R.string.setting_booru_type_subtype_danbooru_title)
.description(R.string.setting_booru_type_subtype_danbooru_description)
.build(),
// Moebooru
GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_MOEBOORU)
.title(R.string.setting_booru_type_subtype_moebooru_title)
.description(R.string.setting_booru_type_subtype_moebooru_description)
.build(),
// Gelbooru
GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_GELBOORU)
.title(R.string.setting_booru_type_subtype_gelbooru_title)
.description(R.string.setting_booru_type_subtype_gelbooru_description)
.build(),
// Zerochan
GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_ZEROCHAN)
.title(R.string.setting_booru_type_subtype_zerochan_title)
.description(R.string.setting_booru_type_subtype_zerochan_description)
.build(),
// Wallhaven.cc
GuidedAction.Builder(context)
.id(SUBACTION_ID_BOORU_TYPE_WALLHAVEN)
.title(R.string.setting_booru_type_subtype_wallhaven_title)
.description(R.string.setting_booru_type_subtype_wallhaven_description)
.build(),
);
// Actual Booru Type choice button with subactions
val currentBooruType = PreferencesManager.booruType
val actionBooruType = GuidedAction.Builder(context)
.id(ACTION_ID_BOORU_TYPE)
.title(R.string.setting_booru_type_title)
.description(currentBooruType)
.description(PreferencesManager.getBooruName(currentBooruType))
.subActions(booruTypeSubActions)
.build()
actions.add(actionBooruType)
@ -104,10 +139,13 @@ class SettingsFragment : GuidedStepSupportFragment() {
override fun onSubGuidedActionClicked(action: GuidedAction): Boolean {
when (action.id) {
SUBACTION_ID_BOORU_TYPE_DANBOORU -> {
findActionById(ACTION_ID_BOORU_TYPE)?.description = "danbooru"
SUBACTION_ID_BOORU_TYPE_DANBOORU, SUBACTION_ID_BOORU_TYPE_MOEBOORU,
SUBACTION_ID_BOORU_TYPE_GELBOORU, SUBACTION_ID_BOORU_TYPE_ZEROCHAN,
SUBACTION_ID_BOORU_TYPE_WALLHAVEN -> {
val bType = BOORU_TYPE_ACTION_MAP[action.id]!!
findActionById(ACTION_ID_BOORU_TYPE)?.description = PreferencesManager.getBooruName(bType)
notifyActionChanged(findActionPositionById(ACTION_ID_BOORU_TYPE))
PreferencesManager.booruType = "danbooru"
PreferencesManager.booruType = bType
}
}
return true
@ -151,5 +189,17 @@ class SettingsFragment : GuidedStepSupportFragment() {
private const val ACTION_ID_BOORU_API_KEY = 6L
private const val SUBACTION_ID_BOORU_TYPE_DANBOORU = 7L
private const val SUBACTION_ID_BOORU_TYPE_MOEBOORU = 8L
private const val SUBACTION_ID_BOORU_TYPE_GELBOORU = 9L
private const val SUBACTION_ID_BOORU_TYPE_ZEROCHAN = 10L
private const val SUBACTION_ID_BOORU_TYPE_WALLHAVEN = 11L
private val BOORU_TYPE_ACTION_MAP = mapOf(
SUBACTION_ID_BOORU_TYPE_DANBOORU to DANBOORU,
SUBACTION_ID_BOORU_TYPE_MOEBOORU to MOEBOORU,
SUBACTION_ID_BOORU_TYPE_GELBOORU to GELBOORU,
SUBACTION_ID_BOORU_TYPE_ZEROCHAN to ZEROCHAN,
SUBACTION_ID_BOORU_TYPE_WALLHAVEN to WALLHAVEN,
)
}
}

View file

@ -1,5 +1,6 @@
<resources>
<string name="plugin_name">Booru Wallpaper Provider</string>
<string name="plugin_version">1.1</string>
<string name="plugin_description">
A wallpaper provider that can talk to Booru sites running Danbooru software.
</string>
@ -8,7 +9,7 @@
<!-- Setting parameters -->
<string name="settings">Settings</string>
<string name="setting_image_url_title">Image URL</string>
<string name="setting_booru_url_title">Booru URL - e.g. \'https://danbooru.donmai.us/\'</string>
<string name="setting_booru_url_title">Booru URL - e.g. \'https://danbooru.donmai.us\'</string>
<string name="setting_booru_type_title">Booru Type</string>
<string name="setting_booru_tag_search_title">Search query - e.g. \'ratio:16:9 rating:general\'</string>
<string name="setting_booru_user_id_title">Booru Username </string>
@ -20,5 +21,13 @@
</string>
<string name="setting_booru_type_subtype_danbooru_title">Danbooru</string>
<string name="setting_booru_type_subtype_danbooru_description">Danbooru-based (danbooru.donmai.us) - Currently the only supported type</string>
<string name="setting_booru_type_subtype_danbooru_description">Danbooru-based (danbooru.donmai.us)</string>
<string name="setting_booru_type_subtype_moebooru_title">Moebooru (Danbooru v1)</string>
<string name="setting_booru_type_subtype_moebooru_description">Moebooru-based (konachan.com, yande.re)</string>
<string name="setting_booru_type_subtype_gelbooru_title">Gelbooru</string>
<string name="setting_booru_type_subtype_gelbooru_description">Gelbooru-based (gelbooru.com)</string>
<string name="setting_booru_type_subtype_zerochan_title">Zerochan</string>
<string name="setting_booru_type_subtype_zerochan_description">Zerochan-based (zerochan.net, asiachan.com)</string>
<string name="setting_booru_type_subtype_wallhaven_title">Wallhaven</string>
<string name="setting_booru_type_subtype_wallhaven_description">Wallhaven-based (wallhaven.cc)</string>
</resources>