Miniflux Extension

Miniflux RSS 阅读器扩展

  1. // ==UserScript==
  2. // @name Miniflux Extension
  3. // @name:zh-CN Miniflux Extension
  4. // @namespace https://github.com/utags/miniflux-extension
  5. // @homepage https://github.com/utags/miniflux-extension#readme
  6. // @supportURL https://github.com/utags/miniflux-extension/issues
  7. // @version 0.0.2
  8. // @description An extension for Miniflux RSS reader
  9. // @description:zh-CN Miniflux RSS 阅读器扩展
  10. // @icon https://miniflux.app/favicon.ico
  11. // @author Pipecraft
  12. // @license MIT
  13. // @match https://reader.miniflux.app/*
  14. // @match https://miniflux.pipecraft.net/*
  15. // @grant GM_addElement
  16. // @grant GM_addStyle
  17. // ==/UserScript==
  18. //
  19. //// Recent Updates
  20. //// - 0.0.2 2023.04.26
  21. //// - Add reader.miniflux.app as match site
  22. //// - Automatically update categories every 10 minutes
  23. ////
  24. ;(() => {
  25. "use strict"
  26. var doc = document
  27. var $ = (element, selectors) =>
  28. element && typeof element === "object"
  29. ? element.querySelector(selectors)
  30. : doc.querySelector(element)
  31. var createElement = (tagName, attributes) =>
  32. setAttributes(doc.createElement(tagName), attributes)
  33. var addEventListener = (element, type, listener, options) => {
  34. if (!element) {
  35. return
  36. }
  37. if (typeof type === "object") {
  38. for (const type1 in type) {
  39. if (Object.hasOwn(type, type1)) {
  40. element.addEventListener(type1, type[type1])
  41. }
  42. }
  43. } else if (typeof type === "string" && typeof listener === "function") {
  44. element.addEventListener(type, listener, options)
  45. }
  46. }
  47. var setAttribute = (element, name, value) =>
  48. element ? element.setAttribute(name, value) : void 0
  49. var setAttributes = (element, attributes) => {
  50. if (element && attributes) {
  51. for (const name in attributes) {
  52. if (Object.hasOwn(attributes, name)) {
  53. const value = attributes[name]
  54. if (value === void 0) {
  55. continue
  56. }
  57. if (/^(value|textContent|innerText|innerHTML)$/.test(name)) {
  58. element[name] = value
  59. } else if (name === "style") {
  60. setStyle(element, value, true)
  61. } else if (/on\w+/.test(name)) {
  62. const type = name.slice(2)
  63. addEventListener(element, type, value)
  64. } else {
  65. setAttribute(element, name, value)
  66. }
  67. }
  68. }
  69. }
  70. return element
  71. }
  72. var setStyle = (element, values, overwrite) => {
  73. if (!element) {
  74. return
  75. }
  76. const style = element.style
  77. if (typeof values === "string") {
  78. style.cssText = overwrite ? values : style.cssText + ";" + values
  79. return
  80. }
  81. if (overwrite) {
  82. style.cssText = ""
  83. }
  84. for (const key in values) {
  85. if (Object.hasOwn(values, key)) {
  86. style[key] = values[key].replace("!important", "")
  87. }
  88. }
  89. }
  90. if (typeof Object.hasOwn !== "function") {
  91. Object.hasOwn = (instance, prop) =>
  92. Object.prototype.hasOwnProperty.call(instance, prop)
  93. }
  94. var addElement = (parentNode, tagName, attributes) => {
  95. if (typeof parentNode === "string" || typeof tagName === "string") {
  96. const element = GM_addElement(parentNode, tagName, attributes)
  97. setAttributes(element, attributes)
  98. return element
  99. }
  100. setAttributes(tagName, attributes)
  101. parentNode.append(tagName)
  102. return tagName
  103. }
  104. var addStyle = (styleText) => GM_addStyle(styleText)
  105. var content_default =
  106. "[data-miniflux_ext] #left_section{position:fixed;top:0;left:0;padding:20px;overflow:auto;height:100%}"
  107. var config = {
  108. matches: [
  109. "https://reader.miniflux.app/*",
  110. "https://miniflux.pipecraft.net/*",
  111. ],
  112. }
  113. var m1 = 1e3 * 60
  114. var storageKey = "extension.miniflux.cache"
  115. function getCategoriesFromCache() {
  116. const categoriesText = localStorage.getItem(storageKey)
  117. return categoriesText
  118. ? createElement("div", {
  119. class: "items",
  120. innerHTML: categoriesText,
  121. })
  122. : null
  123. }
  124. async function updateCategories() {
  125. const response = await fetch("/categories")
  126. if (response.status === 200) {
  127. const text = await response.text()
  128. const div = createElement("div")
  129. div.innerHTML = text
  130. const categories = $(div, ".items")
  131. if (categories == null ? void 0 : categories.innerHTML) {
  132. const categoriesText = localStorage.getItem(storageKey)
  133. if (categoriesText !== categories.innerHTML) {
  134. localStorage.setItem(storageKey, categories.innerHTML)
  135. return true
  136. }
  137. }
  138. }
  139. return false
  140. }
  141. var appendCategories = () => {
  142. const container = $("[data-miniflux_ext] #left_section")
  143. if (!container) {
  144. return
  145. }
  146. let categories = $("[data-miniflux_ext] #left_section .items")
  147. if (categories) {
  148. categories.remove()
  149. }
  150. categories = getCategoriesFromCache()
  151. if (categories) {
  152. container.append(categories)
  153. }
  154. }
  155. async function autoUpdateCategories() {
  156. if (await updateCategories()) {
  157. appendCategories()
  158. setTimeout(autoUpdateCategories, 3 * m1)
  159. } else {
  160. setTimeout(autoUpdateCategories, 10 * m1)
  161. }
  162. }
  163. async function main() {
  164. if (!document.body || $("[data-miniflux_ext] #left_section")) {
  165. return
  166. }
  167. addStyle(content_default)
  168. document.body.dataset.miniflux_ext = "1"
  169. addElement(document.body, "section", {
  170. id: "left_section",
  171. innerHTML: `<section class="page-header"><h1>Categories</h1></section>`,
  172. })
  173. appendCategories()
  174. setTimeout(autoUpdateCategories, 1e3)
  175. }
  176. main()
  177. addEventListener(document, "DOMContentLoaded", main)
  178. })()