forked from Shinonome/dots-hyprland
booru: api caller service
This commit is contained in:
@@ -83,6 +83,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ActiveWindow {
|
ActiveWindow {
|
||||||
|
Layout.rightMargin: Appearance.rounding.screenRounding
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
bar: barRoot
|
bar: barRoot
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ ColumnLayout {
|
|||||||
z: 2
|
z: 2
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
// TODO: make the end point in the moving direction go first
|
||||||
anchors.leftMargin: {
|
anchors.leftMargin: {
|
||||||
const tabCount = root.tabButtonList.length
|
const tabCount = root.tabButtonList.length
|
||||||
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
|
const targetWidth = tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ ToolTip {
|
|||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: parent
|
target: parent
|
||||||
onHoveredChanged: {
|
function onHoveredChanged() {
|
||||||
if (parent.hovered) {
|
if (parent.hovered) {
|
||||||
tooltipShowDelay.restart()
|
tooltipShowDelay.restart()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import "root:/"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/modules/common"
|
||||||
|
import "root:/modules/common/widgets"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
onFocusChanged: (focus) => {
|
||||||
|
if (focus) {
|
||||||
|
tagInputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ListView { // Booru responses
|
||||||
|
id: booruResponseListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
model: Booru.responses
|
||||||
|
delegate: StyledText {
|
||||||
|
id: booruResponseText
|
||||||
|
text: JSON.stringify(modelData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
border.width: 1
|
||||||
|
border.color: Appearance.m3colors.m3outlineVariant
|
||||||
|
color: "transparent"
|
||||||
|
implicitWidth: tagInputField.implicitWidth
|
||||||
|
implicitHeight: tagInputField.implicitHeight
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Appearance.animation.elementDecel.duration
|
||||||
|
easing.type: Appearance.animation.elementDecel.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextArea {
|
||||||
|
id: tagInputField
|
||||||
|
anchors.fill: parent
|
||||||
|
wrapMode: TextArea.Wrap
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// Submit on Enter or Ctrl+Enter
|
||||||
|
const tagList = tagInputField.text.split(/\s+/);
|
||||||
|
Booru.makeRequest(tagList);
|
||||||
|
tagInputField.clear()
|
||||||
|
event.accepted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ Scope { // Scope
|
|||||||
id: root
|
id: root
|
||||||
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
property int sidebarWidth: Appearance.sizes.sidebarWidth
|
||||||
property int sidebarPadding: 15
|
property int sidebarPadding: 15
|
||||||
property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "flare", "name": qsTr("Waifus")}]
|
property var tabButtonList: [{"icon": "neurology", "name": qsTr("Intelligence")}, {"icon": "bookmark_heart", "name": qsTr("Anime")}]
|
||||||
|
|
||||||
Variants { // Window repeater
|
Variants { // Window repeater
|
||||||
id: sidebarVariants
|
id: sidebarVariants
|
||||||
@@ -88,7 +88,6 @@ Scope { // Scope
|
|||||||
sidebarRoot.visible = false;
|
sidebarRoot.visible = false;
|
||||||
}
|
}
|
||||||
if (event.modifiers === Qt.ControlModifier) {
|
if (event.modifiers === Qt.ControlModifier) {
|
||||||
console.log("Control pressed")
|
|
||||||
if (event.key === Qt.Key_PageDown) {
|
if (event.key === Qt.Key_PageDown) {
|
||||||
sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1)
|
sidebarRoot.currentTab = Math.min(sidebarRoot.currentTab + 1, root.tabButtonList.length - 1)
|
||||||
} else if (event.key === Qt.Key_PageUp) {
|
} else if (event.key === Qt.Key_PageUp) {
|
||||||
@@ -133,8 +132,11 @@ Scope { // Scope
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {}
|
StyledText {
|
||||||
Item {}
|
text: "To be implemented"
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
Anime {}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell;
|
||||||
|
import Quickshell.Io;
|
||||||
|
import Qt.labs.platform
|
||||||
|
import QtQuick;
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var getWorkingImageSource: (url) => {
|
||||||
|
if (url.includes('pximg.net')) {
|
||||||
|
return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\d+\.(png|jpg|jpeg|gif)$/, '')}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
property var providerList: ["yandere", "konachan", "danbooru", "gelbooru"]
|
||||||
|
property var providers: {
|
||||||
|
"yandere": {
|
||||||
|
"name": "yande.re",
|
||||||
|
"url": "https://yande.re",
|
||||||
|
"api": "https://yande.re/post.json",
|
||||||
|
"listAccess": [],
|
||||||
|
"mapFunc": (response) => {
|
||||||
|
return response.map(item => {
|
||||||
|
return {
|
||||||
|
"id": item.id,
|
||||||
|
"aspect_ratio": item.width / item.height,
|
||||||
|
"tags": item.tags,
|
||||||
|
"rating": item.rating,
|
||||||
|
"is_nsfw": (item.rating != 's'),
|
||||||
|
"md5": item.md5,
|
||||||
|
"preview_url": item.preview_url,
|
||||||
|
"sample_url": item.sample_url ?? item.file_url,
|
||||||
|
"file_url": item.file_url,
|
||||||
|
"file_ext": item.file_ext,
|
||||||
|
"source": getWorkingImageSource(item.source),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"konachan": {
|
||||||
|
"name": "Konachan",
|
||||||
|
"url": "https://konachan.com",
|
||||||
|
"api": "https://konachan.com/post.json",
|
||||||
|
"listAccess": [],
|
||||||
|
"mapFunc": (response) => {
|
||||||
|
return response.map(item => {
|
||||||
|
return {
|
||||||
|
"id": item.id,
|
||||||
|
"aspect_ratio": item.width / item.height,
|
||||||
|
"tags": item.tags,
|
||||||
|
"rating": item.rating,
|
||||||
|
"is_nsfw": (item.rating != 's'),
|
||||||
|
"md5": item.md5,
|
||||||
|
"preview_url": item.preview_url,
|
||||||
|
"sample_url": item.sample_url ?? item.file_url,
|
||||||
|
"file_url": item.file_url,
|
||||||
|
"file_ext": item.file_ext,
|
||||||
|
"source": getWorkingImageSource(item.source),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"danbooru": {
|
||||||
|
"name": "Danbooru",
|
||||||
|
"url": "https://danbooru.donmai.us",
|
||||||
|
"api": "https://danbooru.donmai.us/posts.json",
|
||||||
|
"listAccess": [],
|
||||||
|
"mapFunc": (response) => {
|
||||||
|
return response.map(item => {
|
||||||
|
return {
|
||||||
|
"id": item.id,
|
||||||
|
"aspect_ratio": item.image_width / item.image_height,
|
||||||
|
"tags": item.tag_string,
|
||||||
|
"rating": item.rating,
|
||||||
|
"is_nsfw": (item.rating != 's'),
|
||||||
|
"md5": item.md5,
|
||||||
|
"preview_url": item.preview_file_url,
|
||||||
|
"sample_url": item.file_url ?? item.large_file_url,
|
||||||
|
"file_url": item.large_file_url,
|
||||||
|
"file_ext": item.file_ext,
|
||||||
|
"source": getWorkingImageSource(item.source),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gelbooru": {
|
||||||
|
"name": "Gelbooru",
|
||||||
|
"url": "https://gelbooru.com",
|
||||||
|
"api": "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1",
|
||||||
|
"listAccess": ["post"],
|
||||||
|
"mapFunc": (response) => {
|
||||||
|
return response.map(item => {
|
||||||
|
return {
|
||||||
|
"id": item.id,
|
||||||
|
"aspect_ratio": item.width / item.height,
|
||||||
|
"tags": item.tags,
|
||||||
|
"rating": item.rating.replace('general', 's').charAt(0),
|
||||||
|
"is_nsfw": (item.rating != 's'),
|
||||||
|
"md5": item.md5,
|
||||||
|
"preview_url": item.preview_url,
|
||||||
|
"sample_url": item.sample_url ?? item.file_url,
|
||||||
|
"file_url": item.file_url,
|
||||||
|
"file_ext": item.file_url.split('.').pop(),
|
||||||
|
"source": getWorkingImageSource(item.source),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property var responses: []
|
||||||
|
onResponsesChanged: {
|
||||||
|
console.log("[Booru] Responses changed: " + JSON.stringify(responses))
|
||||||
|
}
|
||||||
|
property var currentProvider: "yandere"
|
||||||
|
|
||||||
|
function setProvider(provider) {
|
||||||
|
if (providerList.indexOf(provider) !== -1) {
|
||||||
|
currentProvider = provider
|
||||||
|
} else {
|
||||||
|
console.log("[Booru] Invalid provider: " + provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructRequestUrl(tags, nsfw=true, limit=20) {
|
||||||
|
var provider = providers[currentProvider]
|
||||||
|
var baseUrl = provider.api
|
||||||
|
var tagString = tags.join(" ")
|
||||||
|
if (!nsfw) {
|
||||||
|
tagString += " rating:safe"
|
||||||
|
}
|
||||||
|
var params = []
|
||||||
|
// Danbooru, Yandere, Konachan: tags & limit
|
||||||
|
if (currentProvider === "danbooru" || currentProvider === "yandere" || currentProvider === "konachan") {
|
||||||
|
params.push("tags=" + encodeURIComponent(tagString))
|
||||||
|
params.push("limit=" + limit)
|
||||||
|
}
|
||||||
|
var url = baseUrl
|
||||||
|
if (baseUrl.indexOf("?") === -1) {
|
||||||
|
url += "?" + params.join("&")
|
||||||
|
} else {
|
||||||
|
url += "&" + params.join("&")
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRequest(tags, nsfw=true, limit=20) {
|
||||||
|
var url = constructRequestUrl(tags, nsfw, limit)
|
||||||
|
console.log("[Booru] Making request to " + url)
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest()
|
||||||
|
xhr.open("GET", url)
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
|
||||||
|
try {
|
||||||
|
// console.log("[Booru] Raw response length: " + xhr.responseText.length)
|
||||||
|
var response = JSON.parse(xhr.responseText)
|
||||||
|
|
||||||
|
// Access nested properties based on listAccess
|
||||||
|
var accessList = providers[currentProvider].listAccess
|
||||||
|
for (var i = 0; i < accessList.length; ++i) {
|
||||||
|
if (response && response.hasOwnProperty(accessList[i])) {
|
||||||
|
response = response[accessList[i]]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response = providers[currentProvider].mapFunc(response)
|
||||||
|
// console.log("[Booru] Scoped & mapped response: " + JSON.stringify(response))
|
||||||
|
var newResponses = root.responses.slice() // make a shallow copy
|
||||||
|
newResponses.push(response)
|
||||||
|
root.responses = newResponses
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.log("[Booru] Failed to parse response: " + e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
|
console.log("[Booru] Request failed with status: " + xhr.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Required for danbooru
|
||||||
|
xhr.setRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36")
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Could not set User-Agent:", e)
|
||||||
|
}
|
||||||
|
xhr.send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user