Last active
May 28, 2026 21:41
-
-
Save charleshan/f981b64d42bff8c6d0ad96738a6c945b to your computer and use it in GitHub Desktop.
Fix for KOReader Wallabag deleting archive even when not told to + downloading Wallabag Archive
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Wallabag: respect "Delete remotely archived and deleted articles locally" | |
| (sync_remote_archive) when auto-uploading statuses. | |
| Bug: uploadStatuses() always deleted/archived local files after uploading a | |
| status to the server, even when sync_remote_archive was disabled — ignoring | |
| the setting's label. This patch makes the local deletion conditional on | |
| sync_remote_archive being on. | |
| How the patching works | |
| ---------------------- | |
| The Wallabag plugin is loaded via dofile(), not require(). We use KOReader's | |
| supported hook userpatch.registerPatchPluginFunc(plugin_name, fn), which is | |
| called with the plugin's class table after an instance is created, so we can | |
| wrap the method in place without touching any global state. | |
| Targeted at the wallabag.koplugin shipped in KOReader 2026.03.x, whose API has | |
| uploadStatuses(), archiveLocalArticle()/deleteLocalArticle() and the | |
| sync_remote_archive setting. | |
| Install: copy to koreader/patches/1-wallabag-dont-delete-local-on-upload.lua | |
| ]] | |
| local userpatch = require("userpatch") | |
| userpatch.registerPatchPluginFunc("wallabag", function(Wallabag) | |
| -- registerPatchPluginFunc runs this on every createPluginInstance() call, | |
| -- so guard against wrapping the (shared) class method more than once. | |
| if Wallabag._sync_remote_archive_patched then return end | |
| Wallabag._sync_remote_archive_patched = true | |
| -- We patch uploadStatuses() directly rather than the two helper functions | |
| -- (archiveLocalArticle / deleteLocalArticle) because those helpers are also | |
| -- called by processRemoteDeletes(), which is already correctly gated by | |
| -- sync_remote_archive at its call site in downloadArticles(). | |
| local orig_uploadStatuses = Wallabag.uploadStatuses | |
| Wallabag.uploadStatuses = function(self, ...) | |
| if self.sync_remote_archive then | |
| -- sync_remote_archive is on: original behaviour, delete locally | |
| return orig_uploadStatuses(self, ...) | |
| end | |
| -- sync_remote_archive is off: upload status to server but keep local | |
| -- file. Temporarily replace the two deletion helpers with no-ops for | |
| -- the duration of this call only. | |
| local orig_archiveLocal = self.archiveLocalArticle | |
| local orig_deleteLocal = self.deleteLocalArticle | |
| self.archiveLocalArticle = function() return 0 end | |
| self.deleteLocalArticle = function() return 0 end | |
| local count_remote, count_local = orig_uploadStatuses(self, ...) | |
| self.archiveLocalArticle = orig_archiveLocal | |
| self.deleteLocalArticle = orig_deleteLocal | |
| return count_remote, count_local | |
| end | |
| end) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Wallabag: "Also download archived articles" toggle. | |
| Adds Settings > Download settings > Also download archived articles. | |
| When on, fetches both unread AND archived articles. Default off. | |
| How the patching works | |
| ---------------------- | |
| The Wallabag plugin is loaded via dofile(), not require(), so the standard | |
| userpatch require("main") trick can't reach the live class. KOReader provides | |
| the supported hook userpatch.registerPatchPluginFunc(plugin_name, fn): fn is | |
| called with the plugin's class table right after a plugin instance is created, | |
| so we can patch methods in place without touching any global state. | |
| Targeted at the wallabag.koplugin shipped in KOReader v2026.03-43 | |
| (g6101f9acc). That version persists settings via saveSettings() (NOT | |
| onFlushSettings / self.updated): each Settings toggle flips a field then calls | |
| self:saveSettings(), which rebuilds its save table inline. We mirror that. | |
| Install: copy to koreader/patches/1-wallabag-download-archived.lua | |
| ]] | |
| local userpatch = require("userpatch") | |
| userpatch.registerPatchPluginFunc("wallabag", function(Wallabag) | |
| -- registerPatchPluginFunc runs this on every createPluginInstance() call, | |
| -- so guard against patching the (shared) class table more than once — | |
| -- otherwise method wrappers nest and the menu item is inserted repeatedly. | |
| if Wallabag._download_archived_patched then return end | |
| Wallabag._download_archived_patched = true | |
| -- Read the persisted setting from saved settings, defaulting to false. | |
| -- Done lazily (rather than by wrapping init) because the very first | |
| -- instance each launch is created *before* this patch func runs, so its | |
| -- init has already finished; lazy-loading covers that instance too. | |
| local function downloadArchived(self) | |
| if self.download_archived == nil then | |
| local saved = self.wb_settings | |
| and self.wb_settings.data | |
| and self.wb_settings.data.wallabag | |
| and self.wb_settings.data.wallabag.download_archived | |
| self.download_archived = saved or false | |
| end | |
| return self.download_archived | |
| end | |
| -- 1. Persist the setting ------------------------------------------------- | |
| -- This version has no onFlushSettings; toggles persist by calling | |
| -- self:saveSettings(), which rebuilds its save table inline from self.* | |
| -- and so omits our key. Let the original save+flush run, then inject our | |
| -- key into the just-written in-memory data and flush once more. | |
| local orig_save = Wallabag.saveSettings | |
| Wallabag.saveSettings = function(self, ...) | |
| orig_save(self, ...) | |
| if self.wb_settings and self.wb_settings.data | |
| and self.wb_settings.data.wallabag then | |
| self.wb_settings.data.wallabag.download_archived = downloadArchived(self) | |
| self.wb_settings:flush() | |
| end | |
| end | |
| -- 2. Remove archive=0 filter when the setting is on --------------------- | |
| -- The article list URL is built as "/api/entries.json?archive=0&page=…" | |
| -- We intercept callAPI and strip the filter for that specific endpoint. | |
| local orig_callAPI = Wallabag.callAPI | |
| Wallabag.callAPI = function(self, method, url, ...) | |
| if downloadArchived(self) | |
| and method == "GET" | |
| and type(url) == "string" | |
| and url:match("/api/entries%.json%?archive=0&") then | |
| -- strip "?archive=0&" → "?" leaving the remaining params intact | |
| url = url:gsub("/api/entries%.json%?archive=0&", | |
| "/api/entries.json?") | |
| end | |
| return orig_callAPI(self, method, url, ...) | |
| end | |
| -- 3. Inject menu toggle into Download settings -------------------------- | |
| local orig_menu = Wallabag.addToMainMenu | |
| Wallabag.addToMainMenu = function(self, menu_items, ...) | |
| orig_menu(self, menu_items, ...) | |
| local _ = require("gettext") | |
| local wallabag_menu = menu_items.wallabag | |
| if not (wallabag_menu and wallabag_menu.sub_item_table) then return end | |
| -- NB: use __ / ___ (not _) for the loop indices — _ is gettext above, | |
| -- and a "for _, item" loop variable would shadow it, turning | |
| -- _("Settings") into a call on a number (crash) when the menu is built. | |
| for __, item in ipairs(wallabag_menu.sub_item_table) do | |
| if item.text == _("Settings") and item.sub_item_table then | |
| for ___, sub in ipairs(item.sub_item_table) do | |
| if sub.text == _("Download settings") and sub.sub_item_table then | |
| table.insert(sub.sub_item_table, { | |
| text = _("Also download archived articles"), | |
| help_text = _( | |
| "Download both unread and archived articles from the server.\n" | |
| .. "When disabled (default), only unread articles are downloaded." | |
| ), | |
| keep_menu_open = true, | |
| checked_func = function() | |
| return downloadArchived(self) | |
| end, | |
| callback = function() | |
| self.download_archived = not downloadArchived(self) | |
| self:saveSettings() | |
| end, | |
| }) | |
| return -- done, stop searching | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment