From 8d1d74c5e8bd1402853e3bb073d59b6ed3902505 Mon Sep 17 00:00:00 2001 From: dev-claw <53543762+dev-claw@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:51:24 +0100 Subject: [PATCH 1/3] fixes #303 (#306) --- .../main/kotlin/me/vripper/host/ImxHost.kt | 18 +++-- .../main/kotlin/me/vripper/model/Settings.kt | 18 ++++- vripper-core/src/main/proto/settings.proto | 5 ++ .../fragments/HostSettingsFragment.kt | 76 +++++++++++++++++++ .../components/fragments/SettingsFragment.kt | 11 ++- .../gui/components/views/ActionBarView.kt | 2 + .../gui/components/views/MenuBarView.kt | 2 + .../gui/controller/SettingsController.kt | 26 +++++-- .../gui/model/settings/HostSettingsModel.kt | 27 +++++++ .../gui/services/GrpcEndpointService.kt | 17 +++++ .../web/grpc/GrpcServerAppEndpointService.kt | 9 ++- 11 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/HostSettingsFragment.kt create mode 100644 vripper-gui/src/main/kotlin/me/vripper/gui/model/settings/HostSettingsModel.kt diff --git a/vripper-core/src/main/kotlin/me/vripper/host/ImxHost.kt b/vripper-core/src/main/kotlin/me/vripper/host/ImxHost.kt index 43697db..bccc211 100644 --- a/vripper-core/src/main/kotlin/me/vripper/host/ImxHost.kt +++ b/vripper-core/src/main/kotlin/me/vripper/host/ImxHost.kt @@ -2,6 +2,8 @@ package me.vripper.host import me.vripper.entities.ImageEntity import me.vripper.exception.HostException +import me.vripper.model.HostName +import me.vripper.model.HostSettingKey import me.vripper.services.download.ImageDownloadRunnable import me.vripper.utilities.HtmlUtils import me.vripper.utilities.LoggerDelegate @@ -20,9 +22,7 @@ internal class ImxHost : Host("imx.to", 8) { context: ImageDownloadRunnable.Context ): Pair { log.debug("Resolving name and image url for ${context.imageEntity.url}") - val imgTitle = getTitle(context).ifEmpty { - getDefaultImageName(context.imageEntity.thumbUrl) - } + val imgTitle = getTitle(context) val imgUrl = findPattern(context.imageEntity) return Pair( imgTitle, imgUrl @@ -30,7 +30,10 @@ internal class ImxHost : Host("imx.to", 8) { } private fun getTitle(context: ImageDownloadRunnable.Context): String { - return try { + + return if (context.settings.hostSettings[HostName.IMX]?.get(HostSettingKey.TRY_TO_FETCH_ORIGINAL_FILENAME) + .toBoolean() + ) { val httpsUrl = context.imageEntity.url.replace("http:", "https:") val document = fetchDocument(httpsUrl, context) var value: String? = null @@ -58,9 +61,10 @@ internal class ImxHost : Host("imx.to", 8) { log.debug("Resolving name for $httpsUrl") val imgTitle = imgNode?.attributes?.getNamedItem("alt")?.textContent?.trim() ?: "" - return imgTitle - } catch (_: Exception) { - "" + imgTitle + + } else { + getDefaultImageName(context.imageEntity.thumbUrl) } } diff --git a/vripper-core/src/main/kotlin/me/vripper/model/Settings.kt b/vripper-core/src/main/kotlin/me/vripper/model/Settings.kt index aa1f046..d79d54f 100644 --- a/vripper-core/src/main/kotlin/me/vripper/model/Settings.kt +++ b/vripper-core/src/main/kotlin/me/vripper/model/Settings.kt @@ -5,12 +5,27 @@ import me.vripper.utilities.ApplicationProperties.VRIPPER_DIR import java.nio.file.Files import kotlin.io.path.pathString +enum class HostName { + IMX +} + +enum class HostSettingKey(val type: SettingType) { + TRY_TO_FETCH_ORIGINAL_FILENAME(SettingType.BOOLEAN) +} + +enum class SettingType { + STRING, BOOLEAN, INT +} + @Serializable data class Settings( val connectionSettings: ConnectionSettings = ConnectionSettings(), val downloadSettings: DownloadSettings = DownloadSettings(), val viperSettings: ViperSettings = ViperSettings(), - val systemSettings: SystemSettings = SystemSettings() + val systemSettings: SystemSettings = SystemSettings(), + val hostSettings: Map> = mapOf( + HostName.IMX to mapOf(HostSettingKey.TRY_TO_FETCH_ORIGINAL_FILENAME to "true") + ) ) @Serializable @@ -51,3 +66,4 @@ data class SystemSettings( val clipboardPollingRate: Int = 500, val maxEventLog: Int = 1_000, ) + diff --git a/vripper-core/src/main/proto/settings.proto b/vripper-core/src/main/proto/settings.proto index e63bda5..01db0f8 100644 --- a/vripper-core/src/main/proto/settings.proto +++ b/vripper-core/src/main/proto/settings.proto @@ -36,9 +36,14 @@ message SystemSettings { int32 maxEventLog = 4; } +message HostSettingsMap { + map settings = 1; +} + message Settings { ConnectionSettings connectionSettings = 1; DownloadSettings downloadSettings = 2; ViperSettings viperSettings = 3; SystemSettings systemSettings = 4; + map hostSettings = 5; } \ No newline at end of file diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/HostSettingsFragment.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/HostSettingsFragment.kt new file mode 100644 index 0000000..1b823c7 --- /dev/null +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/HostSettingsFragment.kt @@ -0,0 +1,76 @@ +package me.vripper.gui.components.fragments + +import atlantafx.base.util.IntegerStringConverter +import javafx.scene.control.Spinner +import me.vripper.gui.model.settings.HostSettingsModel +import me.vripper.model.HostName +import me.vripper.model.HostSettingKey +import me.vripper.model.SettingType +import tornadofx.* + +class HostSettingsFragment : Fragment("Host Settings") { + + val hostSettings: Map> by param() + val hostSettingsModels: List + + override val root = vbox {} + + init { + val mutableHostSettingsModels = mutableListOf() + + // Build models dynamically from hostSettings map + hostSettings.forEach { (hostName, settingsMap) -> + settingsMap.forEach { (settingKey, settingValue) -> + try { + val key = settingKey + val model = HostSettingsModel( + host = hostName, + settingKey = key, + initialValue = settingValue + ) + mutableHostSettingsModels.add(model) + } catch (e: IllegalArgumentException) { + // Skip unknown settings + } + } + } + + hostSettingsModels = mutableHostSettingsModels.toList() + + // Build UI dynamically + with(root) { + val groupedByHost = hostSettingsModels.groupBy { it.host } + + form { + groupedByHost.forEach { (hostName, settings) -> + fieldset(hostName.name) { + settings.forEach { model -> + field( + model.settingKey.name.replace("_", " ").lowercase() + .replaceFirstChar { it.uppercase() }) { + when (model.settingKey.type) { + SettingType.STRING -> textfield(model.valueProperty as javafx.beans.property.StringProperty) + SettingType.BOOLEAN -> checkbox( + "", + model.valueProperty as javafx.beans.property.BooleanProperty + ) + + SettingType.INT -> { + add(Spinner(Int.MIN_VALUE, Int.MAX_VALUE, model.getValue().toInt()).apply { + valueProperty().onChange { + (model.valueProperty as javafx.beans.property.IntegerProperty).value = + it ?: 0 + } + isEditable = true + IntegerStringConverter.createFor(this) + }) + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/SettingsFragment.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/SettingsFragment.kt index 19852f2..6bb472b 100644 --- a/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/SettingsFragment.kt +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/components/fragments/SettingsFragment.kt @@ -24,6 +24,7 @@ class SettingsFragment : Fragment("Settings") { val connectionSettings: ConnectionSettings by param() val viperSettings: ViperSettings by param() val systemSettings: SystemSettings by param() + val hostSettings: Map> by param() private val settingsController: SettingsController by inject() private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val downloadSettingsFragment: DownloadSettingsFragment = @@ -34,6 +35,9 @@ class SettingsFragment : Fragment("Settings") { find(mapOf(ViperSettingsFragment::viperSettings to viperSettings)) private val systemSettingsFragment: SystemSettingsFragment = find(mapOf(SystemSettingsFragment::systemSettings to systemSettings)) + private val hostSettingsFragment: HostSettingsFragment = + find(mapOf(HostSettingsFragment::hostSettings to hostSettings)) + override val root = vbox(alignment = Pos.CENTER_RIGHT) { spacing = 5.0 @@ -60,6 +64,10 @@ class SettingsFragment : Fragment("Settings") { add(viperSettingsFragment) graphic = FontIcon.of(Feather.LINK_2) } + tab(hostSettingsFragment.title) { + add(hostSettingsFragment) + graphic = FontIcon.of(Feather.IMAGE) + } } borderpane { right { @@ -75,7 +83,8 @@ class SettingsFragment : Fragment("Settings") { downloadSettingsFragment.downloadSettingsModel, connectionSettingsFragment.connectionSettingsModel, viperSettingsFragment.viperSettingsModel, - systemSettingsFragment.systemSettingsModel + systemSettingsFragment.systemSettingsModel, + hostSettingsFragment.hostSettingsModels ) runLater { close() diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/ActionBarView.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/ActionBarView.kt index d54601e..b16ba1c 100644 --- a/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/ActionBarView.kt +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/ActionBarView.kt @@ -97,6 +97,7 @@ class ActionBarView : View() { val connectionSettings = settingsController.findConnectionSettings() val viperGirlsSettings = settingsController.findViperGirlsSettings() val systemSettings = settingsController.findSystemSettings() + val hostSettings = settingsController.findHostSettings() runLater { find( mapOf( @@ -104,6 +105,7 @@ class ActionBarView : View() { SettingsFragment::connectionSettings to connectionSettings, SettingsFragment::viperSettings to viperGirlsSettings, SettingsFragment::systemSettings to systemSettings, + SettingsFragment::hostSettings to hostSettings, ) ).openModal() } diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/MenuBarView.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/MenuBarView.kt index 7815589..20e6dc9 100644 --- a/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/MenuBarView.kt +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/components/views/MenuBarView.kt @@ -99,6 +99,7 @@ class MenuBarView : View() { val connectionSettings = settingsController.findConnectionSettings() val viperGirlsSettings = settingsController.findViperGirlsSettings() val systemSettings = settingsController.findSystemSettings() + val hostSettings = settingsController.findHostSettings() runLater { find( mapOf( @@ -106,6 +107,7 @@ class MenuBarView : View() { SettingsFragment::connectionSettings to connectionSettings, SettingsFragment::viperSettings to viperGirlsSettings, SettingsFragment::systemSettings to systemSettings, + SettingsFragment::hostSettings to hostSettings, ) ).openModal() } diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/controller/SettingsController.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/controller/SettingsController.kt index 995f2d8..0ade803 100644 --- a/vripper-gui/src/main/kotlin/me/vripper/gui/controller/SettingsController.kt +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/controller/SettingsController.kt @@ -1,9 +1,6 @@ package me.vripper.gui.controller -import me.vripper.gui.model.settings.ConnectionSettingsModel -import me.vripper.gui.model.settings.DownloadSettingsModel -import me.vripper.gui.model.settings.SystemSettingsModel -import me.vripper.gui.model.settings.ViperSettingsModel +import me.vripper.gui.model.settings.* import me.vripper.gui.utils.AppEndpointManager import me.vripper.model.* import tornadofx.Controller @@ -36,11 +33,18 @@ class SettingsController : Controller() { ) } + suspend fun findHostSettings(): Map> { + return runCatching { AppEndpointManager.currentAppEndpointService().getSettings().hostSettings }.getOrDefault( + emptyMap() + ) + } + suspend fun saveNewSettings( downloadSettingsModel: DownloadSettingsModel, connectionSettingsModel: ConnectionSettingsModel, viperSettingsModel: ViperSettingsModel, - systemSettingsModel: SystemSettingsModel + systemSettingsModel: SystemSettingsModel, + hostSettingsModel: List, ) { runCatching { AppEndpointManager.currentAppEndpointService().saveSettings( @@ -76,7 +80,17 @@ class SettingsController : Controller() { systemSettingsModel.enable, systemSettingsModel.pollingRate, systemSettingsModel.logEntries - ) + ), + hostSettings = run { + val hostSettingsMap = mutableMapOf>() + hostSettingsModel.forEach { model -> + val hostName = model.host + val settingKey = model.settingKey + hostSettingsMap.computeIfAbsent(hostName) { mutableMapOf() }[settingKey] = + model.valueProperty.value.toString() + } + hostSettingsMap + } ) ) } diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/model/settings/HostSettingsModel.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/model/settings/HostSettingsModel.kt new file mode 100644 index 0000000..fc7f680 --- /dev/null +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/model/settings/HostSettingsModel.kt @@ -0,0 +1,27 @@ +package me.vripper.gui.model.settings + +import javafx.beans.property.Property +import javafx.beans.property.SimpleBooleanProperty +import javafx.beans.property.SimpleIntegerProperty +import javafx.beans.property.SimpleStringProperty +import me.vripper.model.HostName +import me.vripper.model.HostSettingKey +import me.vripper.model.SettingType + +class HostSettingsModel( + val host: HostName, + val settingKey: HostSettingKey, + initialValue: String, +) { + val valueProperty: Property<*> = when (settingKey.type) { + SettingType.STRING -> SimpleStringProperty(initialValue) + SettingType.BOOLEAN -> SimpleBooleanProperty(initialValue.toBoolean()) + SettingType.INT -> SimpleIntegerProperty(initialValue.toIntOrNull() ?: 0) + } + + fun getValue(): String = when (settingKey.type) { + SettingType.STRING -> (valueProperty as SimpleStringProperty).value + SettingType.BOOLEAN -> (valueProperty as SimpleBooleanProperty).value.toString() + SettingType.INT -> (valueProperty as SimpleIntegerProperty).value.toString() + } +} diff --git a/vripper-gui/src/main/kotlin/me/vripper/gui/services/GrpcEndpointService.kt b/vripper-gui/src/main/kotlin/me/vripper/gui/services/GrpcEndpointService.kt index 5922259..d7c7871 100644 --- a/vripper-gui/src/main/kotlin/me/vripper/gui/services/GrpcEndpointService.kt +++ b/vripper-gui/src/main/kotlin/me/vripper/gui/services/GrpcEndpointService.kt @@ -223,6 +223,15 @@ internal class GrpcEndpointService : IAppEndpointService { this.downloadSettings = downloadSettings this.viperSettings = viperSettings this.systemSettings = systemSettings + settings.hostSettings.forEach { (hostKey, hostSettings) -> + val hostSetting = with(SettingsOuterClass.HostSettingsMap.newBuilder()) { + hostSettings.forEach { (settingKey, settingValue) -> + this.putSettings(settingKey.name, settingValue) + } + build() + } + this.putHostSettings(hostKey.name, hostSetting) + } build() } endpointServiceCoroutineStub!!.saveSettings(settingsRequest) @@ -280,6 +289,14 @@ internal class GrpcEndpointService : IAppEndpointService { clipboardPollingRate = settings.systemSettings.clipboardPollingRate, maxEventLog = settings.systemSettings.maxEventLog, ), + hostSettings = settings.hostSettingsMap.entries.associate { + val host = HostName.valueOf(it.key) + val hostSettings = it.value.settingsMap.entries.associate { hostSettingsEntry -> + val settingKey = HostSettingKey.valueOf(hostSettingsEntry.key) + settingKey to hostSettingsEntry.value + } + host to hostSettings + } ) diff --git a/vripper-web/src/main/kotlin/me/vripper/web/grpc/GrpcServerAppEndpointService.kt b/vripper-web/src/main/kotlin/me/vripper/web/grpc/GrpcServerAppEndpointService.kt index 9006569..8e94ead 100644 --- a/vripper-web/src/main/kotlin/me/vripper/web/grpc/GrpcServerAppEndpointService.kt +++ b/vripper-web/src/main/kotlin/me/vripper/web/grpc/GrpcServerAppEndpointService.kt @@ -213,7 +213,14 @@ class GrpcServerAppEndpointService : EndpointServiceGrpcKt.EndpointServiceCorout enableClipboardMonitoring = request.systemSettings.enableClipboardMonitoring, clipboardPollingRate = request.systemSettings.clipboardPollingRate, maxEventLog = request.systemSettings.maxEventLog, - ) + ), + hostSettings = request.hostSettingsMap.entries.associate { hostSettings -> + HostName.valueOf(hostSettings.key) to hostSettings.value.settingsMap.entries.associate { + HostSettingKey.valueOf( + it.key + ) to it.value + } + } ) ) return EndpointServiceOuterClass.EmptyResponse.getDefaultInstance() From a549105f7a3ac174e3ecdc11fe09f4b34167f98a Mon Sep 17 00:00:00 2001 From: dev-claw <53543762+dev-claw@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:51:33 +0100 Subject: [PATCH 2/3] fixes #302 (#305) --- .../main/kotlin/me/vripper/host/ImageBamHost.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/vripper-core/src/main/kotlin/me/vripper/host/ImageBamHost.kt b/vripper-core/src/main/kotlin/me/vripper/host/ImageBamHost.kt index 1bb0c28..375e67b 100644 --- a/vripper-core/src/main/kotlin/me/vripper/host/ImageBamHost.kt +++ b/vripper-core/src/main/kotlin/me/vripper/host/ImageBamHost.kt @@ -23,11 +23,18 @@ internal class ImageBamHost : Host("imagebam.com", 2) { val doc = try { log.debug(String.format("Looking for xpath expression %s in %s", CONTINUE_XPATH, context.imageEntity.url)) if (XpathUtils.getAsNode(document, CONTINUE_XPATH) != null) { - val sfwCookie = BasicClientCookie("sfw_inter", "1") - sfwCookie.domain = "www.imagebam.com" - sfwCookie.path = "/" - sfwCookie.setExpiryDate(LocalDateTime.now().plusDays(3).atZone(ZoneId.systemDefault()).toInstant()) + val nsfwCookie = BasicClientCookie("nsfw_inter", "1").apply { + domain = "www.imagebam.com" + path = "/" + setExpiryDate(LocalDateTime.now().plusDays(3).atZone(ZoneId.systemDefault()).toInstant()) + } + val sfwCookie = BasicClientCookie("sfw_inter", "1").apply { + domain = "www.imagebam.com" + path = "/" + setExpiryDate(LocalDateTime.now().plusDays(3).atZone(ZoneId.systemDefault()).toInstant()) + } + context.httpContext.cookieStore.addCookie(nsfwCookie) context.httpContext.cookieStore.addCookie(sfwCookie) fetch(context.imageEntity.url, context) { HtmlUtils.clean(it.entity.content) From 123ee14dd6f43e5343c065d9c70d355abfc48f3b Mon Sep 17 00:00:00 2001 From: dev-claw <53543762+dev-claw@users.noreply.github.com> Date: Thu, 30 Apr 2026 21:51:43 +0100 Subject: [PATCH 3/3] fixes #300 (#304) --- vripper-core/src/main/kotlin/me/vripper/host/Host.kt | 2 +- vripper-core/src/main/kotlin/me/vripper/host/ViprImHost.kt | 1 + .../me/vripper/services/download/ImageDownloadRunnable.kt | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/vripper-core/src/main/kotlin/me/vripper/host/Host.kt b/vripper-core/src/main/kotlin/me/vripper/host/Host.kt index 9fab3a0..cf1d868 100644 --- a/vripper-core/src/main/kotlin/me/vripper/host/Host.kt +++ b/vripper-core/src/main/kotlin/me/vripper/host/Host.kt @@ -171,9 +171,9 @@ internal abstract class Host( fun fetch( url: String, context: Context, - referer: String = "https://vipergirls.to/", transformer: (ClassicHttpResponse) -> T ): T { + val referer = context.headers["Referer"] ?: "https://vipergirls.to/" val httpGet = HttpGet(url).also { it.addHeader("Referer", referer) diff --git a/vripper-core/src/main/kotlin/me/vripper/host/ViprImHost.kt b/vripper-core/src/main/kotlin/me/vripper/host/ViprImHost.kt index 80ca9c1..070a57e 100644 --- a/vripper-core/src/main/kotlin/me/vripper/host/ViprImHost.kt +++ b/vripper-core/src/main/kotlin/me/vripper/host/ViprImHost.kt @@ -35,6 +35,7 @@ internal class ViprImHost : Host("vipr.im", 15) { .map { obj: Node -> obj.textContent } .map { obj: String -> obj.trim() }.orElse(null) val imgUrl = imgNode.attributes.getNamedItem("src").textContent.trim() + context.headers["Referer"] = "https://vipr.im/" Pair(imgTitle!!, imgUrl) } catch (e: Exception) { throw HostException("Unexpected error occurred", e) diff --git a/vripper-core/src/main/kotlin/me/vripper/services/download/ImageDownloadRunnable.kt b/vripper-core/src/main/kotlin/me/vripper/services/download/ImageDownloadRunnable.kt index 50fdd7b..6e45cbd 100644 --- a/vripper-core/src/main/kotlin/me/vripper/services/download/ImageDownloadRunnable.kt +++ b/vripper-core/src/main/kotlin/me/vripper/services/download/ImageDownloadRunnable.kt @@ -49,6 +49,7 @@ internal class ImageDownloadRunnable( cookieStore = HTTPService.cookieStore } val requests = mutableListOf() + val headers = mutableMapOf() init { ApplicationProperties.VRIPPER_DIR.listDirectoryEntries()