From f2b523545bfcfd020f5b7a3688a2287786bea373 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Tue, 29 Apr 2025 22:26:45 +0200 Subject: [PATCH] booru: proper danbooru support, fix gelbooru --- .../quickshell/modules/common/Appearance.qml | 2 + .../modules/common/widgets/StyledToolTip.qml | 3 +- .../quickshell/modules/sidebarLeft/Anime.qml | 118 +++++++++++++----- .../modules/sidebarLeft/anime/BooruImage.qml | 29 ++++- .../sidebarLeft/anime/BooruResponse.qml | 12 +- .config/quickshell/services/Booru.qml | 19 ++- 6 files changed, 145 insertions(+), 38 deletions(-) diff --git a/.config/quickshell/modules/common/Appearance.qml b/.config/quickshell/modules/common/Appearance.qml index cbb54071c..cb000bfab 100644 --- a/.config/quickshell/modules/common/Appearance.qml +++ b/.config/quickshell/modules/common/Appearance.qml @@ -113,12 +113,14 @@ Singleton { property color colOnLayer1Inactive: mix(colOnLayer1, colLayer1, 0.45); property color colLayer2: mix(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 0.55); property color colOnLayer2: m3colors.m3onSurface; + property color colOnLayer2Disabled: mix(colOnLayer2, m3colors.m3background, 0.4); property color colLayer3: mix(m3colors.m3surfaceContainerHigh, m3colors.m3onSurface, 0.96); property color colOnLayer3: m3colors.m3onSurface; property color colLayer1Hover: mix(colLayer1, colOnLayer1, 0.88); property color colLayer1Active: mix(colLayer1, colOnLayer1, 0.77); property color colLayer2Hover: mix(colLayer2, colOnLayer2, 0.90); property color colLayer2Active: mix(colLayer2, colOnLayer2, 0.80); + property color colLayer2Disabled: mix(colLayer2, m3colors.m3background, 0.8); property color colLayer3Hover: mix(colLayer3, colOnLayer3, 0.90); property color colLayer3Active: mix(colLayer3, colOnLayer3, 0.80); property color colPrimaryHover: mix(m3colors.m3primary, colLayer1Hover, 0.85) diff --git a/.config/quickshell/modules/common/widgets/StyledToolTip.qml b/.config/quickshell/modules/common/widgets/StyledToolTip.qml index 73a06c92f..ff01a424f 100644 --- a/.config/quickshell/modules/common/widgets/StyledToolTip.qml +++ b/.config/quickshell/modules/common/widgets/StyledToolTip.qml @@ -9,7 +9,7 @@ ToolTip { property bool extraVisibleCondition: true property bool alternativeVisibleCondition: false property bool internalVisibleCondition: false - padding: 7 + padding: 5 visible: ((extraVisibleCondition && (parent.hovered === undefined || parent?.hovered) && internalVisibleCondition)) || alternativeVisibleCondition @@ -50,6 +50,7 @@ ToolTip { StyledText { id: tooltipTextObject text: content + font.pixelSize: Appearance.font.pixelSize.smaller color: Appearance.colors.colOnTooltip wrapMode: Text.Wrap } diff --git a/.config/quickshell/modules/sidebarLeft/Anime.qml b/.config/quickshell/modules/sidebarLeft/Anime.qml index e5e41aeda..ec070f261 100644 --- a/.config/quickshell/modules/sidebarLeft/Anime.qml +++ b/.config/quickshell/modules/sidebarLeft/Anime.qml @@ -3,6 +3,7 @@ import "root:/services" import "root:/modules/common" import "root:/modules/common/widgets" import "./anime/" +import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -15,6 +16,14 @@ Item { id: root property var panelWindow property var inputField: tagInputField + property string previewDownloadPath: `${StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]}/media/waifus`.replace("file://", "") + property string downloadPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework").replace("file://", "") + property string nsfwPath: (StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0] + "/homework/🌶️").replace("file://", "") + + Component.onCompleted: { + Hyprland.dispatch(`exec rm -rf ${previewDownloadPath}`) + Hyprland.dispatch(`exec mkdir -p ${previewDownloadPath}`) + } function handleInput(inputText) { if (inputText.startsWith("/")) { @@ -28,9 +37,15 @@ Item { const newProvider = args[0]; Booru.setProvider(newProvider); } - else if (command == "lewd" || command == "nsfw") { + else if (command == "nsfw") { ConfigOptions.sidebar.booru.allowNsfw = !ConfigOptions.sidebar.booru.allowNsfw } + else if (command == "safe") { + ConfigOptions.sidebar.booru.allowNsfw = false + } + else if (command == "lewd") { + ConfigOptions.sidebar.booru.allowNsfw = true + } } else { @@ -105,6 +120,9 @@ Item { delegate: BooruResponse { responseData: modelData tagInputField: root.inputField + previewDownloadPath: root.previewDownloadPath + downloadPath: root.downloadPath + nsfwPath: root.nsfwPath } } @@ -164,34 +182,78 @@ Item { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - TextArea { // The actual input field widget - id: tagInputField - wrapMode: TextArea.Wrap - Layout.fillWidth: true + + RowLayout { Layout.topMargin: 5 - padding: 10 - color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant - renderType: Text.NativeRendering - selectedTextColor: Appearance.m3colors.m3onPrimary - selectionColor: Appearance.m3colors.m3primary - placeholderText: qsTr("Enter tags") - placeholderTextColor: Appearance.m3colors.m3outline + spacing: 0 + TextArea { // The actual input field widget + id: tagInputField + wrapMode: TextArea.Wrap + Layout.fillWidth: true + padding: 10 + color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant + renderType: Text.NativeRendering + selectedTextColor: Appearance.m3colors.m3onPrimary + selectionColor: Appearance.m3colors.m3primary + placeholderText: qsTr("Enter tags") + placeholderTextColor: Appearance.m3colors.m3outline - background: Item {} + background: Item {} - Keys.onPressed: (event) => { - if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { - if (event.modifiers & Qt.ShiftModifier) { - // Insert newline - tagInputField.insert(tagInputField.cursorPosition, "\n") - event.accepted = true - } else { // Accept text + Keys.onPressed: (event) => { + if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) { + if (event.modifiers & Qt.ShiftModifier) { + // Insert newline + tagInputField.insert(tagInputField.cursorPosition, "\n") + event.accepted = true + } else { // Accept text + const inputText = tagInputField.text + root.handleInput(inputText) + tagInputField.clear() + event.accepted = true + } + } + } + } + Button { // Send button + id: sendButton + Layout.alignment: Qt.AlignTop + Layout.rightMargin: 5 + implicitWidth: 40 + implicitHeight: 40 + enabled: tagInputField.text.length > 0 + + MouseArea { + anchors.fill: parent + cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor + onClicked: { const inputText = tagInputField.text root.handleInput(inputText) tagInputField.clear() - event.accepted = true } } + + background: Rectangle { + radius: Appearance.rounding.small + color: sendButton.enabled ? (sendButton.down ? Appearance.colors.colPrimaryActive : + sendButton.hovered ? Appearance.colors.colPrimaryHover : + Appearance.m3colors.m3primary) : Appearance.colors.colLayer2Disabled + + Behavior on color { + ColorAnimation { + duration: Appearance.animation.elementDecel.duration + easing.type: Appearance.animation.elementDecel.type + } + } + } + + contentItem: MaterialSymbol { + anchors.centerIn: parent + text: "send" + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.larger + color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled + } } } @@ -216,8 +278,6 @@ Item { Item { implicitHeight: providerRowLayout.implicitHeight + 5 * 2 implicitWidth: providerRowLayout.implicitWidth + 10 * 2 - // radius: Appearance.rounding.small - // color: Appearance.colors.colLayer1 RowLayout { id: providerRowLayout @@ -249,7 +309,7 @@ Item { } StyledText { - font.pixelSize: Appearance.font.pixelSize.small + font.pixelSize: Appearance.font.pixelSize.large color: Appearance.colors.colOnLayer1 text: "•" } @@ -267,13 +327,13 @@ Item { Layout.leftMargin: 10 Layout.alignment: Qt.AlignVCenter font.pixelSize: Appearance.font.pixelSize.smaller - color: Appearance.colors.coloOnLayer1 + color: Appearance.colors.colOnLayer1 text: qsTr("NSFW") } StyledSwitch { id: nsfwSwitch enabled: Booru.currentProvider !== "zerochan" - scale: 0.75 + scale: 0.6 Layout.alignment: Qt.AlignVCenter checked: (ConfigOptions.sidebar.booru.allowNsfw && Booru.currentProvider !== "zerochan") onCheckedChanged: { @@ -294,9 +354,9 @@ Item { buttonText: modelData.name background: Rectangle { radius: Appearance.rounding.small - color: (tagButton.down ? Appearance.colors.colLayer1Active : - tagButton.hovered ? Appearance.colors.colLayer1Hover : - Appearance.transparentize(Appearance.colors.colLayer1, 1)) + color: tagButton.down ? Appearance.colors.colLayer2Active : + tagButton.hovered ? Appearance.colors.colLayer2Hover : + Appearance.colors.colLayer2 Behavior on color { ColorAnimation { diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml index 6bd450a42..3160b1b58 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruImage.qml @@ -1,9 +1,11 @@ import "root:/" import "root:/modules/common" import "root:/modules/common/widgets" +import Qt.labs.platform import QtQuick import QtQuick.Controls import QtQuick.Layouts +import Quickshell.Io import Quickshell.Hyprland import Qt5Compat.GraphicalEffects @@ -11,10 +13,31 @@ Button { id: root property var imageData property var rowHeight + property bool manualDownload: false + property string previewDownloadPath + property string downloadPath + property string nsfwPath + property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1)) - // onImageDataChanged: { - // console.log("Image data changed:", imageData) - // } + Process { + id: downloadProcess + running: false + command: ["bash", "-c", `curl '${imageData.preview_url}' -o '${previewDownloadPath}/${root.fileName}' && echo 'done'`] + stdout: SplitParser { + onRead: (data) => { + console.log("Download output:", data) + if(data.includes("done")) { + imageObject.source = `${previewDownloadPath}/${root.fileName}` + } + } + } + } + + Component.onCompleted: { + if (root.manualDownload) { + downloadProcess.running = true + } + } padding: 0 implicitWidth: imageObject.width diff --git a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml index dabf247cf..681906b16 100644 --- a/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml +++ b/.config/quickshell/modules/sidebarLeft/anime/BooruResponse.qml @@ -17,11 +17,15 @@ Rectangle { property var responseData property var tagInputField + property string previewDownloadPath + property string downloadPath + property string nsfwPath + onResponseDataChanged: { console.log("Response data changed:", responseData) } - property real availableWidth: parent?.width + property real availableWidth: parent.width ?? 0 property real rowTooShortThreshold: 100 property real imageSpacing: 5 property real responsePadding: 5 @@ -65,7 +69,7 @@ Rectangle { visible: root.responseData.page != "" && root.responseData.page > 0 implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30) implicitHeight: pageNumber.implicitHeight + 5 * 2 - Layout.alignment: Qt.AlignTop + Layout.alignment: Qt.AlignVCenter StyledText { id: pageNumber @@ -205,6 +209,10 @@ Rectangle { delegate: BooruImage { imageData: modelData rowHeight: imageRow.rowHeight + manualDownload: root.responseData.provider == "danbooru" + previewDownloadPath: root.previewDownloadPath + downloadPath: root.downloadPath + nsfwPath: root.nsfwPath } } } diff --git a/.config/quickshell/services/Booru.qml b/.config/quickshell/services/Booru.qml index 1357d4f17..d22985a23 100644 --- a/.config/quickshell/services/Booru.qml +++ b/.config/quickshell/services/Booru.qml @@ -17,6 +17,7 @@ Singleton { } } + property string failMessage: qsTr("That didn't work. Tips:\n- Check your tags and NSFW settings\n- If you don't have a tag in mind, type a page number") property var responses: [] property var getWorkingImageSource: (url) => { if (url.includes('pximg.net')) { @@ -203,7 +204,12 @@ Singleton { else { params.push("tags=" + encodeURIComponent(tagString)) params.push("limit=" + limit) - params.push("page=" + page) + if (currentProvider == "gelbooru") { + params.push("pid=" + page) + } + else { + params.push("page=" + page) + } } var url = baseUrl if (baseUrl.indexOf("?") === -1) { @@ -224,7 +230,7 @@ Singleton { if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) { try { // console.log("[Booru] Raw response length: " + xhr.responseText.length) - // console.log("[Booru] Raw response: " + xhr.responseText) + console.log("[Booru] Raw response: " + xhr.responseText) var response = JSON.parse(xhr.responseText) // Access nested properties based on listAccess @@ -243,11 +249,18 @@ Singleton { "tags": tags, "page": page, "images": response, - "message": "" + "message": response.length > 0 ? "" : root.failMessage }] } catch (e) { console.log("[Booru] Failed to parse response: " + e) + root.responses = [...root.responses, { + "provider": currentProvider, + "tags": tags, + "page": page, + "images": [], + "message": root.failMessage + }] } } else if (xhr.readyState === XMLHttpRequest.DONE) {