Merge pull request #1104 from c0mpile/pr/wallhaven-api-key

feat: Add Wallhaven API key support for NSFW content and update purity settings
This commit is contained in:
Lemmy
2025-12-25 18:02:48 -05:00
committed by GitHub
5 changed files with 282 additions and 25 deletions
+7 -1
View File
@@ -2799,7 +2799,13 @@
"all": "All",
"label": "Content filter",
"sfw": "SFW",
"sketchy": "Sketchy"
"sketchy": "Sketchy",
"nsfw": "NSFW"
},
"apikey": {
"label": "API Key",
"placeholder": "Enter your Wallhaven API Key",
"help": "An API key is required to access NSFW content."
},
"ratios": {
"any": "Any",
+1
View File
@@ -168,6 +168,7 @@
"wallhavenCategories": "111",
"wallhavenPurity": "100",
"wallhavenRatios": "",
"wallhavenApiKey": "",
"wallhavenResolutionMode": "atleast",
"wallhavenResolutionWidth": "",
"wallhavenResolutionHeight": ""
+1
View File
@@ -376,6 +376,7 @@ Singleton {
property string wallhavenCategories: "111" // general,anime,people
property string wallhavenPurity: "100" // sfw only
property string wallhavenRatios: ""
property string wallhavenApiKey: ""
property string wallhavenResolutionMode: "atleast" // "atleast" or "exact"
property string wallhavenResolutionWidth: ""
property string wallhavenResolutionHeight: ""
@@ -138,6 +138,48 @@ Popup {
Layout.fillWidth: true
}
// API Key
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginS
NText {
text: I18n.tr("wallpaper.panel.apikey.label")
color: Color.mOnSurface
pointSize: Style.fontSizeM
}
NTextInput {
id: apiKeyInput
Layout.fillWidth: true
placeholderText: I18n.tr("wallpaper.panel.apikey.placeholder")
text: Settings.data.wallpaper.wallhavenApiKey || ""
// Fix for password echo mode
Component.onCompleted: {
if (apiKeyInput.inputItem) {
apiKeyInput.inputItem.echoMode = TextInput.Password;
}
}
onEditingFinished: {
Settings.data.wallpaper.wallhavenApiKey = text;
}
}
NText {
text: I18n.tr("wallpaper.panel.apikey.help")
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeS
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
NDivider {
Layout.fillWidth: true
}
// Sorting
RowLayout {
Layout.fillWidth: true
@@ -241,32 +283,228 @@ Popup {
Layout.preferredWidth: implicitWidth
}
NComboBox {
id: purityComboBox
Item {
Layout.fillWidth: true
Layout.minimumWidth: 200
model: [
{
"key": "111",
"name": I18n.tr("wallpaper.panel.purity.all")
},
{
"key": "100",
"name": I18n.tr("wallpaper.panel.purity.sfw")
},
{
"key": "010",
"name": I18n.tr("wallpaper.panel.purity.sketchy")
}
RowLayout {
id: purityRow
spacing: Style.marginL
function getPurityValue(index) {
var purity = Settings.data.wallpaper.wallhavenPurity || "100";
return purity.length > index && purity.charAt(index) === "1";
}
function updatePurity(sfw, sketchy, nsfw) {
var purity = (sfw ? "1" : "0") + (sketchy ? "1" : "0") + (nsfw ? "1" : "0");
Settings.data.wallpaper.wallhavenPurity = purity;
// Update checkboxes immediately
sfwToggle.checked = sfw;
sketchyToggle.checked = sketchy;
nsfwToggle.checked = nsfw;
if (typeof WallhavenService !== "undefined") {
WallhavenService.purity = purity;
WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1);
}
]
currentKey: Settings.data.wallpaper.wallhavenPurity
onSelected: key => {
Settings.data.wallpaper.wallhavenPurity = key;
if (typeof WallhavenService !== "undefined") {
WallhavenService.purity = key;
WallhavenService.search(Settings.data.wallpaper.wallhavenQuery || "", 1);
}
}
}
Connections {
target: Settings.data.wallpaper
function onWallhavenPurityChanged() {
sfwToggle.checked = purityRow.getPurityValue(0);
sketchyToggle.checked = purityRow.getPurityValue(1);
nsfwToggle.checked = purityRow.getPurityValue(2);
}
function onWallhavenApiKeyChanged() {
// If API key is removed, disable NSFW
if (!Settings.data.wallpaper.wallhavenApiKey && nsfwToggle.checked) {
nsfwToggle.toggled(false);
}
}
}
Component.onCompleted: {
sfwToggle.checked = purityRow.getPurityValue(0);
sketchyToggle.checked = purityRow.getPurityValue(1);
nsfwToggle.checked = purityRow.getPurityValue(2);
}
// SFW checkbox
Item {
Layout.preferredWidth: sfwCheckboxRow.implicitWidth
Layout.preferredHeight: sfwCheckboxRow.implicitHeight
RowLayout {
id: sfwCheckboxRow
anchors.fill: parent
spacing: Style.marginS
NText {
text: I18n.tr("wallpaper.panel.purity.sfw")
color: Color.mOnSurface
pointSize: Style.fontSizeM
}
Rectangle {
id: sfwBox
implicitWidth: Math.round(Style.baseWidgetSize * 0.7)
implicitHeight: Math.round(Style.baseWidgetSize * 0.7)
radius: Style.radiusXS
color: sfwToggle.checked ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: sfwToggle.checked
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: Color.mOnPrimary
pointSize: Math.max(Style.fontSizeXS, sfwBox.width * 0.5)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: sfwToggle.toggled(!sfwToggle.checked)
}
}
}
}
// Sketchy checkbox
Item {
Layout.preferredWidth: sketchyCheckboxRow.implicitWidth
Layout.preferredHeight: sketchyCheckboxRow.implicitHeight
RowLayout {
id: sketchyCheckboxRow
anchors.fill: parent
spacing: Style.marginS
NText {
text: I18n.tr("wallpaper.panel.purity.sketchy")
color: Color.mOnSurface
pointSize: Style.fontSizeM
}
Rectangle {
id: sketchyBox
implicitWidth: Math.round(Style.baseWidgetSize * 0.7)
implicitHeight: Math.round(Style.baseWidgetSize * 0.7)
radius: Style.radiusXS
color: sketchyToggle.checked ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: sketchyToggle.checked
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: Color.mOnPrimary
pointSize: Math.max(Style.fontSizeXS, sketchyBox.width * 0.5)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: sketchyToggle.toggled(!sketchyToggle.checked)
}
}
}
}
// NSFW checkbox
Item {
Layout.preferredWidth: nsfwCheckboxRow.implicitWidth
Layout.preferredHeight: nsfwCheckboxRow.implicitHeight
visible: Settings.data.wallpaper.wallhavenApiKey !== ""
RowLayout {
id: nsfwCheckboxRow
anchors.fill: parent
spacing: Style.marginS
NText {
text: I18n.tr("wallpaper.panel.purity.nsfw")
color: Color.mOnSurface
pointSize: Style.fontSizeM
}
Rectangle {
id: nsfwBox
implicitWidth: Math.round(Style.baseWidgetSize * 0.7)
implicitHeight: Math.round(Style.baseWidgetSize * 0.7)
radius: Style.radiusXS
color: nsfwToggle.checked ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: nsfwToggle.checked
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: Color.mOnPrimary
pointSize: Math.max(Style.fontSizeXS, nsfwBox.width * 0.5)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: nsfwToggle.toggled(!nsfwToggle.checked)
}
}
}
}
// Invisible objects for logic
QtObject {
id: sfwToggle
property bool checked: false
signal toggled(bool checked)
onToggled: checked => {
purityRow.updatePurity(checked, purityRow.getPurityValue(1), purityRow.getPurityValue(2));
}
}
QtObject {
id: sketchyToggle
property bool checked: false
signal toggled(bool checked)
onToggled: checked => {
purityRow.updatePurity(purityRow.getPurityValue(0), checked, purityRow.getPurityValue(2));
}
}
QtObject {
id: nsfwToggle
property bool checked: false
signal toggled(bool checked)
onToggled: checked => {
purityRow.updatePurity(purityRow.getPurityValue(0), purityRow.getPurityValue(1), checked);
}
}
}
}
@@ -739,6 +977,7 @@ Popup {
WallhavenService.sorting = Settings.data.wallpaper.wallhavenSorting;
WallhavenService.order = Settings.data.wallpaper.wallhavenOrder;
WallhavenService.ratios = Settings.data.wallpaper.wallhavenRatios;
WallhavenService.apiKey = Settings.data.wallpaper.wallhavenApiKey;
// Update resolution settings (without triggering search)
updateResolution(false);
+10
View File
@@ -28,6 +28,7 @@ Singleton {
property string resolutions: "" // e.g., "1920x1080,1920x1200"
property string ratios: "" // e.g., "16x9,16x10"
property string colors: "" // Color hex codes
property string apiKey: "" // User API key for NSFW access
// Signals
signal searchCompleted(var results, var meta)
@@ -88,6 +89,10 @@ Singleton {
if (colors) {
params.push("colors=" + colors);
}
if (apiKey) {
params.push("apikey=" + apiKey);
}
params.push("page=" + currentPage);
@@ -131,6 +136,11 @@ Singleton {
lastError = errorMsg;
Logger.w("Wallhaven", errorMsg);
searchFailed(errorMsg);
} else if (xhr.status === 401) {
var errorMsg = "Invalid API Key. Please check your settings.";
lastError = errorMsg;
Logger.e("Wallhaven", errorMsg);
searchFailed(errorMsg);
} else {
var errorMsg = "API error: " + xhr.status;
lastError = errorMsg;