Compare commits

..

506 Commits

Author SHA1 Message Date
Frédéric Guillot 4a4c309da0 Update ChangeLog 2023-07-21 18:07:13 -07:00
Frédéric Guillot 5e76c797c2 Update issue templates 2023-07-14 20:25:23 -07:00
David Izquierdo 4fdef7b837 Add scrape and rewrite rules for webtoons
Although the only source I have for the rewrite rule is, in fact, https://github.com/miniflux/v2/pull/892, it does work when combined with add_dynamic_image and scraping the right element. I have not investigated further.

Works around https://github.com/miniflux/v2/issues/775 and https://github.com/miniflux/v2/issues/1871 (as in, gives us working webtoons feeds but referer spoofing would still be a nice tool to have).

Fixes https://github.com/miniflux/v2/issues/256.
2023-07-10 21:25:48 -07:00
Frédéric Guillot 7988241e11 Fix regression in integration page and simplify SQL query 2023-07-10 21:07:05 -07:00
Frédéric Guillot 309e6d1084 Wallabag integration: add more information in error message 2023-07-10 19:51:04 -07:00
dependabot[bot] 92bb040640 Bump github.com/mccutchen/go-httpbin/v2 from 2.9.2 to 2.10.0
Bumps [github.com/mccutchen/go-httpbin/v2](https://github.com/mccutchen/go-httpbin) from 2.9.2 to 2.10.0.
- [Release notes](https://github.com/mccutchen/go-httpbin/releases)
- [Commits](https://github.com/mccutchen/go-httpbin/compare/v2.9.2...v2.10.0)

---
updated-dependencies:
- dependency-name: github.com/mccutchen/go-httpbin/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 19:22:31 -07:00
Igor Rzegocki 9b42d0e25e feat: support for custom youtube embed URL 2023-07-07 15:59:23 -07:00
Frédéric Guillot f286c3c1c9 Keep styling backward compatible 2023-07-07 15:44:44 -07:00
Tuukka Ojala e16870a638 Fix incorrect return value comparisons 2023-07-07 15:44:44 -07:00
Tuukka Ojala f0eb6b2688 Fix code formatting 2023-07-07 15:44:44 -07:00
Tuukka Ojala 29a06511a9 Fix accessibility issues in modal component
* Fix modal aria role
* Trap focusing with tab / shift+tab inside the modal
* Restore keyboard focus when closing modal
* Automatically move keyboard focus to first focusable element unless specified otherwise
* Keyboard shortcut help modal: move keyboard focus to modal title
* Keyboard shortcut help modal: change close control from link to button
2023-07-07 15:44:44 -07:00
Jean Khawand bfb4fc1c36 Add Notion integration 2023-07-07 15:20:14 -07:00
dependabot[bot] 06c37a132f Bump golang.org/x/oauth2 from 0.9.0 to 0.10.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-05 19:52:15 -07:00
mrtnvgr 70b3061946 fix(css): improve responsive design 2023-06-30 17:37:51 -07:00
xl 356d32c6fe Add user setting for marking entry as read on view 2023-06-29 21:03:05 -07:00
mrtnvgr 6046a74a64 feat: improve russian translation 2023-06-29 20:54:29 -07:00
dependabot[bot] 56fbea4bd3 Bump github.com/mccutchen/go-httpbin/v2 from 2.9.1 to 2.9.2
Bumps [github.com/mccutchen/go-httpbin/v2](https://github.com/mccutchen/go-httpbin) from 2.9.1 to 2.9.2.
- [Release notes](https://github.com/mccutchen/go-httpbin/releases)
- [Commits](https://github.com/mccutchen/go-httpbin/compare/v2.9.1...v2.9.2)

---
updated-dependencies:
- dependency-name: github.com/mccutchen/go-httpbin/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-27 20:49:37 -07:00
Wayne Tan ee0dcdf753 Bump go-oidc to v3.6.0 2023-06-27 20:21:51 -07:00
Frédéric Guillot 5550d662a2 Add the possibility to run cleanup tasks from the command line 2023-06-25 11:41:30 -07:00
Frédéric Guillot 3dc8e5ebaf Refresh feeds in the cronjob in parallel 2023-06-25 10:35:59 -07:00
Frédéric Guillot c85b19098d Add the possibility to run Miniflux as a cronjob 2023-06-24 22:29:30 -07:00
kramanathan01 c3250257b1 Use go-httpbin to run tests locally and avoid remote calls to httpbin.org
https://github.com/mccutchen/go-httpbin is MIT licensed and implements httpbin interfaces in Golang. 

Combining this package with `httptest.Server` allows client unit tests to run entirely locally with no dependency on remote calls to httpbin.org
2023-06-24 22:05:47 -07:00
privatmamtora fffa74f782 Display tags when viewing entries 2023-06-24 17:44:37 -07:00
kramanathan01 fa3de272e8 GET categories returns total_unread & feed_count 2023-06-24 17:04:27 -07:00
Frédéric Guillot b13c7e328a Improve date parser to handle various broken date formats 2023-06-24 15:27:33 -07:00
Frédéric Guillot 30d4b8986a Avoid "pq: time zone displacement out of range" errors 2023-06-24 15:09:58 -07:00
Frédéric Guillot aadbd5adf3 Improve entry existance check to make better use of index 2023-06-24 13:21:07 -07:00
Frédéric Guillot b552c293ca Add unique index enclosures_user_entry_url_idx 2023-06-24 11:59:58 -07:00
Frédéric Guillot a2f15b3c36 Add translation key: form.integration.linkding_bookmark 2023-06-24 10:38:07 -07:00
movd 736fb7320e Add mark as unread for Linkding integration 2023-06-24 10:38:07 -07:00
Gabriel Augendre 765b4c6424 Add sub-folder support for Wallabag integration 2023-06-24 10:14:46 -07:00
Frédéric Guillot df472254d3 Use RockyLinux to build RPM package 2023-06-23 20:56:53 -07:00
Frédéric Guillot 98167487aa Remove test dependency on httpbin.org
httpbin.org is very flaky
2023-06-23 17:52:55 -07:00
Frédéric Guillot 257ef7e573 Disable CGO when building RPM package 2023-06-22 21:22:44 -07:00
Frédéric Guillot 30288fec8d Disable CGO when building Docker images 2023-06-22 21:19:44 -07:00
Frédéric Guillot e234b86af6 Update ChangeLog 2023-06-21 20:42:31 -07:00
dependabot[bot] d00803617e Bump github.com/tdewolff/minify/v2 from 2.12.6 to 2.12.7
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.6 to 2.12.7.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.6...v2.12.7)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 15:57:40 -07:00
fred af74e39fa7 Add test case to parse Atom icon URL 2023-06-19 15:17:41 -07:00
fred 8646d61182 Replace copyright header with SPDX identifier 2023-06-19 15:00:45 -07:00
fred 28775f5e10 Refactor entry/feed query builder sorting to match SQL semantic 2023-06-19 14:13:19 -07:00
dependabot[bot] 095bec072c Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.1 to 1.16.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.1...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-15 21:38:10 -07:00
dependabot[bot] bd0f21e139 Bump golang.org/x/oauth2 from 0.8.0 to 0.9.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 19:43:21 -07:00
dependabot[bot] 69414dcd7c Bump golang.org/x/term from 0.8.0 to 0.9.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/term/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-12 17:46:32 -07:00
Kristof Mattei 28ee9cad19 fix: remove title for a hrefs 2023-06-05 20:09:43 -07:00
Naïm Favier 7d1609bd93 Order history by changed_at, published_at
When a bunch of entries are marked as read at once, they should have the
same `changed_at`; fall back to sorting them chronologically by
publication date.
2023-06-04 15:03:57 -07:00
Ryan Stafford 1aeb1b20da Use image included in feed as feed icon 2023-06-04 15:01:59 -07:00
Ztec 228bb62df4 Add Media Player and resume to last playback position
In order to ease podcast listening, the player can be put on top of the feed entry as main content.
Use the `Use podcast player` option to enable that. It works on audio and video.

Also, when playing audio or video, progression will be saved in order to be able to resume listening later.
This position saving is done using the original attachement/enclosures player AND podcast player and do not rely on
the podcast player option ti be enabled.

Additionally, I made the player fill the width with the entry container to ease seeking and have a bigger video.

updateEnclosures now keep existing enclosures based on URL

When feeds get updated, enclosures entries are always wiped and re-created. This cause two issue
 - enclosure progression get lost in the process
 - enclosure ID changes

I used the URL as identifier of an enclosure. Not perfect but hopefully should work.
When an enclosure already exist, I simply do nothing and leave the entry as is in the database.
If anyone is listening/watching to this enclosure during the refresh, the id stay coherent and progression saving still works.

The updateEnclosures function got a bit more complex. I tried to make it the more clear I could.
Some optimisation are possible but would make the function harder to read in my opinion.

I'm not sure if this is often the case, but some feeds may include tracking or simply change the url each
time we update the feed. In those situation, enclosures ids and progression will be lost.

I have no idea how to handle this last situation. Use the size instead/alongside url to define the identity of an enclosure ?

Translation: english as placeholder for every language except French

Aside, I tested a video feed and fixed a few things for it. In fact, the MimeType was not working
at all on my side, and found a pretty old stackoverflow discussion that suggest to use an Apple non-standard MimeType for
m4v video format. I only did one substitution because I only have one feed to test. Any new video feed can make this go away
or evolve depending on the situation. Real video feeds does not tend to be easy to find and test extensively this.

Co-authored-by: toastal
2023-06-04 14:49:46 -07:00
Ryan Cao c4e2eaa609 Add default tag names for Linkding integration 2023-05-30 21:02:27 -07:00
Romain de Laage 118e18190d Mark only globally visible entries when marking all entries from UI 2023-05-30 20:29:44 -07:00
Jonatas Baldin 31c4172540 Remove the "í" letter from the portuguese "lido" word 2023-05-28 19:13:36 -07:00
dependabot[bot] 93b43d37df Bump github.com/tdewolff/minify/v2 from 2.12.5 to 2.12.6
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.5 to 2.12.6.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.5...v2.12.6)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-26 16:13:54 -07:00
Frédéric Guillot 3987a2ce8a Reading time is not aligned correctly with the latest Safari
Fixes #1873
2023-05-10 20:38:18 -07:00
Frédéric Guillot fe039b3c55 Use glyphs of the same size on keyboard shortcuts page 2023-05-10 20:09:13 -07:00
dependabot[bot] 7537932154 Bump golang.org/x/oauth2 from 0.7.0 to 0.8.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/oauth2/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 19:21:59 -07:00
dependabot[bot] 902f6cb9c0 Bump golang.org/x/crypto from 0.8.0 to 0.9.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/crypto/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 19:15:13 -07:00
Frédéric Guillot 790ce5be6d Increase golangci-lint timeout value 2023-05-09 19:06:36 -07:00
dependabot[bot] bcfc7a883c Bump golang.org/x/net from 0.9.0 to 0.10.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.10.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.10.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 17:14:56 -07:00
Pontus Jensen Karlsson 9fdbd180df Added maskable versions of the PWA icon.
Recent versions of Android allows the user to choose their own
homescreen icons shape. This introduces the concept of maskable PWA
icons, which without the "purpose" tag and properly padded icons makes
the homescreen icon look really boxy and weird.

This adds a new version of the icon with more padding in three sizes, as
well as the "purpose" attribute in the manifest.json file. The three old
icons are retained for compatibility with desktop and iOS.
2023-05-08 16:35:37 -07:00
Frédéric Guillot 4c0c658152 Update ChangeLog 2023-05-06 14:09:45 -07:00
dependabot[bot] 88062ab9f9 Bump golang.org/x/term from 0.7.0 to 0.8.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/term/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-04 17:04:42 -07:00
Adriano Di Luzio 85856baf13 fix: Point to docs for URL rewrite rules too 2023-05-04 17:04:21 -07:00
dependabot[bot] 2d33b7df6e Bump github.com/prometheus/client_golang from 1.15.0 to 1.15.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.0 to 1.15.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.0...v1.15.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-03 17:58:32 -07:00
Davide Masserut 5d8a8878d5 Update scraping rules for ilpost.it 2023-05-02 17:07:25 -07:00
dependabot[bot] 8d2dab44d8 Bump github.com/lib/pq from 1.10.8 to 1.10.9
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.8 to 1.10.9.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.8...v1.10.9)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-02 16:19:33 -07:00
dependabot[bot] d435e67a36 Bump mvdan.cc/xurls/v2 from 2.4.0 to 2.5.0
Bumps [mvdan.cc/xurls/v2](https://github.com/mvdan/xurls) from 2.4.0 to 2.5.0.
- [Release notes](https://github.com/mvdan/xurls/releases)
- [Commits](https://github.com/mvdan/xurls/compare/v2.4.0...v2.5.0)

---
updated-dependencies:
- dependency-name: mvdan.cc/xurls/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-17 16:58:42 -07:00
Romain de Laage 33c4b5188c Add a rewrite rule to remove clickbait titles 2023-04-15 18:25:43 -07:00
dependabot[bot] 8161085714 Bump github.com/lib/pq from 1.10.7 to 1.10.8
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.7 to 1.10.8.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.7...v1.10.8)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-14 19:13:51 -07:00
dependabot[bot] 6493239484 Bump github.com/prometheus/client_golang from 1.14.0 to 1.15.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-13 20:48:57 -07:00
dependabot[bot] a143681af3 Bump golang.org/x/crypto from 0.7.0 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 16:12:41 -07:00
Emiel Wiedijk 5a88e0465e Update rewrite rules for theverge.com
Articles on The Verge sometimes contain a section for related articles.
This section can be distracting in reader mode. Therefore, filter the
related article section using the scraper rules.
2023-04-07 16:12:19 -07:00
dependabot[bot] 30bb901d7c Bump golang.org/x/oauth2 from 0.6.0 to 0.7.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/oauth2/releases)
- [Commits](https://github.com/golang/oauth2/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 16:02:42 -07:00
dependabot[bot] 40418fcf6f Bump golang.org/x/net from 0.8.0 to 0.9.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-06 17:42:43 -07:00
dependabot[bot] ad85e5be80 Bump golang.org/x/term from 0.6.0 to 0.7.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-05 20:06:59 -07:00
Frédéric Guillot aa9b18a8d6 Make sure PROXY_IMAGES option is backward compatible
Bug introduced in PR #1610

Fixes #1753
2023-04-02 18:35:43 -07:00
Jake Walker 8b6dd3e599 Keep other table rows and columns 2023-04-02 17:50:19 -07:00
Jake Walker 49d2596fc6 Basic table removal rule 2023-04-02 17:50:19 -07:00
rook1e 9a826bbe6f feat: support searching well-known urls in subdirectory 2023-04-02 17:44:14 -07:00
rook1e acc9186a59 fix: extra-long title overflow 2023-04-02 17:37:25 -07:00
dzaikos 7d252ea45b Add swipe as option for gesture navigation between entries.
* Refactor `TouchHandler` to handle double-tap and swipe gestures.
  * Renamed existing `onTouch` JavaScript methods to `onItemTouch` and
    added `onContentTouch` methods for swipe gesture.
  * Refactor double-tap. It's now a method in `TouchHandler` versus
    anonymous functions in `listen()` method.
* Updated CSS classes.
  * Added `touch-action` CSS for `.entry-content`.
  * Renamed CSS classes for adding events in `TouchHandler`.
* Updated users settings to replace checkbox for double tap with select
  for none, double tap, or swipe.
* Added database migrations for new gesture_nav option.
  * Rename `users.double_tap` to `users.gesture_nav` and migrate
    existing user settings.
* Updated translation files. (Non-English updated with Google
  Translate.)

Resolves #1449, closes #1495
2023-03-28 18:00:57 -07:00
Frédéric Guillot 140a40acaf Use secrets.GITHUB_TOKEN to push images instead of a PAT 2023-03-27 21:29:33 -07:00
toastal 56efba66f5 Prefer typographic punctuation
For a long time, we’ve not been limited to ASCII and have machines that
can properly render the typographically-correct punctuation symbols for
our languages. This leads to a better, clearer reading experience and
also matches the `<meta charset="utf-8">` and the the use of such
punctuation on FAQs.

Changes:
• Ellipsis: `...` → `…` (https://en.wikipedia.org/wiki/Ellipsis)
• Apostrophe: `'` → `’` (https://en.wikipedia.org/wiki/Apostrophe)

While I could try to do research on other languages, I’m not a native
speaker in them and wouldn’t feel comfortable making any adjustments
outside of English.
2023-03-27 20:55:25 -07:00
Frédéric Guillot 7e612cddd3 Update issue templates 2023-03-26 19:13:53 -07:00
Davide Masserut 034e46700c Process older entries first
Feed entries are usually ordered from most to least recent.

Processing older entries first ensures that their creation timestamp
is lower than that of newer entries.

This is useful when we order by creation, because then we get a
consistent timeline.
2023-03-25 16:19:07 -07:00
Daniel Jakots ac8f64d7a1 Set Prometheus as datasource everywhere
Requested by @lnicola.
2023-03-24 20:12:13 -07:00
Daniel Jakots b536e05fee Fix grafana dashboard 2023-03-24 20:12:13 -07:00
dependabot[bot] 6eed037186 Bump actions/setup-go from 3 to 4
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 20:20:59 -07:00
Frédéric Guillot 5912400dee Push Docker images to Quay.io (RedHat) 2023-03-19 21:25:05 -07:00
Frédéric Guillot ab209df78f Update ChangeLog 2023-03-16 19:34:20 -07:00
dependabot[bot] 11a352dcfd Bump github.com/tdewolff/minify/v2 from 2.12.4 to 2.12.5
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.4 to 2.12.5.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.4...v2.12.5)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-16 18:04:23 -07:00
Frédéric Guillot 9ae6922bdc Fix null reference in toggle entry attachments shortcut
Fixes #1723
2023-03-13 20:20:35 -07:00
Frédéric Guillot ea8c3c801a Update Security policy 2023-03-13 19:56:47 -07:00
Frédéric Guillot eb9508502c Avoid XSS when opening a broken image due to unescaped ServerError in proxy handler
Creating an RSS feed item with the inline description containing an `<img>` tag
with a `srcset` attribute pointing to an invalid URL like
`http:a<script>alert(1)</script>`, we can coerce the proxy handler into an error
condition where the invalid URL is returned unescaped and in full.

This results in JavaScript execution on the Miniflux instance as soon as the
user is convinced to open the broken image.
2023-03-12 22:36:03 -07:00
Frédéric Guillot b46b5dfb2a Use r.RemoteAddr to check /metrics endpoint network access
HTTP headers like X-Forwarded-For or X-Real-Ip can be easily spoofed. As
such, it cannot be used to test if the client IP is allowed.

The recommendation is to use HTTP Basic authentication to protect the
metrics endpoint, or run Miniflux behind a trusted reverse-proxy.
2023-03-11 20:53:12 -08:00
Frédéric Guillot 877dbed5e8 Add HTTP Basic authentication for /metrics endpoint 2023-03-11 20:13:52 -08:00
fructurj 79ff381c4c Update es_ES.json 2023-03-11 17:38:07 -08:00
dependabot[bot] f6a672738a Bump golang.org/x/crypto from 0.6.0 to 0.7.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 20:38:55 -08:00
dependabot[bot] e4964d6933 Bump golang.org/x/oauth2 from 0.5.0 to 0.6.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/oauth2/releases)
- [Commits](https://github.com/golang/oauth2/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 20:27:58 -08:00
Davide Masserut 755c9af47d Update scraping rules for ilpost.it 2023-03-01 20:04:25 -08:00
Frédéric Guillot 02e4b8eadc Update GitHub Actions to use Go 1.20 2023-03-01 19:56:06 -08:00
Frédéric Guillot aaa1625724 Ignore empty link when discovering feeds 2023-02-26 17:19:26 -08:00
Frédéric Guillot bb5f3ec6a8 Disable CGO explicitly to make sure the binary is statically linked
Apparently this behavior has been changed in Go 1.20: https://tip.golang.org/doc/go1.20#cgo
2023-02-25 16:55:11 -08:00
Sigsign 8804eb9a78 Update Japanese translation 2023-02-25 15:58:39 -08:00
Romain de Laage 2c2700a31d Proxy support for several media types
closes #615
closes #635
2023-02-25 15:57:59 -08:00
privatmamtora 8f9ccc6540 Parse <category> from Feeds (RSS, Atom and JSON) 2023-02-24 20:52:45 -08:00
dependabot[bot] ff8d68c151 Bump github.com/PuerkitoBio/goquery from 1.8.0 to 1.8.1
Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/PuerkitoBio/goquery/releases)
- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/PuerkitoBio/goquery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-20 19:24:57 -08:00
the7thNightmare 1fb0bc29db Update the plural for Indonesian
Copied from the zh_CN plural
2023-02-19 19:53:06 -08:00
Ananta Krsna dasa a1593b8942 Run the application in one command 2023-02-19 11:56:51 -08:00
Ananta Krsna dasa 20c4cb770e Bring back the health check condition to depends_on 2023-02-19 11:56:51 -08:00
Ananta Krsna dasa db7a4ae7e9 Remove deprecated version element 2023-02-19 11:56:51 -08:00
the7thNightmare aabb766fad Add Indonesian Language 2023-02-19 11:49:17 -08:00
the7thNightmare 8dce3099d9 Add Indonesian Language 2023-02-19 11:49:17 -08:00
dependabot[bot] fb2b43176f Bump golang.org/x/net from 0.6.0 to 0.7.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.6.0 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-14 19:06:58 -08:00
dependabot[bot] 2f6034c63c Bump golang.org/x/crypto from 0.5.0 to 0.6.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-09 17:54:31 -08:00
dependabot[bot] 67190fc988 Bump golang.org/x/oauth2 from 0.4.0 to 0.5.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/oauth2/releases)
- [Commits](https://github.com/golang/oauth2/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-09 17:49:00 -08:00
dependabot[bot] e4c0495646 Bump golang.org/x/net from 0.5.0 to 0.6.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-08 20:15:36 -08:00
dependabot[bot] a7508b2746 Bump golang.org/x/term from 0.4.0 to 0.5.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-07 20:16:21 -08:00
Wojtek 34408b50a7 Add CSS classes to differentiate between category/feed/entry view and icons 2023-02-06 20:46:42 -08:00
Marie Ramlow 48acd1feca Add rewrite and scraper rules for blog.cloudflare.com 2023-02-05 21:01:42 -08:00
Ryan Cao 8d51fd8ff5 fix: add color-scheme to themes 2023-02-05 20:58:23 -08:00
Martin Vietz a44ba4abcb Add toggle open/close entry attachments shortcut 2023-02-05 20:51:51 -08:00
dependabot[bot] b338c9b3c2 Bump github.com/yuin/goldmark from 1.5.3 to 1.5.4
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.5.3 to 1.5.4.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.5.3...v1.5.4)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-02 20:07:07 -08:00
xdavidwu 08f7835f5d sanitizer: allow id in <sup>
One of blogs I read uses anchor on <sup> to link a footnote back to its
reference.
2023-01-31 17:53:45 -08:00
dependabot[bot] d38fc80bad Bump docker/build-push-action from 3 to 4
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3 to 4.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 17:04:35 -08:00
Frédéric Guillot b2fd84e0d3 Update ChangeLog 2023-01-29 17:01:14 -08:00
Sigsign e64f488654 Update Japanese translations 2023-01-28 17:58:56 -08:00
Sigsign 8017ed2cf6 Sort like en_US.json 2023-01-28 17:58:56 -08:00
Davide Masserut 65febebd40 Fix header items wrapping 2023-01-17 20:00:13 -08:00
Frédéric Guillot 2e047dff98 Add option to enable or disable double tap 2023-01-14 16:59:52 -08:00
Frédéric Guillot 6612e42668 Improve PWA display mode label in settings page 2023-01-14 15:39:09 -08:00
dependabot[bot] 2956bbad8d Bump golang.org/x/oauth2 from 0.3.0 to 0.4.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/oauth2/releases)
- [Commits](https://github.com/golang/oauth2/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 07:23:59 -08:00
dependabot[bot] 3285a00ebc Bump golang.org/x/crypto from 0.4.0 to 0.5.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 07:23:41 -08:00
dependabot[bot] c0c8e47344 Bump golang.org/x/net from 0.4.0 to 0.5.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 07:14:53 -08:00
dependabot[bot] 3fc02df70f Bump golang.org/x/term from 0.3.0 to 0.4.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-05 07:06:56 -08:00
Tadeusz Magura-Witkowski c071201e37 Update pl_PL.json
Fixed message for form.feed.label.disable (for some reason this was in Russian?).
2022-12-29 12:56:50 -08:00
Davide Masserut 690d66ce0b Update scraping rules for ilpost.it 2022-12-27 13:33:41 -08:00
Davide Masserut ef312ef770 Update scraping rule for ilpost.it 2022-12-16 15:07:10 -08:00
Davide Masserut c0bed53b42 Add scraping rule for ilpost.it 2022-12-15 19:53:12 -08:00
Davide Masserut c0ee3ed375 Update reading time HTML element after fetching the original web page 2022-12-14 19:53:04 -08:00
Davide Masserut ce35b46fee Add category feeds refresh 2022-12-12 19:41:30 -08:00
Frédéric Guillot e12c263fc9 Update Changelog 2022-12-10 10:45:34 -08:00
dependabot[bot] 281c9be6c8 Bump golang.org/x/crypto from 0.3.0 to 0.4.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-07 20:28:01 -08:00
dependabot[bot] b92debf9c9 Bump golang.org/x/net from 0.3.0 to 0.4.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.3.0 to 0.4.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.3.0...v0.4.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-07 20:16:18 -08:00
nyanpasu64 b6c368c39c Disable double-tap mobile gesture if swipe gesture is disabled
Fixes #441.
2022-12-06 20:34:15 -08:00
dependabot[bot] de92e3e472 Bump golang.org/x/oauth2 from 0.2.0 to 0.3.0
Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/oauth2/releases)
- [Commits](https://github.com/golang/oauth2/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/oauth2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-06 19:57:18 -08:00
Harry Cheng d9777f1439 Skip integrations if there are no entries to push 2022-12-04 12:58:10 -08:00
mutantmonkey b4643fd27f Enable TLS-ALPN-01 challenge for ACME
This type of challenge works purely at the TLS layer and is compatible
with SNI proxies. The existing HTTP-01 challenge support has been left
as-is.

Fixes #1476.
2022-11-16 20:41:55 -08:00
Frédéric Guillot d9cf3f9c38 Preconfigure Miniflux for GitHub Codespaces 2022-11-17 03:49:05 +00:00
Frédéric Guillot dfd6b769b9 Create devcontainer.json 2022-11-16 17:34:55 -08:00
dependabot[bot] 68780de6fc Bump golang.org/x/crypto from 0.2.0 to 0.3.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/compare/v0.2.0...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-16 16:18:50 -08:00
Frédéric Guillot 93715b542c Revert "scraper follow the only link"
This reverts commit 10207967c4.
2022-11-14 17:45:40 -08:00
Frédéric Guillot de1a06e3e8 Add missing check in followTheOnlyLink() that leads to a panic
Bug introduced in PR #1290. Fixes #1631.
2022-11-14 16:44:02 -08:00
Frédéric Guillot bfebf946c2 Update ChangeLog 2022-11-13 15:22:25 -08:00
Frédéric Guillot 6094130f61 Update Go dependencies 2022-11-13 14:56:43 -08:00
Frédéric Guillot d1bd0073cc Pin Postgres image version in docker-compose examples to avoid unexpected upgrades 2022-11-13 14:14:04 -08:00
Juan Pedro Bretti Mandarano 789e30fdff Locale terms normalization 2 2022-11-13 13:55:08 -08:00
Juan Pedro Bretti Mandarano 04507a8a34 Locale terms normalization #1619 2022-11-13 13:55:08 -08:00
Frédéric Guillot 1ded3f8bc7 Update integration tests 2022-11-12 21:03:43 -08:00
Frédéric Guillot 3d64162f06 Allow Content-Type and Accept headers to CORS policy 2022-11-12 21:03:43 -08:00
George Angelopoulos 79357a527f Use $(...) notation instead of legacy backticked ...
This is based on a shellcheck recommendation.
https://www.shellcheck.net/wiki/SC2006
2022-11-12 20:30:44 -08:00
George Angelopoulos 1e16e19d11 use dirs file for debian package
Instead of running mkdir from debian/rules, the native way to create the
necessary directories is with the dirs file which is read by the
dh_installdirs helper script.

See:
* https://www.debian.org/doc/manuals/maint-guide/dother.en.html#dirs
* dh_installdirs(1) manual page

I am not aware of any specific problems that could arise from doing it
manually instead of using dh_installdirs. But sticking to the native
approach might be a good idea.
2022-11-12 20:30:44 -08:00
Romain de Laage efa8bfcf0e Use custom home page in PWA 2022-11-12 20:12:39 -08:00
dependabot[bot] 1351761f81 Bump golang.org/x/term from 0.1.0 to 0.2.0
Bumps [golang.org/x/term](https://github.com/golang/term) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/golang/term/releases)
- [Commits](https://github.com/golang/term/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-07 20:35:16 -08:00
dependabot[bot] 962a0460cc Bump github.com/prometheus/client_golang from 1.13.0 to 1.13.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.13.0 to 1.13.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/v1.13.1/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.13.0...v1.13.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-07 19:49:37 -08:00
jebbs 10207967c4 scraper follow the only link
* in some cases, what the scraper got is only a landing page, user can use scraper rules to extract the link of the landing page and follow it
* it also fix the  wrong scrape rule apply when the server redirects it to another host
2022-10-31 19:49:34 -07:00
Frédéric Guillot 242eeaf07e Update golang.org/x/* dependencies 2022-10-27 18:09:19 -07:00
Romain de Laage 550e7d0415 Add matrix bot support 2022-10-27 17:53:19 -07:00
Romain de Laage 3f14d08095 Proxify images in API responses 2022-10-27 17:33:18 -07:00
Frédéric Guillot 206be5ba15 Rename column to CategoriesSortingOrder 2022-10-25 20:06:28 -07:00
Romain de Laage 83e1f154b5 Add optional sort option in category page
closes #1552
2022-10-25 20:06:28 -07:00
Romain de Laage ec47106c26 Remove dependency to go-server-timing
fix #1586
2022-10-23 16:59:05 -07:00
Dave Marquard 74b69a4c7c Add support for the continuation parameter and result for Google Reader API ID calls
Support the ``continuation parameter and result for Google Reader API ID calls. Allows clients to support paging of results / fetching additional results when the number of entries exceed the limit parameter passed in by the client.
2022-10-23 16:49:45 -07:00
George Angelopoulos 84d5a9a354 Use automatic variable for build target file names 2022-10-19 20:37:28 -07:00
Romain de Laage eb86773039 Recalbox rewrite rule 2022-10-19 20:13:44 -07:00
Michael van Tricht 44ca5b8591 Fix reading time dutch text 2022-10-17 21:16:41 -07:00
Frédéric Guillot 95e125dfc7 Update ChangeLog 2022-10-16 14:29:17 -07:00
jgbresson 7f6ce16d85 Add scraping rules for theverge.com 2022-10-16 11:58:35 -07:00
jgbresson aa47789f55 Add add_dynamic_image rewrite rule for theverge.com 2022-10-16 11:57:01 -07:00
Dave Marquard 7a961babd4 support date filtering for all google reader api item ID calls 2022-10-16 11:43:34 -07:00
Frédéric Guillot d947b0194b Handle RSS entries with only a GUID permalink 2022-10-09 16:58:25 -07:00
jtagcat cd7f01f573 client.New(): accept endpoint URLs with /v1/ 2022-10-03 19:49:45 -07:00
dependabot[bot] 159abf7b7d Bump github.com/tdewolff/minify/v2 from 2.12.2 to 2.12.4
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.2 to 2.12.4.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.2...v2.12.4)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-03 17:41:04 -07:00
dependabot[bot] 1b6eafa7d9 Bump github.com/yuin/goldmark from 1.4.15 to 1.5.2
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.15 to 1.5.2.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.15...v1.5.2)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 16:12:44 -07:00
dependabot[bot] 42928e0a9d Bump github.com/tdewolff/minify/v2 from 2.12.1 to 2.12.2
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.1 to 2.12.2.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.1...v2.12.2)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 16:12:16 -07:00
Tomas Peterka 70c58bd346 API: Allow Basic authorization header 2022-09-19 20:56:27 -07:00
dependabot[bot] 556d88c96d Bump github.com/yuin/goldmark from 1.4.14 to 1.4.15
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.14 to 1.4.15.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.14...v1.4.15)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 19:29:05 -07:00
Frédéric Guillot e0aa9459c9 Remove Scorecards GitHub Action 2022-09-18 17:40:17 -07:00
Frédéric Guillot e8fe4ed881 Log feed URL when submitting a subscription that returns an error 2022-09-18 17:37:53 -07:00
Frédéric Guillot 98c166c950 Update "make run" command to execute migrations 2022-09-18 16:57:40 -07:00
jtagcat 3f64e4b943 Add option to send only the URL to Wallabag 2022-09-18 16:52:28 -07:00
ericgaspar 133a1f83e3 Fix typo 2022-09-18 10:49:49 -07:00
dependabot[bot] c50a7d9938 Bump ossf/scorecard-action from 1.1.2 to 2.0.3
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 1.1.2 to 2.0.3.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/ce330fde6b1a5c9c75b417e7efc510b822a35564...865b4092859256271290c77adbd10a43f4779972)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 19:21:35 -07:00
dependabot[bot] 753940e0cf Bump github.com/lib/pq from 1.10.6 to 1.10.7
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.6 to 1.10.7.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.6...v1.10.7)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 19:21:11 -07:00
Frédéric Guillot 138fd926ee Do not convert anchors to absolute links 2022-09-11 22:40:52 -07:00
dependabot[bot] 183cb491b3 Bump github.com/yuin/goldmark from 1.4.13 to 1.4.14
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.4.13 to 1.4.14.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.4.13...v1.4.14)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 15:36:33 -07:00
dependabot[bot] ee070de860 Bump github.com/tdewolff/minify/v2 from 2.12.0 to 2.12.1
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.12.0 to 2.12.1.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.12.0...v2.12.1)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 15:34:58 -07:00
Nicole bbc087d2ad Allow setting a custom image proxy URL 2022-08-29 20:33:47 -07:00
Frédéric Guillot b8c3153b75 Update dependencies 2022-08-28 20:50:45 -07:00
Romain de Laage d7f978da49 Allow zoom on mobile devices
See https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag#user-scalable
2022-08-21 21:33:32 -07:00
Frédéric Guillot 0afad251e0 Fix integration tests on GitHub Actions 2022-08-21 15:31:52 -07:00
Adam B 4d847c6a74 Add scraping rule for royalroad.com
This is what I use for several stories I follow, and I thought it might be useful to other miniflux users.
2022-08-17 19:25:39 -07:00
Owen Valentine f404ddde91 Add swordscomic.com 2022-08-17 19:23:29 -07:00
Owen Valentine c8a3d953cf Add smbc-comics.com 2022-08-17 19:23:29 -07:00
Owen Valentine f851ecac78 Sort alphabetically 2022-08-17 19:23:29 -07:00
Leonid Shevtsov 1c1fcdcd3d Tweak page.categories.feed_count for UA locale 2022-08-15 20:20:23 -07:00
Leonid Shevtsov 594e9a6039 Add Ukrainian locale 2022-08-15 20:20:23 -07:00
Frédéric Guillot ffafa810da Update golang.org/x/crypto 2022-08-13 21:58:45 -07:00
Frédéric Guillot 1ca35e9364 Update ChangeLog 2022-08-13 12:15:17 -07:00
Frédéric Guillot 4dc79833da Update path to Dockerfiles in dependabot.yml 2022-08-11 21:36:28 -07:00
dependabot[bot] e8c4ce995c Bump actions/setup-go from 2 to 3
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 3.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-11 21:29:25 -07:00
dependabot[bot] 9daaffa23f Bump actions/upload-artifact from 2.3.1 to 3.1.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2.3.1 to 3.1.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/82c141cc518b40d92cc801eee768e7aafc9c2fa2...3cea5372237819ed00197afe530f5a7ea3e805c8)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-11 21:28:20 -07:00
dependabot[bot] 63bada09e0 Bump docker/build-push-action from 2 to 3
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2 to 3.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-11 21:27:54 -07:00
dependabot[bot] 612e86cf86 Bump docker/login-action from 1 to 2
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-11 21:26:26 -07:00
Frédéric Guillot 5abf8c6798 Rename default branch to main 2022-08-11 21:17:38 -07:00
Frédéric Guillot 992422c91f Update CodeQL workflow 2022-08-08 22:59:54 -07:00
dependabot[bot] 32ae3c4d4b Bump docker/setup-buildx-action from 1 to 2
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1 to 2.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:27:39 -07:00
dependabot[bot] 9ee9c0579f Bump golangci/golangci-lint-action from 2 to 3
Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 2 to 3.
- [Release notes](https://github.com/golangci/golangci-lint-action/releases)
- [Commits](https://github.com/golangci/golangci-lint-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: golangci/golangci-lint-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:21:03 -07:00
dependabot[bot] 39a8c8c10d Bump ossf/scorecard-action from 1.0.1 to 1.1.2
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 1.0.1 to 1.1.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/e3e75cf2ffbf9364bbff86cdbdf52b23176fe492...ce330fde6b1a5c9c75b417e7efc510b822a35564)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:20:29 -07:00
dependabot[bot] 49c2d3e34d Bump actions/checkout from 2 to 3
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:19:50 -07:00
dependabot[bot] bcef6e2de4 Bump docker/setup-qemu-action from 1 to 2
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1 to 2.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:19:02 -07:00
dependabot[bot] 26d1b8eb4c Bump github.com/prometheus/client_golang from 1.12.2 to 1.13.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.2 to 1.13.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.2...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-08 22:17:56 -07:00
Frédéric Guillot cecab91298 Fix some linter issues 2022-08-08 22:06:38 -07:00
Frédéric Guillot 3eb3ac06b6 Revert "Google Reader API: Fix incorrect ParseInt"
This reverts commit 071e9106a8.
2022-08-03 18:16:20 -07:00
dependabot[bot] aefadfeed4 Bump github.com/yuin/goldmark from 1.1.32 to 1.4.13
Bumps [github.com/yuin/goldmark](https://github.com/yuin/goldmark) from 1.1.32 to 1.4.13.
- [Release notes](https://github.com/yuin/goldmark/releases)
- [Commits](https://github.com/yuin/goldmark/compare/v1.1.32...v1.4.13)

---
updated-dependencies:
- dependency-name: github.com/yuin/goldmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-01 19:33:32 -07:00
Frédéric Guillot 13fa08ad39 Handle Atom links with a text/html type defined 2022-07-31 17:43:03 -07:00
Frédéric Guillot 071e9106a8 Google Reader API: Fix incorrect ParseInt 2022-07-31 17:17:17 -07:00
Gabe Cook 405d4febd9 Parse markdown by default for blog.laravel.com 2022-07-30 20:19:09 -07:00
Gabe Cook 36df7b36ec Add parse_markdown rewrite function 2022-07-30 20:19:09 -07:00
Frédéric Guillot 9ba15e9649 Build RPM and Debian packages on GitHub Actions 2022-07-30 20:11:21 -07:00
Gabe Cook bd1dc3149e Add explosm.net scraper rule 2022-07-30 20:10:52 -07:00
Romain de Laage 03a1cfcd5e Make default home page configurable 2022-07-26 22:03:03 -07:00
Frédéric Guillot 45a9fd5af6 Revert "Don't word-wrap in article titles"
This reverts commit 15268ef4f6.

Unfortunately, it's not really usable on a mobile phone because most
titles are truncated.
2022-07-23 21:46:32 -07:00
Frédéric Guillot 224584312a Add title attribute to entry links because text could be truncated 2022-07-23 18:11:06 -07:00
Frédéric Guillot 5f29af30b0 Add missing CSS after merging PR 1506 2022-07-23 18:09:37 -07:00
ltdk 15268ef4f6 Don't word-wrap in article titles 2022-07-23 17:28:24 -07:00
Romain de Laage 3ac5095776 Highlight categories with unread entries 2022-07-23 17:18:35 -07:00
privatmamtora d7cf782019 Allow option to order by title & author 2022-07-18 21:14:00 -07:00
Frédéric Guillot 055097fdeb Add missing return statements in PR #1225 2022-07-18 21:13:21 -07:00
Ilya Brin 9805654247 Update ru_RU.json
little typos fixed
2022-07-18 20:49:01 -07:00
Gabriel Augendre 6e50ce3293 Make reading speed user-configurable 2022-07-17 19:35:24 -07:00
pawanrai9999 3a0aaddafd Added locale hi_IN
- Added translation for Hindi language used in India
- Thanks to Prince Kumar for helping in translations
                                        - जय हिन्द
2022-07-16 16:40:59 -07:00
Carsten 2659883ce5 Add rewrite rules for article URL before fetching content 2022-07-11 21:12:26 -07:00
dependabot[bot] b1f6f2e7ac Bump github.com/tdewolff/minify/v2 from 2.11.11 to 2.12.0
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.11 to 2.12.0.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.11...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-11 20:10:43 -07:00
Alexandros Kosiaris cf96ab45c1 Support other repo owners in GH docker action
Being able to functionaly utilize the same GH Docker action can be
beneficial for users wanting to build, test and develop in their own
repos. To do so, use ${{ github.repository_owner }} in the action. That
should provide some rudimentary flexibility without breaking the
canonical repo.

Users of this functionality should take care to populate the required
secrets in Github

* DOCKERHUB_TOKEN, DOCKERHUB_USERNAME
* CR_PAT
2022-07-09 19:06:33 -07:00
Frédéric Guillot abe568b5b3 Proxify empty URL should not crash 2022-07-05 21:20:03 -07:00
Frédéric Guillot c0eab5ebc5 Avoid stretched image if specified width is larger than Miniflux's layout 2022-07-04 20:10:07 -07:00
Frédéric Guillot f0a698c6fe Add support for OPML files with several nested outlines 2022-07-04 16:02:49 -07:00
Frédéric Guillot 806a069785 sanitizer: handle image URLs in srcset attribute with comma 2022-07-04 13:50:09 -07:00
Frédéric Guillot d85908e3de Allow width and height attributes for img tags 2022-07-03 17:44:12 -07:00
dependabot[bot] 9c6ea92122 Bump github.com/tdewolff/minify/v2 from 2.11.10 to 2.11.11
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.10 to 2.11.11.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.10...v2.11.11)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-02 05:35:26 -07:00
Alexandros Kosiaris 229774d65d Document that -config-dump shows sensitive info
Why:
Avoid surprising users that may not expect that passwords might be
printed in the config dump output

What:
Update manpage
2022-06-27 17:43:23 +02:00
dependabot[bot] aba2f83371 Bump github.com/tdewolff/minify/v2 from 2.11.9 to 2.11.10
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.9 to 2.11.10.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.9...v2.11.10)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-27 17:42:04 +02:00
Marcus Rohrmoser 69dca2b619 System-V init service. 2022-06-14 21:45:43 +02:00
dependabot[bot] fc4bfb2dc1 Bump github.com/tdewolff/minify/v2 from 2.11.7 to 2.11.9
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.7 to 2.11.9.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.7...v2.11.9)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-07 21:40:24 +02:00
Pk11 a904c634b8 request_builder.js: Fix syntax error 2022-06-06 10:22:41 +02:00
dependabot[bot] 938ae6828c Bump github.com/tdewolff/minify/v2 from 2.11.5 to 2.11.7
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.5 to 2.11.7.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.5...v2.11.7)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-02 15:37:51 +02:00
Frédéric Guillot 79df37ac1e Fix RPM Dockerfile packager 2022-05-27 20:47:14 -07:00
Frédéric Guillot f546fc3060 Fix typo 2022-05-27 20:46:41 -07:00
Frédéric Guillot 6bf89f4b32 Update Changelog 2022-05-27 14:29:57 -07:00
nemunaire 5a07fd8932 Add new rewrite rule to decode base64 content 2022-05-25 20:44:04 -07:00
Frédéric Guillot 9fa086e471 Fix flaky test 2022-05-25 20:34:37 -07:00
kencx 1658db7f10 Add Linkding integration 2022-05-24 20:14:07 -07:00
dependabot[bot] 780c9e3696 Bump github.com/tdewolff/minify/v2 from 2.11.2 to 2.11.5
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.2 to 2.11.5.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.2...v2.11.5)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-23 19:46:07 -07:00
luginbash 74fb430697 Adding comment button to telegram message if there's any 2022-05-21 11:45:39 -07:00
Pascal Noisette d118aa8649 Add API endpoint to fetch unread and read counters 2022-05-21 11:44:56 -07:00
dependabot[bot] 953c1742e0 Bump github.com/prometheus/client_golang from 1.12.1 to 1.12.2
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.1 to 1.12.2.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.1...v1.12.2)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-16 17:37:55 -07:00
dependabot[bot] a88996fb3f Bump github.com/lib/pq from 1.10.5 to 1.10.6
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.5 to 1.10.6.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.5...v1.10.6)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-16 17:37:02 -07:00
Lars Windolf 0014aac201 Fixes logic bug in sanity check 2022-05-11 22:32:48 -07:00
Lars Windolf 8e6babe5d4 Fix typo 2022-05-11 22:00:33 -07:00
Pascal Noisette cdeb2a8fc0 Reduce number of preflight check to save network brandwidth 2022-05-07 10:38:55 -07:00
dependabot[bot] a6e9f88e7a Bump github.com/tdewolff/minify/v2 from 2.11.1 to 2.11.2
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.11.1 to 2.11.2.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.11.1...v2.11.2)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-02 20:31:58 -07:00
Berk Özkütük ab3fdf509f Add Espial integration 2022-04-20 19:44:47 -07:00
knrdl fb585d0086 Allow API search for entries which are not starred 2022-04-13 21:53:06 -07:00
lf94 fa8431c5c6 Try to use outermost element text when title is empty 2022-04-13 21:51:54 -07:00
dzaikos ec2b911881 Make swipe gestures feel more natural.
Removes opacity transition when swiping an article read/unread.

Adds "resistance" to the swiped entry when the 75px threshold is
reached.

Fixes an issue in which a swiped article couldn't be moved <15px.
2022-04-13 21:17:53 -07:00
dependabot[bot] 0f2b29741e Bump github.com/lib/pq from 1.10.4 to 1.10.5
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.4 to 1.10.5.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.4...v1.10.5)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-11 19:12:40 -07:00
dependabot[bot] 2f26b581f4 Bump github.com/tdewolff/minify/v2 from 2.10.0 to 2.11.1
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.10.0 to 2.11.1.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.10.0...v2.11.1)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-11 19:12:20 -07:00
Dave Marquard ad266e45ca add support for feed streams to Reader API IDs API 2022-04-01 19:39:27 -07:00
Frédéric Guillot f6825c1c60 Fix invalid parsing of data URL
Fetching icons crashes with "slice bounds out of range" error if no encoding is specified.
2022-03-25 22:30:20 -07:00
austinsuyoyo 40f958c5ef Add Traditional Chinese translation 2022-03-24 21:56:12 -07:00
Frédéric Guillot 02e975d3f3 Add distroless Docker image variant 2022-03-15 22:55:31 -07:00
Frédéric Guillot 40b54b458d Add Go 1.18 to GitHub Actions 2022-03-15 21:01:00 -07:00
Frédéric Guillot ca60f3cff5 Update ChangeLog 2022-03-08 21:12:23 -08:00
Thiago Perrotta c1ece47532 Gray out pagination buttons when they are not applicable
Whenever the "prev" and "next" buttons have no hyperlink, decrease their
opacity to signal that they lead to nowhere.

This signal is stronger and more obvious than the current one which
merely removes the underline decoration from the text.

This patch is an improvement on top of
https://github.com/miniflux/v2/pull/1107
2022-03-07 14:59:02 -08:00
dependabot[bot] b2e5eda89e Bump mvdan.cc/xurls/v2 from 2.3.0 to 2.4.0
Bumps [mvdan.cc/xurls/v2](https://github.com/mvdan/xurls) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/mvdan/xurls/releases)
- [Commits](https://github.com/mvdan/xurls/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: mvdan.cc/xurls/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-07 14:55:25 -08:00
Frédéric Guillot 1eb01b39e7 Use truncated entry description as title if unavailable 2022-03-04 17:10:32 -08:00
Frédéric Guillot c9e0f0b3e4 Do not fallback to InnerXML if XHTML title is empty 2022-03-04 14:28:56 -08:00
Thiago Perrotta 1e357d3ced Add '+' shortcut for new subscription page 2022-03-02 21:39:23 -08:00
Thiago Perrotta 4831acba39 Add (+) action next to Feeds to quickly add new feeds 2022-03-02 20:38:06 -08:00
月杪 658b8591b6 Unstar not working via Google Reader API/Reeder 2022-02-22 20:12:46 -08:00
Frédéric Guillot 950b001675 Remove circles in front of page header list items 2022-02-13 15:06:22 -08:00
Frédéric Guillot 0b5b41acd9 Fix CSS hover style for links styled as buttons 2022-02-13 14:45:52 -08:00
Frédéric Guillot 6b4b9e9cff Add missing page header icons 2022-02-13 14:34:45 -08:00
Frédéric Guillot da89831116 Avoid showing "undefined" when clicking on read/unread 2022-02-01 21:15:57 -08:00
Thiago Perrotta 3243d88c9b refactor handleEntryStatus / goToNextListItem / goToPrevListItem 2022-02-01 20:42:14 -08:00
Thiago Perrotta 824fc310a9 Add new keyboard shortcut: 'M' - toggle read/unread, go to prev item
Currently there is "Toggle read/unread = m", which toggles and
then goes to the next item.

Having the opposite operation available is handy, especially when adding
new feeds and going through them from oldest to newest posts.

It seems natural to map 'M' (= shift + 'm') for this action.

Closes https://github.com/miniflux/v2/issues/1352
2022-02-01 20:42:14 -08:00
Thiago Perrotta c891ab2588 Add several icons to menus according to their roles
- refresh: https://tabler-icons.io/i/refresh
- edit: https://tabler-icons.io/i/edit
- delete: https://tabler-icons.io/i/delete
- mark page as read: https://tabler-icons.io/i/check
- mark all as read: https://tabler-icons.io/i/checks
- show all entries: https://tabler-icons.io/i/eye
- show only unread entries: https://tabler-icons.io/i/eye-off
- create category: https://tabler-icons.io/i/folder-plus
- add subscription: https://tabler-icons.io/i/plus
- import: https://tabler-icons.io/i/file-import
- export: https://tabler-icons.io/i/file-export
- categories: https://tabler-icons.io/i/folders
2022-02-01 20:39:05 -08:00
Frédéric Guillot 33e9b26fe9 Add missing event argument to onClick() function call 2022-01-31 15:37:54 -08:00
Thiago Perrotta 6e402f60dc Add links to scraper/rewrite/filtering docs when editing feeds 2022-01-31 14:53:58 -08:00
dependabot[bot] 4b56c84a8c Bump github.com/prometheus/client_golang from 1.12.0 to 1.12.1
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.0...v1.12.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 14:53:49 -08:00
dependabot[bot] cee70e04db Bump github.com/tdewolff/minify/v2 from 2.9.29 to 2.10.0
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.29 to 2.10.0.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.29...v2.10.0)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-31 14:44:36 -08:00
Romain de Laage 808635e314 Add a rewrite rule for castopod episodes 2022-01-30 16:33:17 -08:00
Jacob Duba 7a1429bcc1 Fix regression: reset touch-item if not in /unread 2022-01-27 19:39:35 -08:00
Pascal Noisette 21bbaf2691 Add API endpoint to fetch original article 2022-01-26 20:29:37 -08:00
Orpheus Lummis 50c5850f0d Show the category first in feed settings 2022-01-24 21:03:32 -08:00
Thiago Perrotta d22c6c535e Add pagination on top of all entries. Closes #1305.
Enable users to move to prev/next page without having to scroll all the
way to the bottom of the page.

Furthermore, ensure consistency with entry.html which has top and bottom
pagination.
2022-01-24 20:55:27 -08:00
dependabot[bot] 6a41eb382f Bump github.com/prometheus/client_golang from 1.11.0 to 1.12.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.11.0 to 1.12.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.11.0...v1.12.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 19:56:36 -08:00
dependabot[bot] aa70ebcdca Bump github.com/tdewolff/minify/v2 from 2.9.28 to 2.9.29
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.28 to 2.9.29.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.28...v2.9.29)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-24 19:56:08 -08:00
Alex Cohn 7111b3749d Display Go version on /about
Translations are copy-paste best effort from the equivalent Postgres
string, and might not account for grammatical gender, etc.
2022-01-22 11:00:12 -08:00
Frédéric Guillot 93277f4307 Update ChangeLog 2022-01-21 16:45:37 -08:00
Frédéric Guillot 9e3ce3f3a4 Set read-all permission to GITHUB_TOKEN for CI jobs 2022-01-19 21:44:23 -08:00
Frédéric Guillot a98e479e71 Pin jshint version in linter job 2022-01-19 21:37:38 -08:00
Frédéric Guillot 897d8644c5 Fix incorrect conversion between integer types 2022-01-19 21:23:56 -08:00
Frédéric Guillot 2f7ad3ac73 Create scorecards-analysis.yml 2022-01-19 20:47:57 -08:00
Frédéric Guillot 20318e3a79 Create CodeQL workflow 2022-01-19 20:37:48 -08:00
Adrian Smith cc3e65dd3c Handle atom feed with space around CDATA
Trim space around CDATA elements before extracting the CharData.

This problem was discovered when reading https://www.sethvargo.com/feed.xml.
Title and Summary fields have newlines and space between the <title>
element and the CDATA element. e.g.

  <title>
    <![CDATA[Entry title here]]>
  </title>

This meant the title of the feed was coming into MiniFlux as,
  <![CDATA[Entry title here]]>
2022-01-17 15:25:22 -08:00
dependabot[bot] 7b0a4a7803 Bump github.com/tdewolff/minify/v2 from 2.9.27 to 2.9.28
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.27 to 2.9.28.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.27...v2.9.28)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-17 15:15:25 -08:00
Thiago Perrotta bab7734582 packaging: add Documentation to systemd service
c.f. https://www.freedesktop.org/software/systemd/man/systemd.directives.html#Documentation=
2022-01-17 13:23:19 -08:00
Jacob Duba 3dda84bf9a Do not reset touch-item if successfully swiped 2022-01-15 10:47:25 -08:00
Frédéric Guillot f18ded6117 Add support for multiple authors in Atom feeds 2022-01-14 20:20:55 -08:00
dependabot[bot] 41d47244af Bump github.com/tdewolff/minify/v2 from 2.9.26 to 2.9.27
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.26 to 2.9.27.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.26...v2.9.27)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-10 17:47:54 -08:00
Frédéric Guillot b113af8472 Omit User-Agent header in image proxy to avoid being blocked 2022-01-09 20:23:35 -08:00
Frédéric Guillot 2309b27458 Use custom feed user agent to fetch website icon 2022-01-08 15:20:18 -08:00
Romain de Laage 8329e9b46c Make Invidious instance configurable 2022-01-05 20:43:03 -08:00
dependabot[bot] 5879404fd2 Bump github.com/tdewolff/minify/v2 from 2.9.24 to 2.9.26
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.24 to 2.9.26.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.24...v2.9.26)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-03 16:21:58 -08:00
Jouni K. Seppänen bb0d2bf675 Add Youtube videos in Quanta articles
Some articles (especially the recent year-in-review ones) include a Youtube
video. The server-side rendered articles do not include the Youtube iframe,
but they do have a script that looks like

    <script type="text/javascript" data-reactid="6">
      window.__APOLLO_STATE__ = {
        ...
          youtube_id: "9uASADiYe_8",

We add a reformatting function that tries to detect obvious JavaScript code
that has a field or variable called youtube_id that has an 11-character
double-quoted value, and adds the referenced Youtube videos in the beginning of
the article. This is slightly more general than needed for Quanta, in the hope
that it could be useful for similar sites.
2022-01-03 10:10:13 -08:00
Jouni K. Seppänen dcf87bd642 Add scrape and rewrite rules for quantamagazine
This is a somewhat complex React site so the rules could be a little fragile.
Text content seems to be always inside .outer--content, and most h6 elements
are fluff like "read later" or pointers to other articles. However, h6.byline
and h6.post__title__kicker are relevant to the current article.

Figure captions are sometimes inside both figure and div.outer--content
elements, sometimes only inside figure, so take both and remove the
intersection.

The figure elements sometimes contain multiple copies of images or
videos, and we just take them all. Math articles seem to use Mathjax,
which we don't add.
2022-01-03 10:10:13 -08:00
Alexandros Kosiaris 4445cdd053 Expose entry unshare in the entry and list views
Why:

A user might want to unshare a specific entry. Navigating to the shared
entries page requires a mental context switch, whereas having the
ability right in the entry page makes it easier.

What:

Add an extra <li> element to display the unshare icon and link in the
entry view as well as the item_meta template. The latter is shared for
multiple pages listing entries, e.g. bookmarks, feed entries, search,
history etc.

The functionality already exists for the shared entries page, we are
just expose it in a couple more places

Signed-off-by: Alexandros Kosiaris <akosiaris@gmail.com>
2022-01-03 10:04:48 -08:00
Frédéric Guillot 0b3c3a1f28 Google Reader: Cosmetic improvements 2022-01-02 20:28:12 -08:00
Gergan Penkov 4b6e46d9ab Add Google Reader API implementation (experimental)
Co-authored-by: Sebastian Kempken <sebastian@kempken.io>
Co-authored-by: Gergan Penkov <gergan@gmail.com>
Co-authored-by: Dave Marquard <dave@marquard.org>
Co-authored-by: Moritz Fago <4459068+MoritzFago@users.noreply.github.com>
2022-01-02 19:45:12 -08:00
Frédéric Guillot 2935aaef45 Add Content-Security-Policy header to feed icon url
- SVG images could contains Javascript. This CSP blocks inline script.
- Feed icons are served using <img> tag and Javascript is not interpreted.

See https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_as_an_Image#restrictions
2022-01-02 17:38:53 -08:00
Nikolay Korotkiy 33fd0a617e Add Finnish translation 2021-12-29 21:25:09 -08:00
Jouni K. Seppänen 2fedd8f234 Add scraper rule for ikiwiki.iki.fi
Feed: https://ikiwiki.iki.fi/feed.php?linkto=current&ns=uutiset%3Ablog&num=5

Example page: https://ikiwiki.iki.fi/uutiset/blog/20210923100421viiveita

(To clarify, I'm not a representative of iki.fi although I have an email address in the domain. This is a nonprofit association that offers email forwarding addresses, and the rss feed in question contains news for their members.)
2021-12-27 20:51:37 -08:00
Ilya Mateyko 527c5f49cb packaging: remove SystemCallFilter from miniflux.service
`SystemCallFilter=@system-service` is not supported on older systemd versions and causes crashes on them.

Fixes #1297.
2021-12-23 17:52:13 -08:00
Éric Gaspar 10d2c88e88 Fix minor typo in French translation 2021-12-21 20:28:57 -08:00
dependabot[bot] fd8e95c878 Bump github.com/tdewolff/minify/v2 from 2.9.22 to 2.9.24
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.22 to 2.9.24.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.22...v2.9.24)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-21 20:23:13 -08:00
Frédéric Guillot 686dd92cb9 Update ChangeLog 2021-12-16 17:00:50 -08:00
Thiago Perrotta 28d036434f Add rewrite rule: monkeyuser.com
Comics site, uses alt image text similarly to xkcd.com.
2021-12-16 11:50:26 -08:00
Thiago Perrotta 4b12043cea Sort rewrite rules 2021-12-16 11:50:26 -08:00
Frédéric Guillot 0f6f4c8c60 Add <head> tag to OPML export 2021-12-16 11:49:50 -08:00
Ilya Mateyko 47b47cc32c Tighten systemd sandboxing and update comments in miniflux.service 2021-12-16 10:33:09 -08:00
Arsenović Arsen ec4e8710f2 packaging: add RuntimeDirectory to systemd service 2021-12-15 20:49:02 -08:00
Frédéric Guillot b3dae675d9 Fix regression introduced by PR #1286 2021-12-11 10:42:30 -08:00
Jebbs c1c3624593 Order disabled feeds at the end of the list 2021-12-10 19:56:14 -08:00
Dustin Breuer b21f12015e Add support for theme color based on preferred color scheme of OS 2021-12-10 19:47:43 -08:00
fguillot ca87894ab2 Update issue templates 2021-12-06 17:34:34 -08:00
dependabot[bot] 42dcc52ed1 Bump github.com/lib/pq from 1.10.3 to 1.10.4
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.3 to 1.10.4.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.3...v1.10.4)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-08 16:50:24 -08:00
dependabot[bot] 45bfbb8a2f Bump github.com/PuerkitoBio/goquery from 1.7.1 to 1.8.0
Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.7.1 to 1.8.0.
- [Release notes](https://github.com/PuerkitoBio/goquery/releases)
- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.7.1...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/PuerkitoBio/goquery
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-01 19:44:59 -07:00
nett_hier d6ad9a471b Fix typos in model/icon.go 2021-10-23 21:47:18 -07:00
Artémis b585dab6b4 Add data-srcset support to "add_dynamic_image rewrite" rewrite rule 2021-10-22 18:12:23 -07:00
Emanuele Bernardi d45e1b3bed Fix docker compose example files compatibility to v3 2021-10-22 14:10:41 -07:00
Akash Kakkar 03fd403e38 Added the role="article" to <article> elements for better accessibility with screen readers and chromium based browsers 2021-10-22 13:50:17 -07:00
Frank Steinborn 2dcabc840c Fix minor typo 2021-10-17 16:58:42 -07:00
Frédéric Guillot 87d58987a6 Do not show secrets in plain text on the /about page 2021-10-17 16:56:04 -07:00
Frédéric Guillot 5f9d6fd81b Handle srcset images with no space after comma 2021-10-13 21:31:08 -07:00
Ilya Mateyko c2c9db737b Hide the logout link when using auth proxy
Fixes #1250
2021-10-13 21:30:18 -07:00
Otto Modinos 4565dd406b Fix wrong CSS var 2021-09-28 18:25:23 -07:00
Benjamin 22bbe1ac50 Change -config-dump to use KEY=VALUE format 2021-09-28 18:20:25 -07:00
Frédéric Guillot 72fbbe311a Update ChangeLog 2021-09-25 17:16:03 -07:00
Frédéric Guillot 423e06cbe8 Build RPM and Debian package with PIE mode enabled 2021-09-25 16:16:47 -07:00
Pk11 1f3a9dabc2 Fix templates being visible on old browsers 2021-09-24 16:24:12 -07:00
NobeKanai 937899194d Fix inconsistent navigation in history pages 2021-09-24 16:22:00 -07:00
NobeKanai cf1939f063 Fix inconsistent navigation 2021-09-24 16:22:00 -07:00
dependabot[bot] edee11931d Bump github.com/tdewolff/minify/v2 from 2.9.21 to 2.9.22
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.21 to 2.9.22.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.21...v2.9.22)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-13 16:28:37 -07:00
Frédéric Guillot 612f9cdbc8 Remove RequestURI() hack
I can't remember why this change was done.

Let's use only the standard lib.

But it seems to break URL like this one: https://www.deimeke.net/dirk/blog/index.php?/feeds/index.rss2
2021-09-11 11:08:15 -07:00
NobeKanai fcd18f0b9c Fix translation in zh_CN 2021-09-10 17:34:38 -07:00
James Loh 78f6bbe93d Add ability to change entry sort order in the UI 2021-09-09 19:59:12 -07:00
dependabot[bot] 27d170cbec Bump github.com/lib/pq from 1.10.2 to 1.10.3
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.2...v1.10.3)

---
updated-dependencies:
- dependency-name: github.com/lib/pq
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-07 20:53:54 -07:00
Frédéric Guillot 49119eff00 Minor improvements in integration package 2021-09-07 20:34:47 -07:00
三三 34dd358eb0 Add Telegram integration 2021-09-07 20:04:22 -07:00
Lukas Dietrich 93596c1218 Add rewrite rule to remove dom elements 2021-09-06 09:47:05 -07:00
coxde 9fbcfc213b Update translation in zh_CN
Fix the translation to make it more localized.
2021-09-03 20:20:50 -07:00
hulb 01f678c3b1 add proxy arg in scraper.Fetch 2021-08-28 21:57:11 -07:00
James Loh 2f6895e118 Fix finding JSON feeds with new MIME type
The 1.1 version (https://jsonfeed.org/version/1.1) for JSON feeds defines that feeds should have a MIME type of `application/feed+json` which Miniflux wasn't searching for
2021-08-21 13:01:08 -07:00
Frédéric Guillot b7c229f30f Update scraper rule for theregister.com 2021-08-16 20:04:02 -07:00
Frédéric Guillot b88d46ee7f Add Go 1.17 to GitHub Actions 2021-08-16 19:46:32 -07:00
Jan-Lukas Else b58a46455b Display option to hide feed only when category is not already hidden 2021-08-16 19:36:05 -07:00
Jan-Lukas Else ce6fa4c0fc Fix #1208 2021-08-16 19:36:05 -07:00
Jan-Lukas Else 9965abccfb Add option to hide feeds from the global Unread list 2021-08-16 19:36:05 -07:00
Frédéric Guillot 8bc06a5d65 Update ChangeLog 2021-08-14 19:48:20 -07:00
dependabot[bot] 7f1c17e396 Bump github.com/tdewolff/minify/v2 from 2.9.19 to 2.9.21
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.19 to 2.9.21.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.19...v2.9.21)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-02 19:01:30 -07:00
dependabot[bot] c5594fdae0 Bump mvdan.cc/xurls/v2 from 2.2.0 to 2.3.0
Bumps [mvdan.cc/xurls/v2](https://github.com/mvdan/xurls) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/mvdan/xurls/releases)
- [Commits](https://github.com/mvdan/xurls/compare/v2.2.0...v2.3.0)

---
updated-dependencies:
- dependency-name: mvdan.cc/xurls/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 16:08:50 -07:00
Alexandros Kosiaris b8b16c3bdf Add /rss/ in finder's wellKnownUrls
ATCOM netvolution WCM, probably alongside others, a CMS powering several
high profile and high traffic Greek news sites, among other sites,
publishes the RSS feed under /rss/. Add it to the list. It's generic
enough to allow us to assume other software might do it to

On a select set of 627 Greek news media sites (the infamous Petsas list),
adding this rule increased discoverability of RSS feeds by a factor of
2.61% (from 498 to 511).
2021-07-22 19:46:40 -07:00
Dave Marquard fc766de02d use authors entry for json 1.1 feeds 2021-07-21 21:28:37 -07:00
Alexandros Kosiaris 79810413bd Add Greek translation
Why:
Cause, why not?

What:
Add the JSON file and amend locale.go to use it
2021-07-18 13:46:41 -07:00
dependabot[bot] dc21991599 Bump github.com/tdewolff/minify/v2 from 2.9.18 to 2.9.19
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.18 to 2.9.19.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.18...v2.9.19)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 18:04:42 -07:00
dependabot[bot] cc41065d9b Bump github.com/PuerkitoBio/goquery from 1.7.0 to 1.7.1
Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/PuerkitoBio/goquery/releases)
- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.7.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/PuerkitoBio/goquery
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-12 18:04:15 -07:00
Alexandros Kosiaris e877800779 ui: Expose markCategoryAsRead
Why:
It is nice to have the ability to mark an entire category as read in the
UI. The API already exposes that functionality anyway, so for
consistency reasons, expose it in the UI as well

What:
Add a new handler in the UI to markCategoryAsRead() and amend views and
router to expose the functionality in the UI
2021-07-05 14:13:18 -07:00
Alexandros Kosiaris d5efd776b7 Add "in" in "logged in" for en_US tooltip.logged_user
Fix a typo, by adding the "in" part of the phrasal verb as the intent is
to notify the user that they are "logged" in in the system as user X and
not to notify them that they are "recorded"
2021-07-05 14:08:59 -07:00
dependabot[bot] 164bcc2f4a Bump github.com/tdewolff/minify/v2 from 2.9.17 to 2.9.18
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.17 to 2.9.18.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.17...v2.9.18)

---
updated-dependencies:
- dependency-name: github.com/tdewolff/minify/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-28 20:22:45 -07:00
dependabot[bot] 21ffd79c63 Bump github.com/PuerkitoBio/goquery from 1.6.1 to 1.7.0
Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/PuerkitoBio/goquery/releases)
- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/PuerkitoBio/goquery
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-22 20:59:29 -07:00
Alexandros Kosiaris 638643cda7 client: Try to parse response Body on InternalServerError
Try to parse the response body from the server when an HTTP 500 is
returned (i.e. http.StatusInternalServerError) as it might contain
useful information. If successful, create a new error and append that
information to the returned error message. Otherwise just maintain the
same behavior
2021-06-16 20:48:12 -07:00
Alexandros Kosiaris 6703e03ce6 contrib: Add support for a $MINIFLUX_IMAGE env var in docker-compose
Allowing to override the image used in docker-compose files can allow
for richer and more easy local development/debugging sessions. The
docker image building process is already using the latest tag anyway,
making it really easy to build an image with a (set of) specific
commits. Using the above built image with the provided docker-compose
files isn't doable without modifications though. Add that support via
environmental variables.
2021-06-16 20:34:25 -07:00
Alexandros Kosiaris 56c3f1193f contrib: Bump docker-compose version to 3.4
'start_period' for 'healthchecks' was added in Compose file version 3.4,

https://docs.docker.com/compose/compose-file/compose-versioning/#version-34

Bump docker-compose files' versions from 3.3 to 3.4

Without this, docker-compose (1.25 at least) returns

ERROR: The Compose file './contrib/docker-compose/caddy.yml' is invalid because:
services.db.healthcheck value Additional properties are not allowed ('start_period' was unexpected)
2021-06-16 20:24:13 -07:00
dependabot[bot] d189df95db Bump github.com/prometheus/client_golang from 1.10.0 to 1.11.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.10.0 to 1.11.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.10.0...v1.11.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-14 18:40:44 -07:00
pennae 0bcfc81b1f add option to hide categories from the global unread list 2021-06-12 13:06:30 -07:00
pennae 571d7bf17c Show "saving" labels for entry status button 2021-06-12 13:00:05 -07:00
Frédéric Guillot 93fd98f8d4 Update ChangeLog 2021-06-06 10:03:44 -07:00
Ilya Mateyko 238a3ee9be client: expose comments_url entry field 2021-06-04 22:18:39 -07:00
Frédéric Guillot 897cd7cb34 Use unique file names for cache busting instead of query string 2021-06-02 19:05:32 -07:00
Frédéric Guillot 409d05eec8 Feeds with errors should appear before unread ones 2021-06-02 18:57:57 -07:00
pennae 4b2a25eed4 Highlight and sort feeds with unread entries in feeds list 2021-06-02 14:01:21 -07:00
Frédéric Guillot 1fc95a83b6 Include 'self' in CSP when using custom styles 2021-06-01 15:07:09 -07:00
pennae cc888e2a55 set items to read on click/middle-click of external link 2021-05-31 20:26:18 -07:00
Frédéric Guillot d9f91fd919 Firefox on Windows does not show the active link as bold 2021-05-31 17:04:02 -07:00
Frédéric Guillot dd3f496d06 Avoid extra HTTP request for fetching custom stylesheet
Use inline CSS with a nonce and move CSP headers to a meta tag.
2021-05-31 14:29:33 -07:00
Frédéric Guillot 09be3d2bac Remove invalid CSRF HTML meta tag 2021-05-31 13:54:47 -07:00
Tai 1fd4c4ef13 Add lang attribute to root HTML tag
Allow hyphens css property to work correctly and improve screen readers.
2021-05-31 13:19:37 -07:00
Jan-Lukas Else 20cd023c07 Use runes instead of bytes to truncate JSON feed titles
This fix avoid breaking Unicode string. 

It solves this error:

pq: invalid byte sequence for encoding "UTF8": 0xf0 0x9f 0x9a 0x2e
2021-05-31 11:42:59 -07:00
dependabot[bot] 1655ca235d Bump github.com/tdewolff/minify/v2 from 2.9.16 to 2.9.17
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.16 to 2.9.17.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.16...v2.9.17)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-30 23:43:57 -07:00
James Loh 77dc6aaf27 Expose changed_at time through the API
No issue

This is already stored and updated in the DB to render the History page but is currently not exposed through the API
2021-05-27 20:13:01 -07:00
Loïc Doubinine b4cdef6875 Fix typo in SECURITY.md 2021-05-24 15:23:20 -07:00
Frédéric Guillot 36868e648c Add new config option CLEANUP_ARCHIVE_BATCH_SIZE 2021-05-23 20:52:13 -07:00
Frédéric Guillot c119a2c011 Add new option DATABASE_CONNECTION_LIFETIME 2021-05-23 19:40:33 -07:00
Frédéric Guillot e0557d8961 Add option to disable watchdog and set default timeout to DB healthcheck 2021-05-23 18:30:20 -07:00
Frédéric Guillot 36d3b1e9fb Add database stats to Prometheus exporter 2021-05-22 20:31:49 -07:00
Frédéric Guillot 012eb61c52 Tweak watchdog 2021-05-22 20:25:38 -07:00
Frédéric Guillot c4a56105ca Add Systemd watchdog 2021-05-22 18:46:15 -07:00
Frédéric Guillot 1005fb973e Avoid custom stylesheet to be cached by third-party CDN
If the application is hosted behind a CDN like Cloudflare,
then all custom stylesheets is be the same for all users.

The random query string prevent the CDN to cache this.
2021-05-21 14:03:45 -07:00
NobeKanai 75ac58abdf Update a shared entry label translation in zh_CN 2021-05-20 07:29:44 -07:00
dependabot[bot] e46b438b06 Bump github.com/lib/pq from 1.10.1 to 1.10.2
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.1...v1.10.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-19 22:47:40 -07:00
Edward Betts 264f4db567 Correct spelling 2021-05-13 18:22:23 -07:00
Frédéric Guillot 0c56b4d580 Update ChangeLog 2021-05-07 16:35:08 -07:00
Frédéric Guillot 32439ca2f0 Security fix: any user can delete any feed
Regression introduced in commit 51fb949.
2021-05-07 16:25:44 -07:00
jacekk fa49bcaf8b Fix password reset via CLI 2021-05-02 20:34:33 -07:00
Frédéric Guillot 89e8e94bf1 Increase default batch size value 2021-04-30 23:02:45 -07:00
Frédéric Guillot 5b8eb4735c Handle RSS feed title with encoded Unicode entities 2021-04-30 22:57:29 -07:00
pennae 1c9f000576 show #unread per category in category list, not #feeds
the number of feeds in the category is currently displayed twice, and a lot less
useful than the number of unread items in the category.
2021-04-26 18:49:32 -07:00
dependabot[bot] de53d8762f Bump github.com/lib/pq from 1.10.0 to 1.10.1
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.10.0 to 1.10.1.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.10.0...v1.10.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-25 23:15:20 -07:00
野辺かない daa56efe27 Fix filtering doesn't work when selecting from multiple found feeds 2021-04-25 21:06:39 -07:00
dependabot[bot] f1db010a68 Bump github.com/tdewolff/minify/v2 from 2.9.15 to 2.9.16
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.15 to 2.9.16.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.15...v2.9.16)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-19 20:26:01 -07:00
Vincent Bernat dbb5906767 Use an appropriate color for visited links on dark theme
The contrast between background and purple for visited links was too
low (1.69). Use a brighter purple for the dark theme (contrast 6.87).
2021-04-14 21:28:31 -07:00
yue 18e414ec45 Fix typo in reader/json/doc.go 2021-04-02 19:00:06 -07:00
fguillot fc00a1b3a8 Create SECURITY.md 2021-03-30 20:51:34 -07:00
Benedikt Hopmann 556b74f4fc Update de_DE.json 2021-03-28 17:22:23 -07:00
Frédéric Guillot 6e2e2d1665 Setup golangci-lint Github Action 2021-03-22 21:34:48 -07:00
Darius 9242350f0e Add per feed cookies option 2021-03-22 20:27:58 -07:00
dependabot[bot] b0c14aa8f9 Bump github.com/prometheus/client_golang from 1.9.0 to 1.10.0
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.9.0...v1.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 17:25:02 -07:00
dependabot[bot] 4b2516532e Bump github.com/tdewolff/minify/v2 from 2.9.13 to 2.9.15
Bumps [github.com/tdewolff/minify/v2](https://github.com/tdewolff/minify) from 2.9.13 to 2.9.15.
- [Release notes](https://github.com/tdewolff/minify/releases)
- [Commits](https://github.com/tdewolff/minify/compare/v2.9.13...v2.9.15)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-22 17:24:36 -07:00
Frédéric Guillot 1aec8d727c Update ChangeLog 2021-03-21 11:29:24 -07:00
Frédéric Guillot e60e0ba3c4 Add workaround to handle some invalid dates 2021-03-21 10:52:27 -07:00
Frédéric Guillot 5877048749 Improve handling of Atom text content with CDATA 2021-03-20 20:47:35 -07:00
Frédéric Guillot c8c1f05328 Add better support of Atom text constructs
- Note that Miniflux does not render entry title with HTML tags as of now
- Omit XHTML div element because it should not be part of the content
2021-03-19 22:05:00 -07:00
Frédéric Guillot 96f3e888cf Handle RDF feed with HTML encoded entry title
Example: http://rss.slashdot.org/Slashdot/slashdotMain
2021-03-19 18:49:51 -07:00
Frédéric Guillot 14888f1cb8 Fix incorrect parsing of Atom entry content of type HTML 2021-03-18 21:43:59 -07:00
Taylan Tatlı 49171c5e8c Turkish language 2021-03-17 21:17:03 -07:00
Frédéric Guillot 51fb9495ad Improve large feed deletion
First patch to avoid the app hanging when deleting large feeds
2021-03-16 22:22:50 -07:00
Ilya Mateyko 89c1b3b4d8 Systemd readiness notification
This change implements the systemd readiness notification, using
the sd_notify protocol.

See https://www.freedesktop.org/software/systemd/man/sd_notify.html.
2021-03-16 20:14:43 -07:00
Gabriel Augendre 1d80c12e18 Prevent Youtube scraping if entry already exists 2021-03-08 20:10:53 -08:00
dependabot[bot] d092fc8436 Bump github.com/lib/pq from 1.9.0 to 1.10.0
Bumps [github.com/lib/pq](https://github.com/lib/pq) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/lib/pq/releases)
- [Commits](https://github.com/lib/pq/compare/v1.9.0...v1.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-07 23:01:21 -08:00
Frédéric Guillot 8a812cd8ec Add basic PWA offline page
- Remove feed_icons cache because it's causing more problems that it solve.
- Add basic offline mode when using the service worker.
- Starting in Chrome 93, offline mode is going to be a requirement to install the PWA.

https://developer.chrome.com/blog/improved-pwa-offline-detection/#enforcement-starting-chrome-93-august-2021
2021-03-07 15:44:42 -08:00
Frédéric Guillot ae13b4e420 Replace icon for "Add to home screen" button 2021-03-07 12:07:54 -08:00
Frédéric Guillot 548c4d4efe Use SVG icons for toast notifications 2021-03-07 12:03:43 -08:00
Frédéric Guillot f6ed2feab4 Use SVG sprite for icons 2021-03-07 11:18:42 -08:00
1pav b730aa520d Reset scroll position on mark page as read 2021-03-06 18:08:13 -08:00
Alexandros Kosiaris 3d16c3e4e6 Add link to mark all feed entries as read 2021-03-06 17:45:25 -08:00
1pav 0d935a863f Make web app display mode configurable
The change is visible after reinstalling the web app. 

It's not compatible with all browsers.

See https://developer.mozilla.org/en-US/docs/Web/Manifest/display
2021-02-28 13:29:51 -08:00
hykhd 053b1d0f8d Handle RSS feeds with CDATA in author item element 2021-02-28 12:26:52 -08:00
Gabriel Augendre b247f3f089 Add read time on article page 2021-02-24 20:06:11 -08:00
Frédéric Guillot 20ec435d6d Move healthcheck from Dockerfile to docker-compose because it's optional 2021-02-22 21:47:30 -08:00
Frédéric Guillot 53cd369b05 Avoid showing a broken image when there is no feed icon 2021-02-22 21:15:08 -08:00
Frédéric Guillot ec3c604a83 Add option to allow self-signed or invalid certificates 2021-02-21 13:58:52 -08:00
Ilya Mateyko c3f871b49b Use YouTube video duration as read time
This feature works by scraping YouTube website.

To enable it, set the FETCH_YOUTUBE_WATCH_TIME environment variable to
1.

Resolves #972.
2021-02-21 11:13:52 -08:00
Gabriel Augendre e5b2eab727 Send full article content to wallabag 2021-02-21 11:05:29 -08:00
Frédéric Guillot bbf93430b7 Add more extensive healthcheck support
- Add new cli argument: -healthcheck
- Add HEALTHCHECK instruction to Dockerfile
- Update Docker Compose examples
2021-02-20 12:58:04 -08:00
hykhd 3cb04b2c56 update whitelist fix bilibili video 2021-02-20 10:29:42 -08:00
Frédéric Guillot e3c28a6c96 Improve health check endpoint to test database connection 2021-02-19 19:32:13 -08:00
Frédéric Guillot c2571f9f47 Remove completely generated files 2021-02-18 21:50:27 -08:00
Frédéric Guillot 4855fbd13f Use embed package for Javascript bundles instead of generated files 2021-02-18 20:49:06 -08:00
Frédéric Guillot 9569666259 Use embed package for CSS bundles instead of generated files 2021-02-17 22:07:28 -08:00
Frédéric Guillot 42edd357bc Add pull-request template 2021-02-17 20:54:39 -08:00
fguillot b33706006a Update issue templates 2021-02-17 20:46:19 -08:00
Frédéric Guillot 0de80c2ff0 Use embed package for binary assets instead of generated files 2021-02-17 20:15:28 -08:00
Frédéric Guillot 5d65a85bdb Use embed package for translations instead of generated files
Replace "go generate" with the new embed package.
2021-02-16 23:09:01 -08:00
Frédéric Guillot a352aff93b Remove deprecated io/ioutil package
Miniflux now requires at least Go 1.16 and io/util is deprecated.

https://golang.org/doc/go1.16#ioutil
2021-02-16 21:25:21 -08:00
Frédéric Guillot 713d575bad Update CI check for Go 1.16 2021-02-16 21:13:13 -08:00
Rogier Lommers 421bb467d0 Show Postgres version in about page 2021-02-16 07:37:24 -08:00
Frédéric Guillot dde2ca06c3 Update ChangeLog 2021-02-15 20:34:19 -08:00
Frédéric Guillot 091308787b Add header "Referrer-Policy: no-referrer"
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
2021-02-14 11:33:31 -08:00
Frédéric Guillot 04f9c456d5 Handle entry title with double encoded entities in Atom feeds 2021-02-14 11:19:21 -08:00
Frédéric Guillot 6b7ffe0e26 Add Open Containers annotations to Docker image 2021-02-13 16:27:23 -08:00
Frédéric Guillot f29940d784 Rename storage certificate cache
This cache is used only for ACME certificates.

Being explicit is always better.
2021-02-13 14:16:30 -08:00
Frédéric Guillot 0413daf76b Remove iframe inner HTML contents
An iframe element never has fallback content, as it will always create a nested
browsing context, regardless of whether the specified initial contents are
successfully used.

https://www.w3.org/TR/2010/WD-html5-20101019/the-iframe-element.html#the-iframe-element
2021-02-13 14:00:21 -08:00
Frédéric Guillot 5043749b9f Add workaround for entry title with double encoded entities
Example: &amp;#39;Text&amp;#39;
2021-02-13 13:33:59 -08:00
Nick Chitwood 793f475edd Update date parser to fix another time zone issue
The Washington Post has its feeds with EST, which is getting parsed by miniflux as UTC, and showing up as 8 hours off.

See http://feeds.washingtonpost.com/rss/politics for an example.

This fix applies a similar workaround for EST/EDT as was done for PST/PDT.
2021-02-10 22:45:02 -08:00
Benedikt Hopmann 03cfbfe4ac Update german translation for blocklist and keeplist 2021-02-08 16:32:07 -08:00
Frédéric Guillot e8d0360e64 Validate Keep list and Block list rules syntax 2021-02-07 18:53:56 -08:00
y0ast 05fd83bd6f add support for ipv6 with zone index 2021-02-07 15:57:40 -08:00
Frédéric Guillot 864dd9f219 Allow images with data URLs
Only URLs with a mime-type image/* are allowed
2021-02-06 14:46:01 -08:00
Frédéric Guillot 9a9a271b1f Limit full-text search indexation to first 500K characters
tsvector has a size limit of 1MB. See https://www.postgresql.org/docs/13/textsearch-limitations.html

Input text is now truncated to avoid this error:

"pq: string is too long for tsvector (1057834 bytes, max 1048575 bytes)"
2021-02-06 14:10:45 -08:00
Frédéric Guillot 89d17107af Change PWA display mode to standalone 2021-02-06 11:54:17 -08:00
Frédéric Guillot b6ddaae82a ETag value is not set correctly in HTTP client (regression)
Bug introduced after refactoring.

See commit 16b7b3bc3e.
2021-02-05 20:36:05 -08:00
Dave Marquard 0bece2df7d Database backed LetsEncrypt certificate cache (#993) 2021-01-29 18:44:40 -08:00
Ilya Mateyko 4464802947 Reformat some Go files
When working on #994 I noticed that some Go files are not formatted with
`gofmt`.

This PR fixes this.
2021-01-27 18:13:58 -08:00
Shizun Ge 7c44238bae Add global option POLLING_PARSING_ERROR_LIMIT 2021-01-25 21:41:36 -08:00
Ilya Mateyko b45c1cf327 Use systemctl edit for editing systemd config file
This is safer than directly editing the package-supplied version.

See https://wiki.archlinux.org/index.php/Systemd#Editing_provided_units.
2021-01-25 21:23:45 -08:00
Frédéric Guillot f1d7385294 Update Go version to 1.15 in go.mod 2021-01-24 11:50:37 -08:00
Kiskae 77f5b911b5 Don't discard the "Fetch via Proxy" option 2021-01-24 11:24:28 -08:00
Shizun Ge f8ca825524 Update go.mod 2021-01-24 11:06:52 -08:00
Frédéric Guillot 6377f3a8b3 Fix regression introduced by PR #973
The relation "entries_user_feed_idx" already exists
2021-01-24 11:00:05 -08:00
Shizun Ge 533d5dfc98 Update man page to show the default values 2021-01-21 23:16:05 -08:00
Patrick Marschik 1ed3816d6f PostgreSQL index optimizations 2021-01-21 23:02:01 -08:00
Shizun Ge 4ff52bd730 Add API endpoints to get feeds and entries of a category 2021-01-18 19:44:02 -08:00
Shizun Ge 02a4c9db53 Create feed query builder 2021-01-18 13:22:09 -08:00
dependabot[bot] 433de17562 Bump github.com/PuerkitoBio/goquery from 1.6.0 to 1.6.1
Bumps [github.com/PuerkitoBio/goquery](https://github.com/PuerkitoBio/goquery) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/PuerkitoBio/goquery/releases)
- [Commits](https://github.com/PuerkitoBio/goquery/compare/v1.6.0...v1.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 11:04:11 -08:00
Shizun Ge 23e4183bec Show global options in the about page
Only shows the options when current user is admin.
2021-01-18 11:02:40 -08:00
Shizun Ge 926ddf6d09 Update man page to mention -1 can be used for CLEANUP_ARCHIVE_* options 2021-01-16 18:00:29 -08:00
496 changed files with 19175 additions and 12347 deletions
+23
View File
@@ -0,0 +1,23 @@
{
"name": "Miniflux",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspace",
"remoteUser": "vscode",
"forwardPorts": [
8080
],
"customizations": {
"vscode": {
"settings": {
"go.toolsManagement.checkForUpdates": "local",
"go.useLanguageServer": true,
"go.gopath": "/go"
},
"extensions": [
"ms-azuretools.vscode-docker",
"golang.go"
]
}
}
}
+27
View File
@@ -0,0 +1,27 @@
version: '3.8'
services:
app:
image: mcr.microsoft.com/devcontainers/go
volumes:
- ..:/workspace:cached
command: sleep infinity
network_mode: service:db
environment:
- CREATE_ADMIN=1
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=test123
db:
image: postgres:15
restart: unless-stopped
volumes:
- postgres-data:/var/lib/postgresql/data
hostname: postgres
environment:
POSTGRES_DB: miniflux2
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
volumes:
postgres-data: null
+10
View File
@@ -0,0 +1,10 @@
---
name: Bug report
about: Create a bug report
title: ''
labels: bug, triage needed
assignees: ''
---
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+10
View File
@@ -0,0 +1,10 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: feature request
assignees: ''
---
- [ ] I have read this document: https://miniflux.app/opinionated.html#feature-request
+10
View File
@@ -0,0 +1,10 @@
---
name: Feed Problems
about: Problems with a feed or a website
title: ''
labels: feed problems, triage needed
assignees: ''
---
+10
View File
@@ -0,0 +1,10 @@
---
name: Improvement
about: Do you have an idea to improve an existing feature?
title: ''
labels: improvements
assignees: ''
---
+2
View File
@@ -0,0 +1,2 @@
paths-ignore:
- ./http/client/testdata
+18
View File
@@ -2,6 +2,15 @@ version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"
reviewers:
- "fguillot"
assignees:
- "fguillot"
- package-ecosystem: "docker"
directory: "/packaging/docker/alpine"
schedule:
interval: "weekly"
reviewers:
@@ -10,6 +19,15 @@ updates:
- "fguillot"
- package-ecosystem: "docker"
directory: "/packaging/docker/distroless"
schedule:
interval: "weekly"
reviewers:
- "fguillot"
assignees:
- "fguillot"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
+4
View File
@@ -0,0 +1,4 @@
Do you follow the guidelines?
- [ ] I have tested my changes
- [ ] I read this document: https://miniflux.app/faq.html#pull-request
+10 -50
View File
@@ -1,40 +1,12 @@
name: CI Workflow
permissions: read-all
on:
pull_request:
branches:
- master
- main
jobs:
linters:
name: Linter Check
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install linters
run: |
cd /tmp && go get -u golang.org/x/lint/golint
sudo npm install -g jshint
env:
GO111MODULE: off
- name: Run golint
run: |
export PATH=/home/runner/go/bin:$PATH
make lint
- name: Run jshint
run: jshint ui/static/js/*.js
unit-tests:
name: Unit Tests
runs-on: ${{ matrix.os }}
@@ -42,20 +14,14 @@ jobs:
max-parallel: 4
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
go-version: [1.14, 1.15]
go-version: ["1.19", "1.20"]
steps:
- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/checkout@v3
- name: Run unit tests
run: make test
@@ -74,19 +40,13 @@ jobs:
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: 1.15
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
go-version: "1.20"
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
uses: actions/checkout@v3
- name: Install Postgres client
run: sudo apt-get install -y postgresql-client
run: sudo apt update && sudo apt install -y postgresql-client
- name: Run integration tests
run: make integration-test
env:
+42
View File
@@ -0,0 +1,42 @@
name: "CodeQL"
permissions: read-all
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '45 22 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go', 'javascript' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+76 -16
View File
@@ -5,53 +5,113 @@ on:
push:
tags:
- '*.*.*'
pull_request:
branches: [ main ]
jobs:
docker-images:
test-docker-images:
if: github.event.pull_request
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Build Alpine image
uses: docker/build-push-action@v4
with:
context: .
file: ./packaging/docker/alpine/Dockerfile
push: false
tags: ${{ github.repository_owner }}/miniflux:alpine-dev
- name: Test Alpine Docker image
run: docker run --rm ${{ github.repository_owner }}/miniflux:alpine-dev miniflux -i
- name: Build Distroless image
uses: docker/build-push-action@v4
with:
context: .
file: ./packaging/docker/distroless/Dockerfile
push: false
tags: ${{ github.repository_owner }}/miniflux:distroless-dev
- name: Test Distroless Docker image
run: docker run --rm ${{ github.repository_owner }}/miniflux:distroless-dev miniflux -i
publish-docker-images:
if: ${{ ! github.event.pull_request }}
permissions:
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Generate Docker tag
id: docker_tag
- name: Generate Alpine Docker tag
id: docker_alpine_tag
run: |
DOCKER_IMAGE=miniflux/miniflux
DOCKER_IMAGE=${{ github.repository_owner }}/miniflux
DOCKER_VERSION=dev
if [ "${{ github.event_name }}" = "schedule" ]; then
DOCKER_VERSION=nightly
TAGS="${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
elif [[ $GITHUB_REF == refs/tags/* ]]; then
DOCKER_VERSION=${GITHUB_REF#refs/tags/}
TAGS="${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},${DOCKER_IMAGE}:latest,ghcr.io/${DOCKER_IMAGE}:latest"
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION},docker.io/${DOCKER_IMAGE}:latest,ghcr.io/${DOCKER_IMAGE}:latest,quay.io/${DOCKER_IMAGE}:latest"
fi
echo ::set-output name=tags::${TAGS}
- name: Generate Distroless Docker tag
id: docker_distroless_tag
run: |
DOCKER_IMAGE=${{ github.repository_owner }}/miniflux
DOCKER_VERSION=dev-distroless
if [ "${{ github.event_name }}" = "schedule" ]; then
DOCKER_VERSION=nightly-distroless
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION}"
elif [[ $GITHUB_REF == refs/tags/* ]]; then
DOCKER_VERSION=${GITHUB_REF#refs/tags/}-distroless
TAGS="docker.io/${DOCKER_IMAGE}:${DOCKER_VERSION},ghcr.io/${DOCKER_IMAGE}:${DOCKER_VERSION},quay.io/${DOCKER_IMAGE}:${DOCKER_VERSION},docker.io/${DOCKER_IMAGE}:latest-distroless,ghcr.io/${DOCKER_IMAGE}:latest-distroless,quay.io/${DOCKER_IMAGE}:latest-distroless"
fi
echo ::set-output name=tags::${TAGS}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.CR_PAT }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
- name: Login to Quay Container Registry
uses: docker/login-action@v2
with:
registry: quay.io
username: ${{ secrets.QUAY_USERNAME }}
password: ${{ secrets.QUAY_TOKEN }}
- name: Build and Push Alpine images
uses: docker/build-push-action@v4
with:
context: .
file: ./packaging/docker/Dockerfile
file: ./packaging/docker/alpine/Dockerfile
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
push: true
tags: ${{ steps.docker_tag.outputs.tags }}
tags: ${{ steps.docker_alpine_tag.outputs.tags }}
- name: Build and Push Distroless images
uses: docker/build-push-action@v4
with:
context: .
file: ./packaging/docker/distroless/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_distroless_tag.outputs.tags }}
+31
View File
@@ -0,0 +1,31 @@
name: Linters
permissions: read-all
on:
pull_request:
branches:
- main
jobs:
jshint:
name: Javascript Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install jshint
run: |
sudo npm install -g jshint@2.13.3
- name: Run jshint
run: jshint ui/static/js/*.js
golangci:
name: Golang Linter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: "1.20"
- uses: golangci/golangci-lint-action@v3
with:
args: --timeout 10m --skip-dirs tests --disable errcheck --enable sqlclosecheck --enable misspell --enable gofmt --enable goimports --enable whitespace
+46
View File
@@ -0,0 +1,46 @@
name: Debian and RPM Package Builders
permissions: read-all
on:
push:
tags:
- '*.*.*'
jobs:
debian-package-builder:
name: Build Debian Packages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
id: buildx
with:
install: true
- name: Available Docker Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Build Debian Packages
run: make debian-packages
- name: List generated files
run: ls -l *.deb
- name: Upload packages to repository
env:
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
run: for f in *.deb; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
rpm-package-builder:
name: Build RPM Package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Build RPM Package
run: make rpm
- name: List generated files
run: ls -l *.rpm
- name: Upload package to repository
env:
FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
run: for f in *.rpm; do curl -F package=@$f https://$FURY_TOKEN@push.fury.io/miniflux/; done
+2
View File
@@ -2,3 +2,5 @@ miniflux-*
miniflux
*.rpm
*.deb
.idea
.vscode
+459 -1
View File
@@ -1,3 +1,461 @@
Version 2.0.46 (July 21, 2023)
------------------------------
* Add scraper and rewrite rules for Webtoons
* Fix regression in integration page and simplify SQL query
* Wallabag integration: add more information in log messages
* Add support for custom Youtube embed URL
* Fix accessibility issues in modal component
* Fix modal aria role
* Trap focusing with tab / shift+tab inside the modal
* Restore keyboard focus when closing modal
* Automatically move keyboard focus to first focusable element unless specified otherwise
* Keyboard shortcut help modal: move keyboard focus to modal title
* Keyboard shortcut help modal: change close control from link to button
* Add Notion integration
* Update `golang.org/x/*` dependencies and `go-oidc` to v3.6.0
* Improve responsive design
* Add user setting for marking entry as read on view
* Improve Russian translation
* Add the possibility to run cleanup tasks from the command line
* Add the possibility to run Miniflux as a cronjob
* Use `go-httpbin` to run tests locally and avoid remote calls to `httpbin.org`
* Display tags when viewing entries
* Update categories API endpoint to return `total_unread` and `feed_count`
* Improve date parser to handle various broken date formats
* Avoid `pq: time zone displacement out of range` errors
* Improve entry existance check to make better use of index
* Add unique index `enclosures_user_entry_url_idx`
* Add mark as unread for Linkding integration
* Add sub-folder support for Wallabag integration
* Use RockyLinux to build RPM package
* Disable CGO when building RPM package
* Disable CGO when building Docker images
Version 2.0.45 (June 21, 2023)
------------------------------
* Add media player to listen to audio and video podcasts with the possiblity to resume to last playback position
* Add default tag names for Linkding integration
* Mark only globally visible entries when marking all entries from UI
* Use image included in feed as feed icon when available
* Order history by `changed_at` and `published_at`
* Remove title attribute from entry title links
* Fix reading time that is not aligned correctly with the latest version of Safari
* Use glyphs of the same size on keyboard shortcuts page
* Add maskable versions of the PWA icon
* Replace copyright header with SPDX identifier
* Remove the "í" letter from the Portuguese "lido" word
* Increase golangci-lint timeout value
* Bump `github.com/tdewolff/minify/v2`, `github.com/prometheus/client_golang`, `golang.org/x/*` dependencies
Version 2.0.44 (May 6, 2023)
----------------------------
* Add link to the URL rewrite rules documentation
* Update scraping rules for `ilpost.it`
* Update rewrite rules for `theverge.com`
* Add a rewrite rule to remove clickbait titles
* Make sure `PROXY_IMAGES` option is backward compatible with `PROXY_OPTION` and `PROXY_MEDIA_TYPES`
* Add new rule to remove tables
* Add support for searching well-known URLs in subdirectory
* Add CSS `word-wrap` rule to break very long entry title into multiple lines
* Add swipe as option for gesture navigation between entries. There are now 3 possible choices: `none`, `double-tap`, and `swipe`.
* Prefer typographic punctuation in English translation
* Process older entries first:
- Feed entries are usually ordered from most to least recent.
- Processing older entries first ensures that their creation timestamp
is lower than that of newer entries.
- This is useful when we order by creation, because then we get a
consistent timeline.
* Fix Grafana dashboard
* Push Docker images to `Quay.io` (RedHat)
* Bump `golang.org/x/*`, `github.com/lib/pq`, `mvdan.cc/xurls/v2` and `github.com/prometheus/client_golang` dependencies
Version 2.0.43 (March 16, 2023)
-------------------------------
* Avoid XSS when opening a broken image due to unescaped ServerError in proxy handler (CVE-2023-27592)
Creating an RSS feed item with the inline description containing an `<img>` tag
with a `srcset` attribute pointing to an invalid URL like
`http:a<script>alert(1)</script>`, we can coerce the proxy handler into an error
condition where the invalid URL is returned unescaped and in full.
This results in JavaScript execution on the Miniflux instance as soon as the
user is convinced to open the broken image.
* Use `r.RemoteAddr` to check `/metrics` endpoint network access (CVE-2023-27591)
HTTP headers like `X-Forwarded-For` or `X-Real-Ip` can be easily spoofed. As
such, it cannot be used to test if the client IP is allowed.
The recommendation is to use HTTP Basic authentication to protect the
metrics endpoint, or run Miniflux behind a trusted reverse-proxy.
* Add HTTP Basic authentication for `/metrics` endpoint
* Add proxy support for several media types
* Parse feed categories from RSS, Atom and JSON feeds
* Ignore empty link when discovering feeds
* Disable CGO explicitly to make sure the binary is statically linked
* Add CSS classes to differentiate between category/feed/entry view and icons
* Add rewrite and scraper rules for `blog.cloudflare.com`
* Add `color-scheme` to themes
* Add new keyboard shortcut to toggle open/close entry attachments section
* Sanitizer: allow `id` attribute in `<sup>` element
* Add Indonesian Language
* Update translations
* Update Docker Compose examples:
- Run the application in one command
- Bring back the health check condition to `depends_on`
- Remove deprecated `version` element
* Update scraping rules for `ilpost.it`
* Bump `github.com/PuerkitoBio/goquery` from `1.8.0` to `1.8.1`
* Bump `github.com/tdewolff/minify/v2` from `2.12.4` to `2.12.5`
* Bump `github.com/yuin/goldmark` from `1.5.3` to `1.5.4`
* Bump `golang.org/x/*` dependencies
Version 2.0.42 (January 29, 2023)
---------------------------------
* Fix header items wrapping
* Add option to enable or disable double tap
* Improve PWA display mode label in settings page
* Bump `golang.org/x/*` dependencies
* Update translations
* Add scraping rule for `ilpost.it`
* Update reading time HTML element after fetching the original web page
* Add category feeds refresh feature
Version 2.0.41 (December 10, 2022)
----------------------------------
* Reverted PR #1290 (follow the only link) because it leads to several panics/segfaults that prevent feed updates
* Disable double-tap mobile gesture if swipe gesture is disabled
* Skip integrations if there are no entries to push
* Enable TLS-ALPN-01 challenge for ACME
- This type of challenge works purely at the TLS layer and is compatible
with SNI proxies. The existing HTTP-01 challenge support has been left
as-is.
* Preconfigure Miniflux for GitHub Codespaces
* Updated `golang.org/x/net/*` dependencies
Version 2.0.40 (November 13, 2022)
----------------------------------
* Update dependencies
* Pin Postgres image version in Docker Compose examples to avoid unexpected upgrades
* Make English and Spanish translation more consistent:
- Use "Feed" everywhere instead of "Subscription"
- Use "Entry" instead of "Article"
* Allow Content-Type and Accept headers in CORS policy
* Use dirs file for Debian package
* Use custom home page in PWA manifest
* Fix scraper rule that could be incorrect when there is a redirect
* Improve web scraper to fetch the only link present as workaround to some landing pages
* Add Matrix bot integration
* Proxify images in API responses
* Add new options in user preferences to configure sorting of entries in the category page
* Remove dependency on `github.com/mitchellh/go-server-timing`
* Add support for the `continuation` parameter and result for Google Reader API ID calls
* Use automatic variable for build target file names
* Add rewrite rule for `recalbox.com`
* Improve Dutch translation
Version 2.0.39 (October 16, 2022)
---------------------------------
* Add support for date filtering in Google Reader API item ID calls
* Handle RSS entries with only a GUID permalink
* Go API Client: Accept endpoint URLs ending with `/v1/`
* CORS API headers: Allow `Basic` authorization header
* Log feed URL when submitting a subscription that returns an error
* Update `make run` command to execute migrations automatically
* Add option to send only the URL to Wallabag
* Do not convert anchors to absolute links
* Add config option to use a custom image proxy URL
* Allow zoom on mobile devices
* Add scraping rules for `theverge.com`, `royalroad.com`, `swordscomic.com`, and `smbc-comics.com`
* Add Ukrainian translation
* Update `golang.org/x/*` dependencies
* Bump `github.com/tdewolff/minify/v2` from `2.12.0` to `2.12.4`
* Bump `github.com/yuin/goldmark` from `1.4.13` to `1.5.2`
* Bump `github.com/lib/pq` from `1.10.6` to `1.10.7`
Version 2.0.38 (August 13, 2022)
--------------------------------
* Rename default branch from master to main
* Update GitHub Actions
* Bump `github.com/prometheus/client_golang` from `1.12.2` to `1.13.0`
* Fix some linter issues
* Handle Atom links with a text/html type defined
* Add `parse_markdown` rewrite function
* Build RPM and Debian packages automatically using GitHub Actions
* Add `explosm.net` scraper rule
* Make default home page configurable
* Add title attribute to entry links because text could be truncated
* Highlight categories with unread entries
* Allow option to order by title and author in API entry endpoint
* Update Russian translation
* Make reading speed user-configurable
* Added translation for Hindi language used in India
* Add rewrite rules for article URL before fetching content
* Bump `github.com/tdewolff/minify/v2` from `2.11.7` to `2.12.0`
* Support other repo owners in GitHub Docker Action
* Proxify empty URL should not crash
* Avoid stretched image if specified width is larger than Miniflux's layout
* Add support for OPML files with several nested outlines
* sanitizer: handle image URLs in `srcset` attribute with comma
* Allow `width` and `height` attributes for `img` tags
* Document that `-config-dump` command line argument shows sensitive info
* Add System-V init service in contrib folder
* Fix syntax error in `RequestBuilder.getCsrfToken()` method
Version 2.0.37 (May 27, 2022)
-----------------------------
* Add rewrite rule to decode base64 content
* Add Linkding integration
* Add comment button to Telegram message
* Add API endpoint to fetch unread and read counters
* Fixes logic bug in Google Reader API sanity check
* Reduce number of CORS preflight check to save network brandwidth
* Add Espial integration
* Allow API search for entries which are not starred
* Try to use outermost element text when title is empty
* Make swipe gestures feel more natural
- Removes opacity transition when swiping an article read/unread
- Adds "resistance" to the swiped entry when the 75px threshold is
reached
- Fixes an issue in which a swiped article couldn't be moved <15px
* Add support for feed streams to Google Reader API IDs API
* Fix invalid parsing of icon data URL
* Add Traditional Chinese translation
* Add distroless Docker image variant
* Add Go 1.18 to GitHub Action
* Bump `github.com/tdewolff/minify/v2` from `2.10.0` to `2.11`
* Bump `github.com/prometheus/client_golang` from `1.12.1` to `1.12.2`
* Bump `github.com/lib/pq` from `1.10.4` to `1.10.6`
Version 2.0.36 (March 8, 2022)
------------------------------
* Gray out pagination buttons when they are not applicable
* Use truncated entry description as title if unavailable
* Do not fallback to InnerXML if XHTML title is empty
* Add `+` keyboard shortcut for new subscription page
* Add `(+)` action next to Feeds to quickly add new feeds
* Fix unstar not working via Google Reader API
* Remove circles in front of page header list items
* Fix CSS hover style for links styled as buttons
* Avoid showing `undefined` when clicking on read/unread
* Add new keyboard shortcut `M` to toggle read/unread, and go to previous item
* Add several icons to menus according to their roles
* Add missing event argument to `onClick()` function call
* Add links to scraper/rewrite/filtering docs when editing feeds
* Add a rewrite rule for Castopod episodes
* Fix regression: reset touch-item if not in `/unread` page
* Add API endpoint to fetch original article
* Show the category first in feed settings
* Add pagination on top of all entries
* Display Go version in "About" page
* Bump `mvdan.cc/xurls/v2` from 2.3.0 to 2.4.0
* Bump `github.com/prometheus/client_golang` from 1.11.0 to 1.12.1
* Bump `github.com/tdewolff/minify/v2` from 2.9.28 to 2.10.0
Version 2.0.35 (January 21, 2022)
---------------------------------
* Set `read-all` permission to `GITHUB_TOKEN` for GitHub Actions
* Pin `jshint` version in linter job
* Fix incorrect conversion between integer types
* Add new GitHub Actions workflows: CodeQL and Scorecards analysis
* Handle Atom feeds with space around CDATA
* Bump `github.com/tdewolff/minify/v2` from 2.9.22 to 2.9.28
* Add Documentation directive to Systemd service
* Do not reset `touch-item` if successfully swiped
* Add support for multiple authors in Atom feeds
* Omit `User-Agent` header in image proxy to avoid being blocked
* Use custom feed user agent to fetch website icon
* Make default Invidious instance configurable
* Add new rewrite rule `add_youtube_video_from_id` to add Youtube videos in Quanta articles
* Add scrape and rewrite rules for `quantamagazine.org`
* Expose entry unshare link in the entry and list views
* Add Google Reader API implementation (experimental)
* Add `Content-Security-Policy` header to feed icon and image proxy endpoints
- SVG images could contain Javascript. This CSP blocks inline script.
- Feed icons are served using `<img>` tag and Javascript is not interpreted.
* Add Finnish translation
* Add scraper rule for `ikiwiki.iki.fi`
* Remove `SystemCallFilter` from `miniflux.service`
* Fix minor typo in French translation
Version 2.0.34 (December 16, 2021)
----------------------------------
* Add rewrite rule for comics website http://monkeyuser.com
* Add `<head>` tag to OPML export
* Tighten Systemd sandboxing and update comments in `miniflux.service`
* Add `RuntimeDirectory` to Systemd service
* Order disabled feeds at the end of the list
* Add support for theme color based on preferred color scheme of OS
* Bump `github.com/lib/pq` from 1.10.3 to 1.10.4
* Bump `github.com/PuerkitoBio/goquery` from 1.7.1 to 1.8.0
* Fix typos in `model/icon.go`
* Add `data-srcset` support to `add_dynamic_image rewrite` rewrite rule
* Fix Docker Compose example files compatibility to v3
* Added the `role="article"` to `<article>` elements for better accessibility with screen readers
* Redact secrets shown on the about page
* Handle `srcset` images with no space after comma
* Hide the logout link when using auth proxy
* Fix wrong CSS variable
* Change `-config-dump` command to use `KEY=VALUE` format
Version 2.0.33 (September 25, 2021)
-----------------------------------
* Build RPM and Debian package with PIE mode enabled
* Add CSS rule to hide `<template>` tag in old browsers
* Bump `github.com/tdewolff/minify/v2 from 2.9.21 to 2.9.22`
* Bump `github.com/lib/pq from 1.10.2 to 1.10.3`
* Remove `RequestURI()` hack
* Improve `zh_CN` translation
* Add ability to change entry sort order in the UI
* Add minor improvements in integration package
* Add Telegram integration
* Add rewrite rule to remove DOM elements
* Add proxy argument to `scraper.Fetch()`
* Add mime type `application/feed+json` to discover JSON Feed v1.1
* Update scraper rule for `theregister.com`
* Add Go 1.17 to GitHub Actions
* Display option to hide feed only when category is not already hidden
* Add option to hide feeds from the global Unread list
Version 2.0.32 (August 14, 2021)
--------------------------------
* Bump `github.com/tdewolff/minify/v2` from 2.9.17 to 2.9.21
* Bump `mvdan.cc/xurls/v2` from 2.2.0 to 2.3.0
* Bump `github.com/PuerkitoBio/goquery` from 1.6.1 to 1.7.1
* Bump `github.com/prometheus/client_golang` from 1.10.0 to 1.11.0
* Add `/rss/` to the list of well known URLs during feed discovery
* Use `authors` entry for JSON 1.1 feeds
* Added Greek translation
* Added the ability to mark an entire category as read in the web ui
* Added "in" in "logged in" for en_US `tooltip.logged_user`
* Added option to hide categories from the global unread list
* Show "saving" labels for entry status button
* Golang client: Try to parse response body on `InternalServerError` errors
* contrib: Add support for a $MINIFLUX_IMAGE env var in docker-compose
* contrib: Bump docker-compose version to 3.4
Version 2.0.31 (June 6, 2021)
-----------------------------
* Expose comments_url entry field in Golang API client
* Use unique file names for cache busting instead of query string
* Highlight and sort feeds with unread entries in feeds list
* Mark items as read on click/middle click of external links
* Fix: Firefox on Windows does not show the active link as bold
* Avoid extra HTTP request for fetching custom stylesheet
* Remove invalid CSRF HTML meta tag
* Add lang attribute to root HTML tag
* Use runes instead of bytes to truncate JSON feed titles (avoid breaking Unicode strings)
* Expose changed_at time through the API
* Add new config option CLEANUP_ARCHIVE_BATCH_SIZE
* Add new option DATABASE_CONNECTION_LIFETIME
* Add database stats to Prometheus exporter
* Add Systemd watchdog
* Avoid custom stylesheet to be cached by third-party CDN
* Update a shared entry label translation in zh_CN
* Bump github.com/tdewolff/minify/v2 from 2.9.16 to 2.9.17
* Bump github.com/lib/pq from 1.10.1 to 1.10.2
Version 2.0.30 (May 7, 2021)
----------------------------
* Security fix: any user can delete any feed (Regression introduced in commit 51fb949)
* Fix password reset via CLI
* Increase default batch size value
* Handle RSS feed title with encoded Unicode entities
* Show number of unread per category in category list instead of number of feeds
* Bump github.com/lib/pq from 1.10.0 to 1.10.1
* Filtering doesn't work when selecting from multiple found feeds
* Bump github.com/tdewolff/minify/v2 from 2.9.15 to 2.9.16
* Use an appropriate color for visited links on dark theme
* Fix typo in reader/json/doc.go
* Create SECURITY.md
* Setup golangci-lint Github Action
* Add per feed cookies option
* Bump github.com/prometheus/client_golang from 1.9.0 to 1.10.0
* Bump github.com/tdewolff/minify/v2 from 2.9.13 to 2.9.15
Version 2.0.29 (Mar 21, 2021)
-----------------------------
* Miniflux requires at least Go 1.16 now
* Improved support of Atom text constructs
- Improve handling of CDATA in text elements
- Omit XHTML root element because it should not be part of the content
- Fix incorrect parsing of HTML elements
* Handle RDF feed with HTML encoded entry title
* Add Turkish language
* Improve deletion of feeds with lots of entries
* Add support of Systemd readiness notification using the sd_notify protocol
* Remove feed_icons service worker cache because it's causing more problems than it solves (and HTTP cache seems faster)
* Add basic PWA offline page
- Add basic offline mode when using the service worker
- Starting in Chrome 93, offline mode is going to be a requirement to install the PWA
* Replace icon for "Add to home screen" button
* Use SVG icons for "toast" notifications
* Use SVG sprite for icons instead of inline elements
* Reset scroll position on mark page as read
* Add link to mark all feed entries as read
* Make web app display mode configurable (The change is visible after reinstalling the web app)
* Handle RSS feeds with CDATA in author item element
* Add read time on the article page
* Avoid showing a broken image when there is no feed icon
* Add option to allow self-signed or invalid certificates
* Add new config option to scrape YouTube's website to get video duration as read time (disabled by default)
* Send full article content to Wallabag
* Add more extensive health check support
- Improve endpoint to test database connection
- Add new cli argument: -healthcheck
- Update Docker Compose examples
* Update iframe "allow list" to support Bilibili videos
* Remove completely generated files and use embed package to bundle JS/CSS/Images/Translations files into the final binary
* Remove deprecated io/ioutil package
* Show Postgres version in "About" page
Version 2.0.28 (Feb 15, 2021)
-----------------------------
* Add HTTP header "Referrer-Policy: no-referrer"
* Handle entry title with double encoded entities
* Add Open Containers annotations to Docker image
* Remove iframe inner HTML contents (iframe element never has fallback content)
* Update date parser to fix another time zone issue
* Update German translation for blocklist and keeplist
* Validate Keep list and Block list rules syntax
* Add support for IPv6 with zone index
* Allow images with data URLs
* Limit full-text search indexation to first 500K characters (tsvector has a size limit of 1MB)
* Change PWA display mode to standalone
* ETag value is not set correctly in HTTP client (regression)
* Add database backed Let's Encrypt certificate cache
* Add global option POLLING_PARSING_ERROR_LIMIT
* Update systemd service file comments to use `systemctl edit` for editing
* Update Go version to 1.15 in go.mod
* Don't discard the "Fetch via Proxy" option
* Update man page to show the default values
* Add PostgreSQL indices
* Add API endpoints to get feeds and entries of a category
* Create feed query builder
* Bump github.com/PuerkitoBio/goquery from 1.6.0 to 1.6.1
* Show global options in the about page
* Update man page to mention -1 can be used for CLEANUP_ARCHIVE_* options
Version 2.0.27 (Jan 9, 2021)
----------------------------
@@ -377,7 +835,7 @@ Version 2.0.13 (Nov 25, 2018)
* Add man page
* Add support for Systemd Socket Activation (experimental)
* Add the possiblity to listen on Unix sockets
* Add the possibility to listen on Unix sockets
* Add config options to disable HTTP and scheduler services
* Archive more read entries in cleanup job
* Change default database connection string (backward compatible)
+56 -45
View File
@@ -10,7 +10,7 @@ DEB_IMG_ARCH := amd64
export PGPASSWORD := postgres
.PHONY: generate \
.PHONY: \
miniflux \
linux-amd64 \
linux-arm64 \
@@ -19,6 +19,7 @@ export PGPASSWORD := postgres
linux-armv5 \
linux-x86 \
darwin-amd64 \
darwin-arm64 \
freebsd-amd64 \
freebsd-x86 \
openbsd-amd64 \
@@ -35,67 +36,68 @@ export PGPASSWORD := postgres
integration-test \
clean-integration-test \
docker-image \
docker-image-distroless \
docker-images \
rpm \
debian \
debian-packages
generate:
@ go generate
miniflux:
@ go build -buildmode=pie -ldflags=$(LD_FLAGS) -o $(APP) main.go
miniflux: generate
@ go build -ldflags=$(LD_FLAGS) -o $(APP) main.go
linux-amd64:
@ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-amd64: generate
@ GOOS=linux GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-amd64 main.go
linux-arm64:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-arm64: generate
@ GOOS=linux GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-arm64 main.go
linux-armv7:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-armv7: generate
@ GOOS=linux GOARCH=arm GOARM=7 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv7 main.go
linux-armv6:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-armv6: generate
@ GOOS=linux GOARCH=arm GOARM=6 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv6 main.go
linux-armv5:
@ CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-armv5: generate
@ GOOS=linux GOARCH=arm GOARM=5 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-armv5 main.go
darwin-amd64:
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
darwin-amd64: generate
@ GOOS=darwin GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-darwin-amd64 main.go
darwin-arm64:
@ GOOS=darwin GOARCH=arm64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
freebsd-amd64: generate
@ GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-amd64 main.go
freebsd-amd64:
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
openbsd-amd64: generate
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-openbsd-amd64 main.go
openbsd-amd64:
@ GOOS=openbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
windows-amd64: generate
@ GOOS=windows GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-windows-amd64 main.go
windows-amd64:
@ GOOS=windows GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
build: linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-armv5 darwin-amd64 freebsd-amd64 openbsd-amd64 windows-amd64
build: linux-amd64 linux-arm64 linux-armv7 linux-armv6 linux-armv5 darwin-amd64 darwin-arm64 freebsd-amd64 openbsd-amd64 windows-amd64
# NOTE: unsupported targets
netbsd-amd64: generate
@ GOOS=netbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-netbsd-amd64 main.go
netbsd-amd64:
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=amd64 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
linux-x86: generate
@ GOOS=linux GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-linux-x86 main.go
linux-x86:
@ CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
freebsd-x86: generate
@ GOOS=freebsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-x86 main.go
freebsd-x86:
@ CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
netbsd-x86: generate
@ GOOS=netbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-netbsd-x86 main.go
netbsd-x86:
@ CGO_ENABLED=0 GOOS=netbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
openbsd-x86: generate
@ GOOS=openbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-freebsd-x86 main.go
openbsd-x86:
@ GOOS=openbsd GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
windows-x86: generate
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-windows-x86 main.go
windows-x86:
@ GOOS=windows GOARCH=386 go build -ldflags=$(LD_FLAGS) -o $(APP)-$@ main.go
run: generate
@ LOG_DATE_TIME=1 go run main.go -debug
run:
@ LOG_DATE_TIME=1 DEBUG=1 RUN_MIGRATIONS=1 go run main.go
clean:
@ rm -f $(APP)-* $(APP) $(APP)*.rpm $(APP)*.deb
@@ -109,11 +111,17 @@ lint:
integration-test:
psql -U postgres -c 'drop database if exists miniflux_test;'
psql -U postgres -c 'create database miniflux_test;'
DATABASE_URL=$(DB_URL) go run main.go -migrate
DATABASE_URL=$(DB_URL) ADMIN_USERNAME=admin ADMIN_PASSWORD=test123 go run main.go -create-admin
go build -o miniflux-test main.go
DATABASE_URL=$(DB_URL) ./miniflux-test -debug >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
while ! echo exit | nc localhost 8080; do sleep 1; done >/dev/null
DATABASE_URL=$(DB_URL) \
ADMIN_USERNAME=admin \
ADMIN_PASSWORD=test123 \
CREATE_ADMIN=1 \
RUN_MIGRATIONS=1 \
DEBUG=1 \
./miniflux-test >/tmp/miniflux.log 2>&1 & echo "$$!" > "/tmp/miniflux.pid"
while ! nc -z localhost 8080; do sleep 1; done
go test -v -tags=integration -count=1 miniflux.app/tests
clean-integration-test:
@@ -123,12 +131,15 @@ clean-integration-test:
@ psql -U postgres -c 'drop database if exists miniflux_test;'
docker-image:
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/Dockerfile .
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/alpine/Dockerfile .
docker-image-distroless:
docker build -t $(DOCKER_IMAGE):$(VERSION) -f packaging/docker/distroless/Dockerfile .
docker-images:
docker buildx build \
--platform linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 \
--file packaging/docker/Dockerfile \
--file packaging/docker/alpine/Dockerfile \
--tag $(DOCKER_IMAGE):$(VERSION) \
--push .
@@ -142,7 +153,7 @@ rpm: clean
rpmbuild -bb --define "_miniflux_version $(VERSION)" /root/rpmbuild/SPECS/miniflux.spec
debian:
@ docker build \
@ docker build --load \
--build-arg BASE_IMAGE_ARCH=$(DEB_IMG_ARCH) \
-t $(DEB_IMG_ARCH)/miniflux-deb-builder \
-f packaging/debian/Dockerfile \
+11
View File
@@ -0,0 +1,11 @@
# Security Policy
## Supported Versions
Only the latest stable version is supported.
## Reporting a Vulnerability
Preferably, [report the vulnerability privately using GitHub](https://github.com/miniflux/v2/security/advisories/new) ([documentation](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability)).
If you do not want to use GitHub, send an email to `security AT miniflux DOT net` with all the steps to reproduce the problem.
+12 -6
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -14,13 +13,14 @@ import (
)
type handler struct {
store *storage.Storage
pool *worker.Pool
store *storage.Storage
pool *worker.Pool
router *mux.Router
}
// Serve declares API routes for the application.
func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
handler := &handler{store, pool}
handler := &handler{store, pool, router}
sr := router.PathPrefix("/v1").Subrouter()
middleware := newMiddleware(store)
@@ -41,9 +41,14 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
sr.HandleFunc("/categories/{categoryID}", handler.updateCategory).Methods(http.MethodPut)
sr.HandleFunc("/categories/{categoryID}", handler.removeCategory).Methods(http.MethodDelete)
sr.HandleFunc("/categories/{categoryID}/mark-all-as-read", handler.markCategoryAsRead).Methods(http.MethodPut)
sr.HandleFunc("/categories/{categoryID}/feeds", handler.getCategoryFeeds).Methods(http.MethodGet)
sr.HandleFunc("/categories/{categoryID}/refresh", handler.refreshCategory).Methods(http.MethodPut)
sr.HandleFunc("/categories/{categoryID}/entries", handler.getCategoryEntries).Methods(http.MethodGet)
sr.HandleFunc("/categories/{categoryID}/entries/{entryID}", handler.getCategoryEntry).Methods(http.MethodGet)
sr.HandleFunc("/discover", handler.discoverSubscriptions).Methods(http.MethodPost)
sr.HandleFunc("/feeds", handler.createFeed).Methods(http.MethodPost)
sr.HandleFunc("/feeds", handler.getFeeds).Methods(http.MethodGet)
sr.HandleFunc("/feeds/counters", handler.fetchCounters).Methods(http.MethodGet)
sr.HandleFunc("/feeds/refresh", handler.refreshAllFeeds).Methods(http.MethodPut)
sr.HandleFunc("/feeds/{feedID}/refresh", handler.refreshFeed).Methods(http.MethodPut)
sr.HandleFunc("/feeds/{feedID}", handler.getFeed).Methods(http.MethodGet)
@@ -59,4 +64,5 @@ func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool) {
sr.HandleFunc("/entries", handler.setEntryStatus).Methods(http.MethodPut)
sr.HandleFunc("/entries/{entryID}", handler.getEntry).Methods(http.MethodGet)
sr.HandleFunc("/entries/{entryID}/bookmark", handler.toggleBookmark).Methods(http.MethodPut)
sr.HandleFunc("/entries/{entryID}/fetch-content", handler.fetchContent).Methods(http.MethodGet)
}
+29 -5
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -98,12 +97,20 @@ func (h *handler) markCategoryAsRead(w http.ResponseWriter, r *http.Request) {
}
func (h *handler) getCategories(w http.ResponseWriter, r *http.Request) {
categories, err := h.store.Categories(request.UserID(r))
var categories model.Categories
var err error
includeCounts := request.QueryStringParam(r, "counts", "false")
if includeCounts == "true" {
categories, err = h.store.CategoriesWithFeedCount(request.UserID(r))
} else {
categories, err = h.store.Categories(request.UserID(r))
}
if err != nil {
json.ServerError(w, r, err)
return
}
json.OK(w, r, categories)
}
@@ -123,3 +130,20 @@ func (h *handler) removeCategory(w http.ResponseWriter, r *http.Request) {
json.NoContent(w, r)
}
func (h *handler) refreshCategory(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
categoryID := request.RouteInt64Param(r, "categoryID")
jobs, err := h.store.NewCategoryBatch(userID, categoryID, h.store.CountFeeds(userID))
if err != nil {
json.ServerError(w, r, err)
return
}
go func() {
h.pool.Push(jobs)
}()
json.NoContent(w, r)
}
+120 -32
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -8,15 +7,50 @@ import (
json_parser "encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"time"
"miniflux.app/config"
"miniflux.app/http/request"
"miniflux.app/http/response/json"
"miniflux.app/model"
"miniflux.app/proxy"
"miniflux.app/reader/processor"
"miniflux.app/storage"
"miniflux.app/url"
"miniflux.app/validator"
)
func (h *handler) getEntryFromBuilder(w http.ResponseWriter, r *http.Request, b *storage.EntryQueryBuilder) {
entry, err := b.GetEntry()
if err != nil {
json.ServerError(w, r, err)
return
}
if entry == nil {
json.NotFound(w, r)
return
}
entry.Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content)
proxyOption := config.Opts.ProxyOption()
for i := range entry.Enclosures {
if proxyOption == "all" || proxyOption != "none" && !url.IsHTTPS(entry.Enclosures[i].URL) {
for _, mediaType := range config.Opts.ProxyMediaTypes() {
if strings.HasPrefix(entry.Enclosures[i].MimeType, mediaType+"/") {
entry.Enclosures[i].URL = proxy.AbsoluteProxifyURL(h.router, r.Host, entry.Enclosures[i].URL)
break
}
}
}
}
json.OK(w, r, entry)
}
func (h *handler) getFeedEntry(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
entryID := request.RouteInt64Param(r, "entryID")
@@ -25,18 +59,18 @@ func (h *handler) getFeedEntry(w http.ResponseWriter, r *http.Request) {
builder.WithFeedID(feedID)
builder.WithEntryID(entryID)
entry, err := builder.GetEntry()
if err != nil {
json.ServerError(w, r, err)
return
}
h.getEntryFromBuilder(w, r, builder)
}
if entry == nil {
json.NotFound(w, r)
return
}
func (h *handler) getCategoryEntry(w http.ResponseWriter, r *http.Request) {
categoryID := request.RouteInt64Param(r, "categoryID")
entryID := request.RouteInt64Param(r, "entryID")
json.OK(w, r, entry)
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
builder.WithCategoryID(categoryID)
builder.WithEntryID(entryID)
h.getEntryFromBuilder(w, r, builder)
}
func (h *handler) getEntry(w http.ResponseWriter, r *http.Request) {
@@ -44,30 +78,24 @@ func (h *handler) getEntry(w http.ResponseWriter, r *http.Request) {
builder := h.store.NewEntryQueryBuilder(request.UserID(r))
builder.WithEntryID(entryID)
entry, err := builder.GetEntry()
if err != nil {
json.ServerError(w, r, err)
return
}
if entry == nil {
json.NotFound(w, r)
return
}
json.OK(w, r, entry)
h.getEntryFromBuilder(w, r, builder)
}
func (h *handler) getFeedEntries(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
h.findEntries(w, r, feedID)
h.findEntries(w, r, feedID, 0)
}
func (h *handler) getCategoryEntries(w http.ResponseWriter, r *http.Request) {
categoryID := request.RouteInt64Param(r, "categoryID")
h.findEntries(w, r, 0, categoryID)
}
func (h *handler) getEntries(w http.ResponseWriter, r *http.Request) {
h.findEntries(w, r, 0)
h.findEntries(w, r, 0, 0)
}
func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int64) {
func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int64, categoryID int64) {
statuses := request.QueryStringParamList(r, "status")
for _, status := range statuses {
if err := validator.ValidateEntryStatus(status); err != nil {
@@ -96,7 +124,7 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
}
userID := request.UserID(r)
categoryID := request.QueryInt64Param(r, "category_id", 0)
categoryID = request.QueryInt64Param(r, "category_id", categoryID)
if categoryID > 0 && !h.store.CategoryIDExists(userID, categoryID) {
json.BadRequest(w, r, errors.New("Invalid category ID"))
return
@@ -108,14 +136,16 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
return
}
tags := request.QueryStringParamList(r, "tags")
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithFeedID(feedID)
builder.WithCategoryID(categoryID)
builder.WithStatuses(statuses)
builder.WithOrder(order)
builder.WithDirection(direction)
builder.WithSorting(order, direction)
builder.WithOffset(offset)
builder.WithLimit(limit)
builder.WithTags(tags)
configureFilters(builder, r)
entries, err := builder.GetEntries()
@@ -130,6 +160,10 @@ func (h *handler) findEntries(w http.ResponseWriter, r *http.Request, feedID int
return
}
for i := range entries {
entries[i].Content = proxy.AbsoluteProxyRewriter(h.router, r.Host, entries[i].Content)
}
json.OK(w, r, &entriesResponse{Total: count, Entries: entries})
}
@@ -163,6 +197,57 @@ func (h *handler) toggleBookmark(w http.ResponseWriter, r *http.Request) {
json.NoContent(w, r)
}
func (h *handler) fetchContent(w http.ResponseWriter, r *http.Request) {
loggedUserID := request.UserID(r)
entryID := request.RouteInt64Param(r, "entryID")
entryBuilder := h.store.NewEntryQueryBuilder(loggedUserID)
entryBuilder.WithEntryID(entryID)
entryBuilder.WithoutStatus(model.EntryStatusRemoved)
entry, err := entryBuilder.GetEntry()
if err != nil {
json.ServerError(w, r, err)
return
}
if entry == nil {
json.NotFound(w, r)
return
}
user, err := h.store.UserByID(entry.UserID)
if err != nil {
json.ServerError(w, r, err)
return
}
if user == nil {
json.NotFound(w, r)
return
}
feedBuilder := storage.NewFeedQueryBuilder(h.store, loggedUserID)
feedBuilder.WithFeedID(entry.FeedID)
feed, err := feedBuilder.GetFeed()
if err != nil {
json.ServerError(w, r, err)
return
}
if feed == nil {
json.NotFound(w, r)
return
}
if err := processor.ProcessEntryWebPage(feed, entry, user); err != nil {
json.ServerError(w, r, err)
return
}
json.OK(w, r, map[string]string{"content": entry.Content})
}
func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
beforeEntryID := request.QueryInt64Param(r, "before_entry_id", 0)
if beforeEntryID > 0 {
@@ -190,7 +275,10 @@ func configureFilters(builder *storage.EntryQueryBuilder, r *http.Request) {
}
if request.HasQueryParam(r, "starred") {
builder.WithStarred()
starred, err := strconv.ParseBool(r.URL.Query().Get("starred"))
if err == nil {
builder.WithStarred(starred)
}
}
searchQuery := request.QueryStringParam(r, "search", "")
+36 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -136,6 +135,30 @@ func (h *handler) markFeedAsRead(w http.ResponseWriter, r *http.Request) {
json.NoContent(w, r)
}
func (h *handler) getCategoryFeeds(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
categoryID := request.RouteInt64Param(r, "categoryID")
category, err := h.store.Category(userID, categoryID)
if err != nil {
json.ServerError(w, r, err)
return
}
if category == nil {
json.NotFound(w, r)
return
}
feeds, err := h.store.FeedsByCategoryWithCounters(userID, categoryID)
if err != nil {
json.ServerError(w, r, err)
return
}
json.OK(w, r, feeds)
}
func (h *handler) getFeeds(w http.ResponseWriter, r *http.Request) {
feeds, err := h.store.Feeds(request.UserID(r))
if err != nil {
@@ -146,6 +169,16 @@ func (h *handler) getFeeds(w http.ResponseWriter, r *http.Request) {
json.OK(w, r, feeds)
}
func (h *handler) fetchCounters(w http.ResponseWriter, r *http.Request) {
counters, err := h.store.FetchCounters(request.UserID(r))
if err != nil {
json.ServerError(w, r, err)
return
}
json.OK(w, r, counters)
}
func (h *handler) getFeed(w http.ResponseWriter, r *http.Request) {
feedID := request.RouteInt64Param(r, "feedID")
feed, err := h.store.FeedByID(request.UserID(r), feedID)
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
+4 -4
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -25,8 +24,9 @@ func (m *middleware) handleCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token")
w.Header().Set("Access-Control-Allow-Headers", "X-Auth-Token, Authorization, Content-Type, Accept")
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Max-Age", "3600")
w.WriteHeader(http.StatusOK)
return
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
+4 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
@@ -29,9 +28,11 @@ func (h *handler) discoverSubscriptions(w http.ResponseWriter, r *http.Request)
subscriptions, finderErr := subscription.FindSubscriptions(
subscriptionDiscoveryRequest.URL,
subscriptionDiscoveryRequest.UserAgent,
subscriptionDiscoveryRequest.Cookie,
subscriptionDiscoveryRequest.Username,
subscriptionDiscoveryRequest.Password,
subscriptionDiscoveryRequest.FetchViaProxy,
subscriptionDiscoveryRequest.AllowSelfSignedCertificates,
)
if finderErr != nil {
json.ServerError(w, r, finderErr)
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package api // import "miniflux.app/api"
+7 -8
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
@@ -10,13 +9,13 @@ import (
"os"
"strings"
"golang.org/x/crypto/ssh/terminal"
"golang.org/x/term"
)
func askCredentials() (string, string) {
fd := int(os.Stdin.Fd())
if !terminal.IsTerminal(fd) {
if !term.IsTerminal(fd) {
fmt.Fprintf(os.Stderr, "This is not a terminal, exiting.\n")
os.Exit(1)
}
@@ -28,9 +27,9 @@ func askCredentials() (string, string) {
fmt.Print("Enter Password: ")
state, _ := terminal.GetState(fd)
defer terminal.Restore(fd, state)
bytePassword, _ := terminal.ReadPassword(fd)
state, _ := term.GetState(fd)
defer term.Restore(fd, state)
bytePassword, _ := term.ReadPassword(fd)
fmt.Printf("\n")
return strings.TrimSpace(username), strings.TrimSpace(string(bytePassword))
+42
View File
@@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
import (
"time"
"miniflux.app/config"
"miniflux.app/logger"
"miniflux.app/metric"
"miniflux.app/model"
"miniflux.app/storage"
)
func runCleanupTasks(store *storage.Storage) {
nbSessions := store.CleanOldSessions(config.Opts.CleanupRemoveSessionsDays())
nbUserSessions := store.CleanOldUserSessions(config.Opts.CleanupRemoveSessionsDays())
logger.Info("[Sessions] Removed %d application sessions and %d user sessions", nbSessions, nbUserSessions)
startTime := time.Now()
if rowsAffected, err := store.ArchiveEntries(model.EntryStatusRead, config.Opts.CleanupArchiveReadDays(), config.Opts.CleanupArchiveBatchSize()); err != nil {
logger.Error("[ArchiveReadEntries] %v", err)
} else {
logger.Info("[ArchiveReadEntries] %d entries changed", rowsAffected)
if config.Opts.HasMetricsCollector() {
metric.ArchiveEntriesDuration.WithLabelValues(model.EntryStatusRead).Observe(time.Since(startTime).Seconds())
}
}
startTime = time.Now()
if rowsAffected, err := store.ArchiveEntries(model.EntryStatusUnread, config.Opts.CleanupArchiveUnreadDays(), config.Opts.CleanupArchiveBatchSize()); err != nil {
logger.Error("[ArchiveUnreadEntries] %v", err)
} else {
logger.Info("[ArchiveUnreadEntries] %d entries changed", rowsAffected)
if config.Opts.HasMetricsCollector() {
metric.ArchiveEntriesDuration.WithLabelValues(model.EntryStatusUnread).Observe(time.Since(startTime).Seconds())
}
}
}
+61 -11
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
@@ -10,13 +9,15 @@ import (
"miniflux.app/config"
"miniflux.app/database"
"miniflux.app/locale"
"miniflux.app/logger"
"miniflux.app/storage"
"miniflux.app/ui/static"
"miniflux.app/version"
)
const (
flagInfoHelp = "Show application information"
flagInfoHelp = "Show build information"
flagVersionHelp = "Show application version"
flagMigrateHelp = "Run SQL migrations"
flagFlushSessionsHelp = "Flush all sessions (disconnect users)"
@@ -26,6 +27,9 @@ const (
flagDebugModeHelp = "Show debug logs"
flagConfigFileHelp = "Load configuration file"
flagConfigDumpHelp = "Print parsed configuration values"
flagHealthCheckHelp = `Perform a health check on the given endpoint (the value "auto" try to guess the health check endpoint).`
flagRefreshFeedsHelp = "Refresh a batch of feeds and exit"
flagRunCleanupTasksHelp = "Run cleanup tasks (delete old sessions and archives old entries)"
)
// Parse parses command line arguments.
@@ -42,6 +46,9 @@ func Parse() {
flagDebugMode bool
flagConfigFile string
flagConfigDump bool
flagHealthCheck string
flagRefreshFeeds bool
flagRunCleanupTasks bool
)
flag.BoolVar(&flagInfo, "info", false, flagInfoHelp)
@@ -57,6 +64,9 @@ func Parse() {
flag.StringVar(&flagConfigFile, "config-file", "", flagConfigFileHelp)
flag.StringVar(&flagConfigFile, "c", "", flagConfigFileHelp)
flag.BoolVar(&flagConfigDump, "config-dump", false, flagConfigDumpHelp)
flag.StringVar(&flagHealthCheck, "healthcheck", "", flagHealthCheckHelp)
flag.BoolVar(&flagRefreshFeeds, "refresh-feeds", false, flagRefreshFeedsHelp)
flag.BoolVar(&flagRunCleanupTasks, "run-cleanup-tasks", false, flagRunCleanupTasksHelp)
flag.Parse()
cfg := config.NewParser()
@@ -86,6 +96,11 @@ func Parse() {
logger.EnableDebug()
}
if flagHealthCheck != "" {
doHealthCheck(flagHealthCheck)
return
}
if flagInfo {
info()
return
@@ -100,22 +115,47 @@ func Parse() {
logger.Info("The default value for DATABASE_URL is used")
}
logger.Debug("Loading translations...")
if err := locale.LoadCatalogMessages(); err != nil {
logger.Fatal("Unable to load translations: %v", err)
}
logger.Debug("Loading static assets...")
if err := static.CalculateBinaryFileChecksums(); err != nil {
logger.Fatal("Unable to calculate binary files checksum: %v", err)
}
if err := static.GenerateStylesheetsBundles(); err != nil {
logger.Fatal("Unable to generate Stylesheet bundles: %v", err)
}
if err := static.GenerateJavascriptBundles(); err != nil {
logger.Fatal("Unable to generate Javascript bundles: %v", err)
}
db, err := database.NewConnectionPool(
config.Opts.DatabaseURL(),
config.Opts.DatabaseMinConns(),
config.Opts.DatabaseMaxConns(),
config.Opts.DatabaseConnectionLifetime(),
)
if err != nil {
logger.Fatal("Unable to connect to the database: %v", err)
logger.Fatal("Unable to initialize database connection pool: %v", err)
}
defer db.Close()
if flagMigrate {
database.Migrate(db)
return
store := storage.NewStorage(db)
if err := store.Ping(); err != nil {
logger.Fatal("Unable to connect to the database: %v", err)
}
store := storage.NewStorage(db)
if flagMigrate {
if err := database.Migrate(db); err != nil {
logger.Fatal(`%v`, err)
}
return
}
if flagResetFeedErrors {
store.ResetFeedErrors()
@@ -137,7 +177,7 @@ func Parse() {
return
}
// Run migrations and start the deamon.
// Run migrations and start the daemon.
if config.Opts.RunMigrations() {
if err := database.Migrate(db); err != nil {
logger.Fatal(`%v`, err)
@@ -148,10 +188,20 @@ func Parse() {
logger.Fatal(`You must run the SQL migrations, %v`, err)
}
// Create admin user and start the deamon.
// Create admin user and start the daemon.
if config.Opts.CreateAdmin() {
createAdmin(store)
}
if flagRefreshFeeds {
refreshFeeds(store)
return
}
if flagRunCleanupTasks {
runCleanupTasks(store)
return
}
startDaemon(store)
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
+38 -8
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
@@ -13,16 +12,16 @@ import (
"time"
"miniflux.app/config"
httpd "miniflux.app/http/server"
"miniflux.app/logger"
"miniflux.app/metric"
"miniflux.app/service/httpd"
"miniflux.app/service/scheduler"
"miniflux.app/storage"
"miniflux.app/systemd"
"miniflux.app/worker"
)
func startDaemon(store *storage.Storage) {
logger.Info("Starting Miniflux...")
logger.Info("Starting daemon...")
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
@@ -31,12 +30,12 @@ func startDaemon(store *storage.Storage) {
pool := worker.NewPool(store, config.Opts.WorkerPoolSize())
if config.Opts.HasSchedulerService() && !config.Opts.HasMaintenanceMode() {
scheduler.Serve(store, pool)
runScheduler(store, pool)
}
var httpServer *http.Server
if config.Opts.HasHTTPService() {
httpServer = httpd.Serve(store, pool)
httpServer = httpd.StartWebServer(store, pool)
}
if config.Opts.HasMetricsCollector() {
@@ -44,6 +43,37 @@ func startDaemon(store *storage.Storage) {
go collector.GatherStorageMetrics()
}
if systemd.HasNotifySocket() {
logger.Info("Sending readiness notification to Systemd")
if err := systemd.SdNotify(systemd.SdNotifyReady); err != nil {
logger.Error("Unable to send readiness notification to systemd: %v", err)
}
if config.Opts.HasWatchdog() && systemd.HasSystemdWatchdog() {
logger.Info("Activating Systemd watchdog")
go func() {
interval, err := systemd.WatchdogInterval()
if err != nil {
logger.Error("Unable to parse watchdog interval from systemd: %v", err)
return
}
for {
err := store.Ping()
if err != nil {
logger.Error(`Systemd Watchdog: %v`, err)
} else {
systemd.SdNotify(systemd.SdNotifyWatchdog)
}
time.Sleep(interval / 3)
}
}()
}
}
<-stop
logger.Info("Shutting down the process...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package cli implements command line arguments for Miniflux application.
*/
package cli // import "miniflux.app/cli"
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
+33
View File
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
import (
"net/http"
"time"
"miniflux.app/config"
"miniflux.app/logger"
)
func doHealthCheck(healthCheckEndpoint string) {
if healthCheckEndpoint == "auto" {
healthCheckEndpoint = "http://" + config.Opts.ListenAddr() + config.Opts.BasePath() + "/healthcheck"
}
logger.Debug(`Executing health check on %s`, healthCheckEndpoint)
client := &http.Client{Timeout: 3 * time.Second}
resp, err := client.Get(healthCheckEndpoint)
if err != nil {
logger.Fatal(`Health check failure: %v`, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logger.Fatal(`Health check failed with status code %d`, resp.StatusCode)
}
logger.Debug(`Health check is OK`)
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
+51
View File
@@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
import (
"sync"
"time"
"miniflux.app/config"
"miniflux.app/logger"
"miniflux.app/model"
feedHandler "miniflux.app/reader/handler"
"miniflux.app/storage"
)
func refreshFeeds(store *storage.Storage) {
var wg sync.WaitGroup
startTime := time.Now()
jobs, err := store.NewBatch(config.Opts.BatchSize())
if err != nil {
logger.Error("[Cronjob] %v", err)
}
nbJobs := len(jobs)
logger.Info("[Cronjob]] Created %d jobs from a batch size of %d", nbJobs, config.Opts.BatchSize())
var jobQueue = make(chan model.Job, nbJobs)
logger.Info("[Cronjob] Starting a pool of %d workers", config.Opts.WorkerPoolSize())
for i := 0; i < config.Opts.WorkerPoolSize(); i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for job := range jobQueue {
logger.Info("[Cronjob] Refreshing feed #%d for user #%d in worker #%d", job.FeedID, job.UserID, workerID)
if err := feedHandler.RefreshFeed(store, job.UserID, job.FeedID); err != nil {
logger.Error("[Cronjob] Refreshing the feed #%d returned this error: %v", job.FeedID, err)
}
}
}(i)
}
for _, job := range jobs {
jobQueue <- job
}
close(jobQueue)
wg.Wait()
logger.Info("[Cronjob] Refreshed %d feed(s) in %s", nbJobs, time.Since(startTime))
}
+3 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
@@ -34,6 +33,7 @@ func resetPassword(store *storage.Storage) {
os.Exit(1)
}
user.Password = password
if err := store.UpdateUser(user); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
+47
View File
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cli // import "miniflux.app/cli"
import (
"time"
"miniflux.app/config"
"miniflux.app/logger"
"miniflux.app/storage"
"miniflux.app/worker"
)
func runScheduler(store *storage.Storage, pool *worker.Pool) {
logger.Info(`Starting background scheduler...`)
go feedScheduler(
store,
pool,
config.Opts.PollingFrequency(),
config.Opts.BatchSize(),
)
go cleanupScheduler(
store,
config.Opts.CleanupFrequencyHours(),
)
}
func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSize int) {
for range time.Tick(time.Duration(frequency) * time.Minute) {
jobs, err := store.NewBatch(batchSize)
logger.Info("[Scheduler:Feed] Pushing %d jobs to the queue", len(jobs))
if err != nil {
logger.Error("[Scheduler:Feed] %v", err)
} else {
pool.Push(jobs)
}
}
}
func cleanupScheduler(store *storage.Storage, frequency int) {
for range time.Tick(time.Duration(frequency) * time.Hour) {
runCleanupTasks(store)
}
}
+2 -2
View File
@@ -20,7 +20,7 @@ package main
import (
"fmt"
"io/ioutil"
"os"
miniflux "miniflux.app/client"
)
@@ -47,7 +47,7 @@ func main() {
return
}
err = ioutil.WriteFile("opml.xml", opml, 0644)
err = os.WriteFile("opml.xml", opml, 0644)
if err != nil {
fmt.Println(err)
return
+88 -10
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/client"
@@ -8,9 +7,9 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/url"
"strconv"
"strings"
)
// Client holds API procedure calls.
@@ -20,6 +19,11 @@ type Client struct {
// New returns a new Miniflux client.
func New(endpoint string, credentials ...string) *Client {
// Web gives "API Endpoint = https://miniflux.app/v1/", it doesn't work (/v1/v1/me)
endpoint = strings.TrimSuffix(endpoint, "/")
endpoint = strings.TrimSuffix(endpoint, "/v1")
// trim to https://miniflux.app
if len(credentials) == 2 {
return &Client{request: &request{endpoint: endpoint, username: credentials[0], password: credentials[1]}}
}
@@ -182,7 +186,6 @@ func (c *Client) CreateCategory(title string) (*Category, error) {
body, err := c.request.Post("/v1/categories", map[string]interface{}{
"title": title,
})
if err != nil {
return nil, err
}
@@ -202,7 +205,6 @@ func (c *Client) UpdateCategory(categoryID int64, title string) (*Category, erro
body, err := c.request.Put(fmt.Sprintf("/v1/categories/%d", categoryID), map[string]interface{}{
"title": title,
})
if err != nil {
return nil, err
}
@@ -223,11 +225,34 @@ func (c *Client) MarkCategoryAsRead(categoryID int64) error {
return err
}
// CategoryFeeds gets feeds of a category.
func (c *Client) CategoryFeeds(categoryID int64) (Feeds, error) {
body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/feeds", categoryID))
if err != nil {
return nil, err
}
defer body.Close()
var feeds Feeds
decoder := json.NewDecoder(body)
if err := decoder.Decode(&feeds); err != nil {
return nil, fmt.Errorf("miniflux: response error (%v)", err)
}
return feeds, nil
}
// DeleteCategory removes a category.
func (c *Client) DeleteCategory(categoryID int64) error {
return c.request.Delete(fmt.Sprintf("/v1/categories/%d", categoryID))
}
// RefreshCategory refreshes a category.
func (c *Client) RefreshCategory(categoryID int64) error {
_, err := c.request.Put(fmt.Sprintf("/v1/categories/%d/refresh", categoryID), nil)
return err
}
// Feeds gets all feeds.
func (c *Client) Feeds() (Feeds, error) {
body, err := c.request.Get("/v1/feeds")
@@ -253,7 +278,7 @@ func (c *Client) Export() ([]byte, error) {
}
defer body.Close()
opml, err := ioutil.ReadAll(body)
opml, err := io.ReadAll(body)
if err != nil {
return nil, err
}
@@ -329,7 +354,7 @@ func (c *Client) MarkFeedAsRead(feedID int64) error {
// RefreshAllFeeds refreshes all feeds.
func (c *Client) RefreshAllFeeds() error {
_, err := c.request.Put(fmt.Sprintf("/v1/feeds/refresh"), nil)
_, err := c.request.Put("/v1/feeds/refresh", nil)
return err
}
@@ -378,6 +403,23 @@ func (c *Client) FeedEntry(feedID, entryID int64) (*Entry, error) {
return entry, nil
}
// CategoryEntry gets a single category entry.
func (c *Client) CategoryEntry(categoryID, entryID int64) (*Entry, error) {
body, err := c.request.Get(fmt.Sprintf("/v1/categories/%d/entries/%d", categoryID, entryID))
if err != nil {
return nil, err
}
defer body.Close()
var entry *Entry
decoder := json.NewDecoder(body)
if err := decoder.Decode(&entry); err != nil {
return nil, fmt.Errorf("miniflux: response error (%v)", err)
}
return entry, nil
}
// Entry gets a single entry.
func (c *Client) Entry(entryID int64) (*Entry, error) {
body, err := c.request.Get(fmt.Sprintf("/v1/entries/%d", entryID))
@@ -433,6 +475,25 @@ func (c *Client) FeedEntries(feedID int64, filter *Filter) (*EntryResultSet, err
return &result, nil
}
// CategoryEntries fetch entries of a category.
func (c *Client) CategoryEntries(categoryID int64, filter *Filter) (*EntryResultSet, error) {
path := buildFilterQueryString(fmt.Sprintf("/v1/categories/%d/entries", categoryID), filter)
body, err := c.request.Get(path)
if err != nil {
return nil, err
}
defer body.Close()
var result EntryResultSet
decoder := json.NewDecoder(body)
if err := decoder.Decode(&result); err != nil {
return nil, fmt.Errorf("miniflux: response error (%v)", err)
}
return &result, nil
}
// UpdateEntries updates the status of a list of entries.
func (c *Client) UpdateEntries(entryIDs []int64, status string) error {
type payload struct {
@@ -450,6 +511,23 @@ func (c *Client) ToggleBookmark(entryID int64) error {
return err
}
// FetchCounters
func (c *Client) FetchCounters() (*FeedCounters, error) {
body, err := c.request.Get("/v1/feeds/counters")
if err != nil {
return nil, err
}
defer body.Close()
var result FeedCounters
decoder := json.NewDecoder(body)
if err := decoder.Decode(&result); err != nil {
return nil, fmt.Errorf("miniflux: response error (%v)", err)
}
return &result, nil
}
func buildFilterQueryString(path string, filter *Filter) string {
if filter != nil {
values := url.Values{}
@@ -490,8 +568,8 @@ func buildFilterQueryString(path string, filter *Filter) string {
values.Set("before_entry_id", strconv.FormatInt(filter.BeforeEntryID, 10))
}
if filter.Starred {
values.Set("starred", "1")
if filter.Starred != "" {
values.Set("starred", filter.Starred)
}
if filter.Search != "" {
-231
View File
@@ -1,231 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package client // import "miniflux.app/client"
import (
"fmt"
"time"
)
// Entry statuses.
const (
EntryStatusUnread = "unread"
EntryStatusRead = "read"
EntryStatusRemoved = "removed"
)
// User represents a user in the system.
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"`
EntriesPerPage int `json:"entries_per_page"`
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
ShowReadingTime bool `json:"show_reading_time"`
EntrySwipe bool `json:"entry_swipe"`
LastLoginAt *time.Time `json:"last_login_at"`
}
func (u User) String() string {
return fmt.Sprintf("#%d - %s (admin=%v)", u.ID, u.Username, u.IsAdmin)
}
// UserCreationRequest represents the request to create a user.
type UserCreationRequest struct {
Username string `json:"username"`
Password string `json:"password"`
IsAdmin bool `json:"is_admin"`
GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"`
}
// UserModificationRequest represents the request to update a user.
type UserModificationRequest struct {
Username *string `json:"username"`
Password *string `json:"password"`
IsAdmin *bool `json:"is_admin"`
Theme *string `json:"theme"`
Language *string `json:"language"`
Timezone *string `json:"timezone"`
EntryDirection *string `json:"entry_sorting_direction"`
Stylesheet *string `json:"stylesheet"`
GoogleID *string `json:"google_id"`
OpenIDConnectID *string `json:"openid_connect_id"`
EntriesPerPage *int `json:"entries_per_page"`
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
ShowReadingTime *bool `json:"show_reading_time"`
EntrySwipe *bool `json:"entry_swipe"`
}
// Users represents a list of users.
type Users []User
// Category represents a feed category.
type Category struct {
ID int64 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
UserID int64 `json:"user_id,omitempty"`
}
func (c Category) String() string {
return fmt.Sprintf("#%d %s", c.ID, c.Title)
}
// Categories represents a list of categories.
type Categories []*Category
// Subscription represents a feed subscription.
type Subscription struct {
Title string `json:"title"`
URL string `json:"url"`
Type string `json:"type"`
}
func (s Subscription) String() string {
return fmt.Sprintf(`Title="%s", URL="%s", Type="%s"`, s.Title, s.URL, s.Type)
}
// Subscriptions represents a list of subscriptions.
type Subscriptions []*Subscription
// Feed represents a Miniflux feed.
type Feed struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
FeedURL string `json:"feed_url"`
SiteURL string `json:"site_url"`
Title string `json:"title"`
CheckedAt time.Time `json:"checked_at,omitempty"`
EtagHeader string `json:"etag_header,omitempty"`
LastModifiedHeader string `json:"last_modified_header,omitempty"`
ParsingErrorMsg string `json:"parsing_error_message,omitempty"`
ParsingErrorCount int `json:"parsing_error_count,omitempty"`
Disabled bool `json:"disabled"`
IgnoreHTTPCache bool `json:"ignore_http_cache"`
FetchViaProxy bool `json:"fetch_via_proxy"`
ScraperRules string `json:"scraper_rules"`
RewriteRules string `json:"rewrite_rules"`
BlocklistRules string `json:"blocklist_rules"`
KeeplistRules string `json:"keeplist_rules"`
Crawler bool `json:"crawler"`
UserAgent string `json:"user_agent"`
Username string `json:"username"`
Password string `json:"password"`
Category *Category `json:"category,omitempty"`
}
// FeedCreationRequest represents the request to create a feed.
type FeedCreationRequest struct {
FeedURL string `json:"feed_url"`
CategoryID int64 `json:"category_id"`
UserAgent string `json:"user_agent"`
Username string `json:"username"`
Password string `json:"password"`
Crawler bool `json:"crawler"`
Disabled bool `json:"disabled"`
IgnoreHTTPCache bool `json:"ignore_http_cache"`
FetchViaProxy bool `json:"fetch_via_proxy"`
ScraperRules string `json:"scraper_rules"`
RewriteRules string `json:"rewrite_rules"`
BlocklistRules string `json:"blocklist_rules"`
KeeplistRules string `json:"keeplist_rules"`
}
// FeedModificationRequest represents the request to update a feed.
type FeedModificationRequest struct {
FeedURL *string `json:"feed_url"`
SiteURL *string `json:"site_url"`
Title *string `json:"title"`
ScraperRules *string `json:"scraper_rules"`
RewriteRules *string `json:"rewrite_rules"`
BlocklistRules *string `json:"blocklist_rules"`
KeeplistRules *string `json:"keeplist_rules"`
Crawler *bool `json:"crawler"`
UserAgent *string `json:"user_agent"`
Username *string `json:"username"`
Password *string `json:"password"`
CategoryID *int64 `json:"category_id"`
Disabled *bool `json:"disabled"`
IgnoreHTTPCache *bool `json:"ignore_http_cache"`
FetchViaProxy *bool `json:"fetch_via_proxy"`
}
// FeedIcon represents the feed icon.
type FeedIcon struct {
ID int64 `json:"id"`
MimeType string `json:"mime_type"`
Data string `json:"data"`
}
// Feeds represents a list of feeds.
type Feeds []*Feed
// Entry represents a subscription item in the system.
type Entry struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
FeedID int64 `json:"feed_id"`
Status string `json:"status"`
Hash string `json:"hash"`
Title string `json:"title"`
URL string `json:"url"`
Date time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
Content string `json:"content"`
Author string `json:"author"`
ShareCode string `json:"share_code"`
Starred bool `json:"starred"`
ReadingTime int `json:"reading_time"`
Enclosures Enclosures `json:"enclosures,omitempty"`
Feed *Feed `json:"feed,omitempty"`
}
// Entries represents a list of entries.
type Entries []*Entry
// Enclosure represents an attachment.
type Enclosure struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
EntryID int64 `json:"entry_id"`
URL string `json:"url"`
MimeType string `json:"mime_type"`
Size int `json:"size"`
}
// Enclosures represents a list of attachments.
type Enclosures []*Enclosure
// Filter is used to filter entries.
type Filter struct {
Status string
Offset int
Limit int
Order string
Direction string
Starred bool
Before int64
After int64
BeforeEntryID int64
AfterEntryID int64
Search string
CategoryID int64
FeedID int64
Statuses []string
}
// EntryResultSet represents the response when fetching entries.
type EntryResultSet struct {
Total int `json:"total"`
Entries Entries `json:"entries"`
}
+3 -6
View File
@@ -1,12 +1,10 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
/*
Package client implements a client library for the Miniflux REST API.
Examples
# Examples
This code snippet fetch the list of users:
@@ -30,6 +28,5 @@ This one discover subscriptions on a website:
return
}
fmt.Println(subscriptions)
*/
package client // import "miniflux.app/client"
+268
View File
@@ -0,0 +1,268 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/client"
import (
"fmt"
"time"
)
// Entry statuses.
const (
EntryStatusUnread = "unread"
EntryStatusRead = "read"
EntryStatusRemoved = "removed"
)
// User represents a user in the system.
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
Password string `json:"password,omitempty"`
IsAdmin bool `json:"is_admin"`
Theme string `json:"theme"`
Language string `json:"language"`
Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"`
EntryOrder string `json:"entry_sorting_order"`
Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"`
EntriesPerPage int `json:"entries_per_page"`
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
ShowReadingTime bool `json:"show_reading_time"`
EntrySwipe bool `json:"entry_swipe"`
GestureNav string `json:"gesture_nav"`
LastLoginAt *time.Time `json:"last_login_at"`
DisplayMode string `json:"display_mode"`
DefaultReadingSpeed int `json:"default_reading_speed"`
CJKReadingSpeed int `json:"cjk_reading_speed"`
DefaultHomePage string `json:"default_home_page"`
CategoriesSortingOrder string `json:"categories_sorting_order"`
MarkReadOnView bool `json:"mark_read_on_view"`
}
func (u User) String() string {
return fmt.Sprintf("#%d - %s (admin=%v)", u.ID, u.Username, u.IsAdmin)
}
// UserCreationRequest represents the request to create a user.
type UserCreationRequest struct {
Username string `json:"username"`
Password string `json:"password"`
IsAdmin bool `json:"is_admin"`
GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"`
}
// UserModificationRequest represents the request to update a user.
type UserModificationRequest struct {
Username *string `json:"username"`
Password *string `json:"password"`
IsAdmin *bool `json:"is_admin"`
Theme *string `json:"theme"`
Language *string `json:"language"`
Timezone *string `json:"timezone"`
EntryDirection *string `json:"entry_sorting_direction"`
EntryOrder *string `json:"entry_sorting_order"`
Stylesheet *string `json:"stylesheet"`
GoogleID *string `json:"google_id"`
OpenIDConnectID *string `json:"openid_connect_id"`
EntriesPerPage *int `json:"entries_per_page"`
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
ShowReadingTime *bool `json:"show_reading_time"`
EntrySwipe *bool `json:"entry_swipe"`
GestureNav *string `json:"gesture_nav"`
DisplayMode *string `json:"display_mode"`
DefaultReadingSpeed *int `json:"default_reading_speed"`
CJKReadingSpeed *int `json:"cjk_reading_speed"`
DefaultHomePage *string `json:"default_home_page"`
CategoriesSortingOrder *string `json:"categories_sorting_order"`
MarkReadOnView *bool `json:"mark_read_on_view"`
}
// Users represents a list of users.
type Users []User
// Category represents a feed category.
type Category struct {
ID int64 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
UserID int64 `json:"user_id,omitempty"`
}
func (c Category) String() string {
return fmt.Sprintf("#%d %s", c.ID, c.Title)
}
// Categories represents a list of categories.
type Categories []*Category
// Subscription represents a feed subscription.
type Subscription struct {
Title string `json:"title"`
URL string `json:"url"`
Type string `json:"type"`
}
func (s Subscription) String() string {
return fmt.Sprintf(`Title="%s", URL="%s", Type="%s"`, s.Title, s.URL, s.Type)
}
// Subscriptions represents a list of subscriptions.
type Subscriptions []*Subscription
// Feed represents a Miniflux feed.
type Feed struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
FeedURL string `json:"feed_url"`
SiteURL string `json:"site_url"`
Title string `json:"title"`
CheckedAt time.Time `json:"checked_at,omitempty"`
EtagHeader string `json:"etag_header,omitempty"`
LastModifiedHeader string `json:"last_modified_header,omitempty"`
ParsingErrorMsg string `json:"parsing_error_message,omitempty"`
ParsingErrorCount int `json:"parsing_error_count,omitempty"`
Disabled bool `json:"disabled"`
IgnoreHTTPCache bool `json:"ignore_http_cache"`
AllowSelfSignedCertificates bool `json:"allow_self_signed_certificates"`
FetchViaProxy bool `json:"fetch_via_proxy"`
ScraperRules string `json:"scraper_rules"`
RewriteRules string `json:"rewrite_rules"`
BlocklistRules string `json:"blocklist_rules"`
KeeplistRules string `json:"keeplist_rules"`
Crawler bool `json:"crawler"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
Username string `json:"username"`
Password string `json:"password"`
Category *Category `json:"category,omitempty"`
HideGlobally bool `json:"hide_globally"`
}
// FeedCreationRequest represents the request to create a feed.
type FeedCreationRequest struct {
FeedURL string `json:"feed_url"`
CategoryID int64 `json:"category_id"`
UserAgent string `json:"user_agent"`
Cookie string `json:"cookie"`
Username string `json:"username"`
Password string `json:"password"`
Crawler bool `json:"crawler"`
Disabled bool `json:"disabled"`
IgnoreHTTPCache bool `json:"ignore_http_cache"`
AllowSelfSignedCertificates bool `json:"allow_self_signed_certificates"`
FetchViaProxy bool `json:"fetch_via_proxy"`
ScraperRules string `json:"scraper_rules"`
RewriteRules string `json:"rewrite_rules"`
BlocklistRules string `json:"blocklist_rules"`
KeeplistRules string `json:"keeplist_rules"`
HideGlobally bool `json:"hide_globally"`
}
// FeedModificationRequest represents the request to update a feed.
type FeedModificationRequest struct {
FeedURL *string `json:"feed_url"`
SiteURL *string `json:"site_url"`
Title *string `json:"title"`
ScraperRules *string `json:"scraper_rules"`
RewriteRules *string `json:"rewrite_rules"`
BlocklistRules *string `json:"blocklist_rules"`
KeeplistRules *string `json:"keeplist_rules"`
Crawler *bool `json:"crawler"`
UserAgent *string `json:"user_agent"`
Cookie *string `json:"cookie"`
Username *string `json:"username"`
Password *string `json:"password"`
CategoryID *int64 `json:"category_id"`
Disabled *bool `json:"disabled"`
IgnoreHTTPCache *bool `json:"ignore_http_cache"`
AllowSelfSignedCertificates *bool `json:"allow_self_signed_certificates"`
FetchViaProxy *bool `json:"fetch_via_proxy"`
HideGlobally *bool `json:"hide_globally"`
}
// FeedIcon represents the feed icon.
type FeedIcon struct {
ID int64 `json:"id"`
MimeType string `json:"mime_type"`
Data string `json:"data"`
}
type FeedCounters struct {
ReadCounters map[int64]int `json:"reads"`
UnreadCounters map[int64]int `json:"unreads"`
}
// Feeds represents a list of feeds.
type Feeds []*Feed
// Entry represents a subscription item in the system.
type Entry struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
FeedID int64 `json:"feed_id"`
Status string `json:"status"`
Hash string `json:"hash"`
Title string `json:"title"`
URL string `json:"url"`
CommentsURL string `json:"comments_url"`
Date time.Time `json:"published_at"`
CreatedAt time.Time `json:"created_at"`
ChangedAt time.Time `json:"changed_at"`
Content string `json:"content"`
Author string `json:"author"`
ShareCode string `json:"share_code"`
Starred bool `json:"starred"`
ReadingTime int `json:"reading_time"`
Enclosures Enclosures `json:"enclosures,omitempty"`
Feed *Feed `json:"feed,omitempty"`
Tags []string `json:"tags"`
}
// Entries represents a list of entries.
type Entries []*Entry
// Enclosure represents an attachment.
type Enclosure struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
EntryID int64 `json:"entry_id"`
URL string `json:"url"`
MimeType string `json:"mime_type"`
Size int `json:"size"`
}
// Enclosures represents a list of attachments.
type Enclosures []*Enclosure
const (
FilterNotStarred = "0"
FilterOnlyStarred = "1"
)
// Filter is used to filter entries.
type Filter struct {
Status string
Offset int
Limit int
Order string
Direction string
Starred string
Before int64
After int64
BeforeEntryID int64
AfterEntryID int64
Search string
CategoryID int64
FeedID int64
Statuses []string
}
// EntryResultSet represents the response when fetching entries.
type EntryResultSet struct {
Total int `json:"total"`
Entries Entries `json:"entries"`
}
+14 -9
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/client"
@@ -10,7 +9,6 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
@@ -83,11 +81,11 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
}
if data != nil {
switch data.(type) {
switch data := data.(type) {
case io.ReadCloser:
request.Body = data.(io.ReadCloser)
request.Body = data
default:
request.Body = ioutil.NopCloser(bytes.NewBuffer(r.toJSON(data)))
request.Body = io.NopCloser(bytes.NewBuffer(r.toJSON(data)))
}
}
@@ -105,8 +103,15 @@ func (r *request) execute(method, path string, data interface{}) (io.ReadCloser,
response.Body.Close()
return nil, ErrForbidden
case http.StatusInternalServerError:
response.Body.Close()
return nil, ErrServerError
defer response.Body.Close()
var resp errorResponse
decoder := json.NewDecoder(response.Body)
// If we failed to decode, just return a generic ErrServerError
if err := decoder.Decode(&resp); err != nil {
return nil, ErrServerError
}
return nil, errors.New("miniflux: internal server error: " + resp.ErrorMessage)
case http.StatusNotFound:
response.Body.Close()
return nil, ErrNotFound
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package config // import "miniflux.app/config"
+286 -49
View File
@@ -1,11 +1,9 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package config // import "miniflux.app/config"
import (
"io/ioutil"
"os"
"testing"
)
@@ -409,41 +407,6 @@ func TestDefaultCertDomainValue(t *testing.T) {
}
}
func TestCertCache(t *testing.T) {
os.Clearenv()
os.Setenv("CERT_CACHE", "foobar")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := "foobar"
result := opts.CertCache()
if result != expected {
t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected)
}
}
func TestDefaultCertCacheValue(t *testing.T) {
os.Clearenv()
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultCertCache
result := opts.CertCache()
if result != expected {
t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected)
}
}
func TestDefaultCleanupFrequencyHoursValue(t *testing.T) {
os.Clearenv()
@@ -761,6 +724,24 @@ func TestDefautSchedulerCountBasedMinInterval(t *testing.T) {
}
}
func TestPollingParsingErrorLimit(t *testing.T) {
os.Clearenv()
os.Setenv("POLLING_PARSING_ERROR_LIMIT", "100")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := 100
result := opts.PollingParsingErrorLimit()
if result != expected {
t.Fatalf(`Unexpected POLLING_SCHEDULER value, got %v instead of %v`, result, expected)
}
}
func TestOAuth2UserCreationWhenUnset(t *testing.T) {
os.Clearenv()
@@ -1181,7 +1162,97 @@ func TestPocketConsumerKeyFromUserPrefs(t *testing.T) {
}
}
func TestProxyImages(t *testing.T) {
func TestProxyOption(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_OPTION", "all")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := "all"
result := opts.ProxyOption()
if result != expected {
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
}
}
func TestDefaultProxyOptionValue(t *testing.T) {
os.Clearenv()
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultProxyOption
result := opts.ProxyOption()
if result != expected {
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expected)
}
}
func TestProxyMediaTypes(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_MEDIA_TYPES", "image,audio")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := []string{"audio", "image"}
if len(expected) != len(opts.ProxyMediaTypes()) {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() {
resultMap[mediaType] = true
}
for _, mediaType := range expected {
if !resultMap[mediaType] {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
}
}
func TestProxyMediaTypesWithDuplicatedValues(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_MEDIA_TYPES", "image,audio, image")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := []string{"audio", "image"}
if len(expected) != len(opts.ProxyMediaTypes()) {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() {
resultMap[mediaType] = true
}
for _, mediaType := range expected {
if !resultMap[mediaType] {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
}
}
func TestProxyImagesOptionBackwardCompatibility(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_IMAGES", "all")
@@ -1191,15 +1262,30 @@ func TestProxyImages(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := "all"
result := opts.ProxyImages()
expected := []string{"image"}
if len(expected) != len(opts.ProxyMediaTypes()) {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
if result != expected {
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() {
resultMap[mediaType] = true
}
for _, mediaType := range expected {
if !resultMap[mediaType] {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
}
expectedProxyOption := "all"
result := opts.ProxyOption()
if result != expectedProxyOption {
t.Fatalf(`Unexpected PROXY_OPTION value, got %q instead of %q`, result, expectedProxyOption)
}
}
func TestDefaultProxyImagesValue(t *testing.T) {
func TestDefaultProxyMediaTypes(t *testing.T) {
os.Clearenv()
parser := NewParser()
@@ -1208,11 +1294,56 @@ func TestDefaultProxyImagesValue(t *testing.T) {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultProxyImages
result := opts.ProxyImages()
expected := []string{"image"}
if len(expected) != len(opts.ProxyMediaTypes()) {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
resultMap := make(map[string]bool)
for _, mediaType := range opts.ProxyMediaTypes() {
resultMap[mediaType] = true
}
for _, mediaType := range expected {
if !resultMap[mediaType] {
t.Fatalf(`Unexpected PROXY_MEDIA_TYPES value, got %v instead of %v`, opts.ProxyMediaTypes(), expected)
}
}
}
func TestProxyHTTPClientTimeout(t *testing.T) {
os.Clearenv()
os.Setenv("PROXY_HTTP_CLIENT_TIMEOUT", "24")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := 24
result := opts.ProxyHTTPClientTimeout()
if result != expected {
t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected)
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
func TestDefaultProxyHTTPClientTimeoutValue(t *testing.T) {
os.Clearenv()
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultProxyHTTPClientTimeout
result := opts.ProxyHTTPClientTimeout()
if result != expected {
t.Fatalf(`Unexpected PROXY_HTTP_CLIENT_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
@@ -1315,6 +1446,41 @@ func TestDefaultHTTPClientMaxBodySizeValue(t *testing.T) {
}
}
func TestHTTPServerTimeout(t *testing.T) {
os.Clearenv()
os.Setenv("HTTP_SERVER_TIMEOUT", "342")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := 342
result := opts.HTTPServerTimeout()
if result != expected {
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
func TestDefaultHTTPServerTimeoutValue(t *testing.T) {
os.Clearenv()
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := defaultHTTPServerTimeout
result := opts.HTTPServerTimeout()
if result != expected {
t.Fatalf(`Unexpected HTTP_SERVER_TIMEOUT value, got %d instead of %d`, result, expected)
}
}
func TestParseConfigFile(t *testing.T) {
content := []byte(`
# This is a comment
@@ -1326,7 +1492,7 @@ DEBUG = yes
Invalid text
`)
tmpfile, err := ioutil.TempFile(".", "miniflux.*.unit_test.conf")
tmpfile, err := os.CreateTemp(".", "miniflux.*.unit_test.conf")
if err != nil {
t.Fatal(err)
}
@@ -1431,3 +1597,74 @@ func TestAuthProxyUserCreationAdmin(t *testing.T) {
t.Fatalf(`Unexpected AUTH_PROXY_USER_CREATION value, got %v instead of %v`, result, expected)
}
}
func TestFetchYouTubeWatchTime(t *testing.T) {
os.Clearenv()
os.Setenv("FETCH_YOUTUBE_WATCH_TIME", "1")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := true
result := opts.FetchYouTubeWatchTime()
if result != expected {
t.Fatalf(`Unexpected FETCH_YOUTUBE_WATCH_TIME value, got %v instead of %v`, result, expected)
}
}
func TestYouTubeEmbedUrlOverride(t *testing.T) {
os.Clearenv()
os.Setenv("YOUTUBE_EMBED_URL_OVERRIDE", "https://invidious.custom/embed/")
parser := NewParser()
opts, err := parser.ParseEnvironmentVariables()
if err != nil {
t.Fatalf(`Parsing failure: %v`, err)
}
expected := "https://invidious.custom/embed/"
result := opts.YouTubeEmbedUrlOverride()
if result != expected {
t.Fatalf(`Unexpected YOUTUBE_EMBED_URL_OVERRIDE value, got %v instead of %v`, result, expected)
}
}
func TestParseConfigDumpOutput(t *testing.T) {
os.Clearenv()
wantOpts := NewOptions()
wantOpts.adminUsername = "my-username"
serialized := wantOpts.String()
tmpfile, err := os.CreateTemp(".", "miniflux.*.unit_test.conf")
if err != nil {
t.Fatal(err)
}
if _, err := tmpfile.Write([]byte(serialized)); err != nil {
t.Fatal(err)
}
parser := NewParser()
parsedOpts, err := parser.ParseFile(tmpfile.Name())
if err != nil {
t.Errorf(`Parsing failure: %v`, err)
}
if parsedOpts.AdminUsername() != wantOpts.AdminUsername() {
t.Fatalf(`Unexpected ADMIN_USERNAME value, got %q instead of %q`, parsedOpts.AdminUsername(), wantOpts.AdminUsername())
}
if err := tmpfile.Close(); err != nil {
t.Fatal(err)
}
if err := os.Remove(tmpfile.Name()); err != nil {
t.Fatal(err)
}
}
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package config handles configuration management for the application.
*/
package config // import "miniflux.app/config"
+227 -69
View File
@@ -1,12 +1,14 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package config // import "miniflux.app/config"
import (
"crypto/rand"
"fmt"
"sort"
"strings"
"time"
"miniflux.app/version"
)
@@ -24,24 +26,31 @@ const (
defaultBasePath = ""
defaultWorkerPoolSize = 5
defaultPollingFrequency = 60
defaultBatchSize = 10
defaultBatchSize = 100
defaultPollingScheduler = "round_robin"
defaultSchedulerEntryFrequencyMinInterval = 5
defaultSchedulerEntryFrequencyMaxInterval = 24 * 60
defaultPollingParsingErrorLimit = 3
defaultRunMigrations = false
defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable"
defaultDatabaseMaxConns = 20
defaultDatabaseMinConns = 1
defaultDatabaseConnectionLifetime = 5
defaultListenAddr = "127.0.0.1:8080"
defaultCertFile = ""
defaultKeyFile = ""
defaultCertDomain = ""
defaultCertCache = "/tmp/cert_cache"
defaultCleanupFrequencyHours = 24
defaultCleanupArchiveReadDays = 60
defaultCleanupArchiveUnreadDays = 180
defaultCleanupArchiveBatchSize = 10000
defaultCleanupRemoveSessionsDays = 30
defaultProxyImages = "http-only"
defaultProxyHTTPClientTimeout = 120
defaultProxyOption = "http-only"
defaultProxyMediaTypes = "image"
defaultProxyUrl = ""
defaultFetchYouTubeWatchTime = false
defaultYouTubeEmbedUrlOverride = "https://www.youtube-nocookie.com/embed/"
defaultCreateAdmin = false
defaultAdminUsername = ""
defaultAdminPassword = ""
@@ -55,6 +64,7 @@ const (
defaultHTTPClientTimeout = 20
defaultHTTPClientMaxBodySize = 15
defaultHTTPClientProxy = ""
defaultHTTPServerTimeout = 300
defaultAuthProxyHeader = ""
defaultAuthProxyUserCreation = false
defaultMaintenanceMode = false
@@ -62,10 +72,20 @@ const (
defaultMetricsCollector = false
defaultMetricsRefreshInterval = 60
defaultMetricsAllowedNetworks = "127.0.0.1/8"
defaultMetricsUsername = ""
defaultMetricsPassword = ""
defaultWatchdog = true
defaultInvidiousInstance = "yewtu.be"
)
var defaultHTTPClientUserAgent = "Mozilla/5.0 (compatible; Miniflux/" + version.Version + "; +https://miniflux.app)"
// Option contains a key to value map of a single option. It may be used to output debug strings.
type Option struct {
Key string
Value interface{}
}
// Options contains configuration options.
type Options struct {
HTTPS bool
@@ -81,26 +101,33 @@ type Options struct {
databaseURL string
databaseMaxConns int
databaseMinConns int
databaseConnectionLifetime int
runMigrations bool
listenAddr string
certFile string
certDomain string
certCache string
certKeyFile string
cleanupFrequencyHours int
cleanupArchiveReadDays int
cleanupArchiveUnreadDays int
cleanupArchiveBatchSize int
cleanupRemoveSessionsDays int
pollingFrequency int
batchSize int
pollingScheduler string
schedulerEntryFrequencyMinInterval int
schedulerEntryFrequencyMaxInterval int
pollingParsingErrorLimit int
workerPoolSize int
createAdmin bool
adminUsername string
adminPassword string
proxyImages string
proxyHTTPClientTimeout int
proxyOption string
proxyMediaTypes []string
proxyUrl string
fetchYouTubeWatchTime bool
youTubeEmbedUrlOverride string
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
@@ -112,6 +139,7 @@ type Options struct {
httpClientMaxBodySize int64
httpClientProxy string
httpClientUserAgent string
httpServerTimeout int
authProxyHeader string
authProxyUserCreation bool
maintenanceMode bool
@@ -119,10 +147,18 @@ type Options struct {
metricsCollector bool
metricsRefreshInterval int
metricsAllowedNetworks []string
metricsUsername string
metricsPassword string
watchdog bool
invidiousInstance string
proxyPrivateKey []byte
}
// NewOptions returns Options with default values.
func NewOptions() *Options {
randomKey := make([]byte, 16)
rand.Read(randomKey)
return &Options{
HTTPS: defaultHTTPS,
logDateTime: defaultLogDateTime,
@@ -137,24 +173,31 @@ func NewOptions() *Options {
databaseURL: defaultDatabaseURL,
databaseMaxConns: defaultDatabaseMaxConns,
databaseMinConns: defaultDatabaseMinConns,
databaseConnectionLifetime: defaultDatabaseConnectionLifetime,
runMigrations: defaultRunMigrations,
listenAddr: defaultListenAddr,
certFile: defaultCertFile,
certDomain: defaultCertDomain,
certCache: defaultCertCache,
certKeyFile: defaultKeyFile,
cleanupFrequencyHours: defaultCleanupFrequencyHours,
cleanupArchiveReadDays: defaultCleanupArchiveReadDays,
cleanupArchiveUnreadDays: defaultCleanupArchiveUnreadDays,
cleanupArchiveBatchSize: defaultCleanupArchiveBatchSize,
cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays,
pollingFrequency: defaultPollingFrequency,
batchSize: defaultBatchSize,
pollingScheduler: defaultPollingScheduler,
schedulerEntryFrequencyMinInterval: defaultSchedulerEntryFrequencyMinInterval,
schedulerEntryFrequencyMaxInterval: defaultSchedulerEntryFrequencyMaxInterval,
pollingParsingErrorLimit: defaultPollingParsingErrorLimit,
workerPoolSize: defaultWorkerPoolSize,
createAdmin: defaultCreateAdmin,
proxyImages: defaultProxyImages,
proxyHTTPClientTimeout: defaultProxyHTTPClientTimeout,
proxyOption: defaultProxyOption,
proxyMediaTypes: []string{defaultProxyMediaTypes},
proxyUrl: defaultProxyUrl,
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
youTubeEmbedUrlOverride: defaultYouTubeEmbedUrlOverride,
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
oauth2ClientID: defaultOAuth2ClientID,
oauth2ClientSecret: defaultOAuth2ClientSecret,
@@ -166,6 +209,7 @@ func NewOptions() *Options {
httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024,
httpClientProxy: defaultHTTPClientProxy,
httpClientUserAgent: defaultHTTPClientUserAgent,
httpServerTimeout: defaultHTTPServerTimeout,
authProxyHeader: defaultAuthProxyHeader,
authProxyUserCreation: defaultAuthProxyUserCreation,
maintenanceMode: defaultMaintenanceMode,
@@ -173,6 +217,11 @@ func NewOptions() *Options {
metricsCollector: defaultMetricsCollector,
metricsRefreshInterval: defaultMetricsRefreshInterval,
metricsAllowedNetworks: []string{defaultMetricsAllowedNetworks},
metricsUsername: defaultMetricsUsername,
metricsPassword: defaultMetricsPassword,
watchdog: defaultWatchdog,
invidiousInstance: defaultInvidiousInstance,
proxyPrivateKey: randomKey,
}
}
@@ -236,6 +285,11 @@ func (o *Options) DatabaseMinConns() int {
return o.databaseMinConns
}
// DatabaseConnectionLifetime returns the maximum amount of time a connection may be reused.
func (o *Options) DatabaseConnectionLifetime() time.Duration {
return time.Duration(o.databaseConnectionLifetime) * time.Minute
}
// ListenAddr returns the listen address for the HTTP server.
func (o *Options) ListenAddr() string {
return o.listenAddr
@@ -256,11 +310,6 @@ func (o *Options) CertDomain() string {
return o.certDomain
}
// CertCache returns the directory to use for Let's Encrypt session cache.
func (o *Options) CertCache() string {
return o.certCache
}
// CleanupFrequencyHours returns the interval in hours for cleanup jobs.
func (o *Options) CleanupFrequencyHours() int {
return o.cleanupFrequencyHours
@@ -276,6 +325,11 @@ func (o *Options) CleanupArchiveUnreadDays() int {
return o.cleanupArchiveUnreadDays
}
// CleanupArchiveBatchSize returns the number of entries to archive for each interval.
func (o *Options) CleanupArchiveBatchSize() int {
return o.cleanupArchiveBatchSize
}
// CleanupRemoveSessionsDays returns the number of days after which to remove sessions.
func (o *Options) CleanupRemoveSessionsDays() int {
return o.cleanupRemoveSessionsDays
@@ -311,6 +365,11 @@ func (o *Options) SchedulerEntryFrequencyMinInterval() int {
return o.schedulerEntryFrequencyMinInterval
}
// PollingParsingErrorLimit returns the limit of errors when to stop polling.
func (o *Options) PollingParsingErrorLimit() int {
return o.pollingParsingErrorLimit
}
// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users.
func (o *Options) IsOAuth2UserCreationAllowed() bool {
return o.oauth2UserCreationAllowed
@@ -366,9 +425,35 @@ func (o *Options) AdminPassword() string {
return o.adminPassword
}
// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyImages() string {
return o.proxyImages
// FetchYouTubeWatchTime returns true if the YouTube video duration
// should be fetched and used as a reading time.
func (o *Options) FetchYouTubeWatchTime() bool {
return o.fetchYouTubeWatchTime
}
// YouTubeEmbedUrlOverride returns YouTube URL which will be used for embeds
func (o *Options) YouTubeEmbedUrlOverride() string {
return o.youTubeEmbedUrlOverride
}
// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyOption() string {
return o.proxyOption
}
// ProxyMediaTypes returns a slice of media types to proxy.
func (o *Options) ProxyMediaTypes() []string {
return o.proxyMediaTypes
}
// ProxyUrl returns a string of a URL to use to proxy image requests
func (o *Options) ProxyUrl() string {
return o.proxyUrl
}
// ProxyHTTPClientTimeout returns the time limit in seconds before the proxy HTTP client cancel the request.
func (o *Options) ProxyHTTPClientTimeout() int {
return o.proxyHTTPClientTimeout
}
// HasHTTPService returns true if the HTTP service is enabled.
@@ -404,6 +489,11 @@ func (o *Options) HTTPClientProxy() string {
return o.httpClientProxy
}
// HTTPServerTimeout returns the time limit in seconds before the HTTP server cancel the request.
func (o *Options) HTTPServerTimeout() int {
return o.httpServerTimeout
}
// HasHTTPClientProxyConfigured returns true if the HTTP proxy is configured.
func (o *Options) HasHTTPClientProxyConfigured() bool {
return o.httpClientProxy != ""
@@ -436,63 +526,131 @@ func (o *Options) MetricsAllowedNetworks() []string {
return o.metricsAllowedNetworks
}
func (o *Options) MetricsUsername() string {
return o.metricsUsername
}
func (o *Options) MetricsPassword() string {
return o.metricsPassword
}
// HTTPClientUserAgent returns the global User-Agent header for miniflux.
func (o *Options) HTTPClientUserAgent() string {
return o.httpClientUserAgent
}
// HasWatchdog returns true if the systemd watchdog is enabled.
func (o *Options) HasWatchdog() bool {
return o.watchdog
}
// InvidiousInstance returns the invidious instance used by miniflux
func (o *Options) InvidiousInstance() string {
return o.invidiousInstance
}
// ProxyPrivateKey returns the private key used by the media proxy
func (o *Options) ProxyPrivateKey() []byte {
return o.proxyPrivateKey
}
// SortedOptions returns options as a list of key value pairs, sorted by keys.
func (o *Options) SortedOptions(redactSecret bool) []*Option {
var keyValues = map[string]interface{}{
"ADMIN_PASSWORD": redactSecretValue(o.adminPassword, redactSecret),
"ADMIN_USERNAME": o.adminUsername,
"AUTH_PROXY_HEADER": o.authProxyHeader,
"AUTH_PROXY_USER_CREATION": o.authProxyUserCreation,
"BASE_PATH": o.basePath,
"BASE_URL": o.baseURL,
"BATCH_SIZE": o.batchSize,
"CERT_DOMAIN": o.certDomain,
"CERT_FILE": o.certFile,
"CLEANUP_ARCHIVE_BATCH_SIZE": o.cleanupArchiveBatchSize,
"CLEANUP_ARCHIVE_READ_DAYS": o.cleanupArchiveReadDays,
"CLEANUP_ARCHIVE_UNREAD_DAYS": o.cleanupArchiveUnreadDays,
"CLEANUP_FREQUENCY_HOURS": o.cleanupFrequencyHours,
"CLEANUP_REMOVE_SESSIONS_DAYS": o.cleanupRemoveSessionsDays,
"CREATE_ADMIN": o.createAdmin,
"DATABASE_CONNECTION_LIFETIME": o.databaseConnectionLifetime,
"DATABASE_MAX_CONNS": o.databaseMaxConns,
"DATABASE_MIN_CONNS": o.databaseMinConns,
"DATABASE_URL": redactSecretValue(o.databaseURL, redactSecret),
"DEBUG": o.debug,
"DISABLE_HSTS": !o.hsts,
"DISABLE_HTTP_SERVICE": !o.httpService,
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
"HTTPS": o.HTTPS,
"HTTP_CLIENT_MAX_BODY_SIZE": o.httpClientMaxBodySize,
"HTTP_CLIENT_PROXY": o.httpClientProxy,
"HTTP_CLIENT_TIMEOUT": o.httpClientTimeout,
"HTTP_CLIENT_USER_AGENT": o.httpClientUserAgent,
"HTTP_SERVER_TIMEOUT": o.httpServerTimeout,
"HTTP_SERVICE": o.httpService,
"INVIDIOUS_INSTANCE": o.invidiousInstance,
"KEY_FILE": o.certKeyFile,
"LISTEN_ADDR": o.listenAddr,
"LOG_DATE_TIME": o.logDateTime,
"MAINTENANCE_MESSAGE": o.maintenanceMessage,
"MAINTENANCE_MODE": o.maintenanceMode,
"METRICS_ALLOWED_NETWORKS": strings.Join(o.metricsAllowedNetworks, ","),
"METRICS_COLLECTOR": o.metricsCollector,
"METRICS_PASSWORD": redactSecretValue(o.metricsPassword, redactSecret),
"METRICS_REFRESH_INTERVAL": o.metricsRefreshInterval,
"METRICS_USERNAME": o.metricsUsername,
"OAUTH2_CLIENT_ID": o.oauth2ClientID,
"OAUTH2_CLIENT_SECRET": redactSecretValue(o.oauth2ClientSecret, redactSecret),
"OAUTH2_OIDC_DISCOVERY_ENDPOINT": o.oauth2OidcDiscoveryEndpoint,
"OAUTH2_PROVIDER": o.oauth2Provider,
"OAUTH2_REDIRECT_URL": o.oauth2RedirectURL,
"OAUTH2_USER_CREATION": o.oauth2UserCreationAllowed,
"POCKET_CONSUMER_KEY": redactSecretValue(o.pocketConsumerKey, redactSecret),
"POLLING_FREQUENCY": o.pollingFrequency,
"POLLING_PARSING_ERROR_LIMIT": o.pollingParsingErrorLimit,
"POLLING_SCHEDULER": o.pollingScheduler,
"PROXY_HTTP_CLIENT_TIMEOUT": o.proxyHTTPClientTimeout,
"PROXY_MEDIA_TYPES": o.proxyMediaTypes,
"PROXY_OPTION": o.proxyOption,
"PROXY_PRIVATE_KEY": redactSecretValue(string(o.proxyPrivateKey), redactSecret),
"PROXY_URL": o.proxyUrl,
"ROOT_URL": o.rootURL,
"RUN_MIGRATIONS": o.runMigrations,
"SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL": o.schedulerEntryFrequencyMaxInterval,
"SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL": o.schedulerEntryFrequencyMinInterval,
"SCHEDULER_SERVICE": o.schedulerService,
"SERVER_TIMING_HEADER": o.serverTimingHeader,
"WATCHDOG": o.watchdog,
"WORKER_POOL_SIZE": o.workerPoolSize,
"YOUTUBE_EMBED_URL_OVERRIDE": o.youTubeEmbedUrlOverride,
}
keys := make([]string, 0, len(keyValues))
for key := range keyValues {
keys = append(keys, key)
}
sort.Strings(keys)
var sortedOptions []*Option
for _, key := range keys {
sortedOptions = append(sortedOptions, &Option{Key: key, Value: keyValues[key]})
}
return sortedOptions
}
func (o *Options) String() string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("LOG_DATE_TIME: %v\n", o.logDateTime))
builder.WriteString(fmt.Sprintf("DEBUG: %v\n", o.debug))
builder.WriteString(fmt.Sprintf("SERVER_TIMING_HEADER: %v\n", o.serverTimingHeader))
builder.WriteString(fmt.Sprintf("HTTP_SERVICE: %v\n", o.httpService))
builder.WriteString(fmt.Sprintf("SCHEDULER_SERVICE: %v\n", o.schedulerService))
builder.WriteString(fmt.Sprintf("HTTPS: %v\n", o.HTTPS))
builder.WriteString(fmt.Sprintf("HSTS: %v\n", o.hsts))
builder.WriteString(fmt.Sprintf("BASE_URL: %v\n", o.baseURL))
builder.WriteString(fmt.Sprintf("ROOT_URL: %v\n", o.rootURL))
builder.WriteString(fmt.Sprintf("BASE_PATH: %v\n", o.basePath))
builder.WriteString(fmt.Sprintf("LISTEN_ADDR: %v\n", o.listenAddr))
builder.WriteString(fmt.Sprintf("DATABASE_URL: %v\n", o.databaseURL))
builder.WriteString(fmt.Sprintf("DATABASE_MAX_CONNS: %v\n", o.databaseMaxConns))
builder.WriteString(fmt.Sprintf("DATABASE_MIN_CONNS: %v\n", o.databaseMinConns))
builder.WriteString(fmt.Sprintf("RUN_MIGRATIONS: %v\n", o.runMigrations))
builder.WriteString(fmt.Sprintf("CERT_FILE: %v\n", o.certFile))
builder.WriteString(fmt.Sprintf("KEY_FILE: %v\n", o.certKeyFile))
builder.WriteString(fmt.Sprintf("CERT_DOMAIN: %v\n", o.certDomain))
builder.WriteString(fmt.Sprintf("CERT_CACHE: %v\n", o.certCache))
builder.WriteString(fmt.Sprintf("CLEANUP_FREQUENCY_HOURS: %v\n", o.cleanupFrequencyHours))
builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_READ_DAYS: %v\n", o.cleanupArchiveReadDays))
builder.WriteString(fmt.Sprintf("CLEANUP_ARCHIVE_UNREAD_DAYS: %v\n", o.cleanupArchiveUnreadDays))
builder.WriteString(fmt.Sprintf("CLEANUP_REMOVE_SESSIONS_DAYS: %v\n", o.cleanupRemoveSessionsDays))
builder.WriteString(fmt.Sprintf("WORKER_POOL_SIZE: %v\n", o.workerPoolSize))
builder.WriteString(fmt.Sprintf("POLLING_FREQUENCY: %v\n", o.pollingFrequency))
builder.WriteString(fmt.Sprintf("BATCH_SIZE: %v\n", o.batchSize))
builder.WriteString(fmt.Sprintf("POLLING_SCHEDULER: %v\n", o.pollingScheduler))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MAX_INTERVAL: %v\n", o.schedulerEntryFrequencyMaxInterval))
builder.WriteString(fmt.Sprintf("SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL: %v\n", o.schedulerEntryFrequencyMinInterval))
builder.WriteString(fmt.Sprintf("PROXY_IMAGES: %v\n", o.proxyImages))
builder.WriteString(fmt.Sprintf("CREATE_ADMIN: %v\n", o.createAdmin))
builder.WriteString(fmt.Sprintf("ADMIN_USERNAME: %v\n", o.adminUsername))
builder.WriteString(fmt.Sprintf("ADMIN_PASSWORD: %v\n", o.adminPassword))
builder.WriteString(fmt.Sprintf("POCKET_CONSUMER_KEY: %v\n", o.pocketConsumerKey))
builder.WriteString(fmt.Sprintf("OAUTH2_USER_CREATION: %v\n", o.oauth2UserCreationAllowed))
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID))
builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret))
builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL))
builder.WriteString(fmt.Sprintf("OAUTH2_OIDC_DISCOVERY_ENDPOINT: %v\n", o.oauth2OidcDiscoveryEndpoint))
builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider))
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout))
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize))
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_PROXY: %v\n", o.httpClientProxy))
builder.WriteString(fmt.Sprintf("HTTP_CLIENT_USER_AGENT: %v\n", o.httpClientUserAgent))
builder.WriteString(fmt.Sprintf("AUTH_PROXY_HEADER: %v\n", o.authProxyHeader))
builder.WriteString(fmt.Sprintf("AUTH_PROXY_USER_CREATION: %v\n", o.authProxyUserCreation))
builder.WriteString(fmt.Sprintf("MAINTENANCE_MODE: %v\n", o.maintenanceMode))
builder.WriteString(fmt.Sprintf("MAINTENANCE_MESSAGE: %v\n", o.maintenanceMessage))
builder.WriteString(fmt.Sprintf("METRICS_COLLECTOR: %v\n", o.metricsCollector))
builder.WriteString(fmt.Sprintf("METRICS_REFRESH_INTERVAL: %v\n", o.metricsRefreshInterval))
builder.WriteString(fmt.Sprintf("METRICS_ALLOWED_NETWORKS: %v\n", o.metricsAllowedNetworks))
for _, option := range o.SortedOptions(false) {
fmt.Fprintf(&builder, "%s=%v\n", option.Key, option.Value)
}
return builder.String()
}
func redactSecretValue(value string, redactSecret bool) string {
if redactSecret && value != "" {
return "<secret>"
}
return value
}
+63 -11
View File
@@ -1,16 +1,15 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package config // import "miniflux.app/config"
import (
"bufio"
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"io/ioutil"
url_parser "net/url"
"os"
"strconv"
@@ -96,6 +95,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.databaseMaxConns = parseInt(value, defaultDatabaseMaxConns)
case "DATABASE_MIN_CONNS":
p.opts.databaseMinConns = parseInt(value, defaultDatabaseMinConns)
case "DATABASE_CONNECTION_LIFETIME":
p.opts.databaseConnectionLifetime = parseInt(value, defaultDatabaseConnectionLifetime)
case "RUN_MIGRATIONS":
p.opts.runMigrations = parseBool(value, defaultRunMigrations)
case "DISABLE_HSTS":
@@ -112,14 +113,14 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.certKeyFile = parseString(value, defaultKeyFile)
case "CERT_DOMAIN":
p.opts.certDomain = parseString(value, defaultCertDomain)
case "CERT_CACHE":
p.opts.certCache = parseString(value, defaultCertCache)
case "CLEANUP_FREQUENCY_HOURS":
p.opts.cleanupFrequencyHours = parseInt(value, defaultCleanupFrequencyHours)
case "CLEANUP_ARCHIVE_READ_DAYS":
p.opts.cleanupArchiveReadDays = parseInt(value, defaultCleanupArchiveReadDays)
case "CLEANUP_ARCHIVE_UNREAD_DAYS":
p.opts.cleanupArchiveUnreadDays = parseInt(value, defaultCleanupArchiveUnreadDays)
case "CLEANUP_ARCHIVE_BATCH_SIZE":
p.opts.cleanupArchiveBatchSize = parseInt(value, defaultCleanupArchiveBatchSize)
case "CLEANUP_REMOVE_SESSIONS_DAYS":
p.opts.cleanupRemoveSessionsDays = parseInt(value, defaultCleanupRemoveSessionsDays)
case "WORKER_POOL_SIZE":
@@ -134,8 +135,22 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.schedulerEntryFrequencyMaxInterval = parseInt(value, defaultSchedulerEntryFrequencyMaxInterval)
case "SCHEDULER_ENTRY_FREQUENCY_MIN_INTERVAL":
p.opts.schedulerEntryFrequencyMinInterval = parseInt(value, defaultSchedulerEntryFrequencyMinInterval)
case "POLLING_PARSING_ERROR_LIMIT":
p.opts.pollingParsingErrorLimit = parseInt(value, defaultPollingParsingErrorLimit)
// kept for compatibility purpose
case "PROXY_IMAGES":
p.opts.proxyImages = parseString(value, defaultProxyImages)
p.opts.proxyOption = parseString(value, defaultProxyOption)
case "PROXY_HTTP_CLIENT_TIMEOUT":
p.opts.proxyHTTPClientTimeout = parseInt(value, defaultProxyHTTPClientTimeout)
case "PROXY_OPTION":
p.opts.proxyOption = parseString(value, defaultProxyOption)
case "PROXY_MEDIA_TYPES":
p.opts.proxyMediaTypes = parseStringList(value, []string{defaultProxyMediaTypes})
// kept for compatibility purpose
case "PROXY_IMAGE_URL":
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
case "PROXY_URL":
p.opts.proxyUrl = parseString(value, defaultProxyUrl)
case "CREATE_ADMIN":
p.opts.createAdmin = parseBool(value, defaultCreateAdmin)
case "ADMIN_USERNAME":
@@ -174,6 +189,8 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.httpClientProxy = parseString(value, defaultHTTPClientProxy)
case "HTTP_CLIENT_USER_AGENT":
p.opts.httpClientUserAgent = parseString(value, defaultHTTPClientUserAgent)
case "HTTP_SERVER_TIMEOUT":
p.opts.httpServerTimeout = parseInt(value, defaultHTTPServerTimeout)
case "AUTH_PROXY_HEADER":
p.opts.authProxyHeader = parseString(value, defaultAuthProxyHeader)
case "AUTH_PROXY_USER_CREATION":
@@ -188,6 +205,26 @@ func (p *Parser) parseLines(lines []string) (err error) {
p.opts.metricsRefreshInterval = parseInt(value, defaultMetricsRefreshInterval)
case "METRICS_ALLOWED_NETWORKS":
p.opts.metricsAllowedNetworks = parseStringList(value, []string{defaultMetricsAllowedNetworks})
case "METRICS_USERNAME":
p.opts.metricsUsername = parseString(value, defaultMetricsUsername)
case "METRICS_USERNAME_FILE":
p.opts.metricsUsername = readSecretFile(value, defaultMetricsUsername)
case "METRICS_PASSWORD":
p.opts.metricsPassword = parseString(value, defaultMetricsPassword)
case "METRICS_PASSWORD_FILE":
p.opts.metricsPassword = readSecretFile(value, defaultMetricsPassword)
case "FETCH_YOUTUBE_WATCH_TIME":
p.opts.fetchYouTubeWatchTime = parseBool(value, defaultFetchYouTubeWatchTime)
case "YOUTUBE_EMBED_URL_OVERRIDE":
p.opts.youTubeEmbedUrlOverride = parseString(value, defaultYouTubeEmbedUrlOverride)
case "WATCHDOG":
p.opts.watchdog = parseBool(value, defaultWatchdog)
case "INVIDIOUS_INSTANCE":
p.opts.invidiousInstance = parseString(value, defaultInvidiousInstance)
case "PROXY_PRIVATE_KEY":
randomKey := make([]byte, 16)
rand.Read(randomKey)
p.opts.proxyPrivateKey = parseBytes(value, randomKey)
}
}
@@ -208,12 +245,12 @@ func parseBaseURL(value string) (string, string, string, error) {
url, err := url_parser.Parse(value)
if err != nil {
return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err)
return "", "", "", fmt.Errorf("config: invalid BASE_URL: %w", err)
}
scheme := strings.ToLower(url.Scheme)
if scheme != "https" && scheme != "http" {
return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https")
return "", "", "", errors.New("config: invalid BASE_URL: scheme must be http or https")
}
basePath := url.Path
@@ -260,16 +297,31 @@ func parseStringList(value string, fallback []string) []string {
}
var strList []string
strMap := make(map[string]bool)
items := strings.Split(value, ",")
for _, item := range items {
strList = append(strList, strings.TrimSpace(item))
itemValue := strings.TrimSpace(item)
if _, found := strMap[itemValue]; !found {
strMap[itemValue] = true
strList = append(strList, itemValue)
}
}
return strList
}
func parseBytes(value string, fallback []byte) []byte {
if value == "" {
return fallback
}
return []byte(value)
}
func readSecretFile(filename, fallback string) string {
data, err := ioutil.ReadFile(filename)
data, err := os.ReadFile(filename)
if err != nil {
return fallback
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2019 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package config // import "miniflux.app/config"
+1 -2
View File
@@ -8,6 +8,5 @@ Here are few Docker Compose examples:
- `traefik.yml`: Use Traefik as reverse-proxy with automatic HTTPS
```bash
docker-compose -f basic.yml up -d db
docker-compose -f basic.yml up
docker compose -f basic.yml up -d
```
+14 -5
View File
@@ -1,25 +1,34 @@
version: '3.3'
services:
miniflux:
image: miniflux/miniflux:latest
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
container_name: miniflux
restart: always
ports:
- "80:8080"
depends_on:
- db
db:
condition: service_healthy
environment:
- DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
- RUN_MIGRATIONS=1
- CREATE_ADMIN=1
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=test123
- DEBUG=1
# Optional health check:
# healthcheck:
# test: ["CMD", "/usr/bin/miniflux", "-healthcheck", "auto"]
db:
image: postgres:latest
image: postgres:15
container_name: postgres
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=secret
volumes:
- miniflux-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
interval: 10s
start_period: 30s
volumes:
miniflux-db:
miniflux-db:
+8 -4
View File
@@ -1,4 +1,3 @@
version: '3.3'
services:
caddy:
image: caddy:2
@@ -13,10 +12,11 @@ services:
- caddy_data:/data
- caddy_config:/config
miniflux:
image: miniflux/miniflux:latest
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
container_name: miniflux
depends_on:
- db
db:
condition: service_healthy
environment:
- DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
- RUN_MIGRATIONS=1
@@ -25,13 +25,17 @@ services:
- ADMIN_PASSWORD=test123
- BASE_URL=https://miniflux.example.org
db:
image: postgres:latest
image: postgres:15
container_name: postgres
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=secret
volumes:
- miniflux-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
interval: 10s
start_period: 30s
volumes:
miniflux-db:
caddy_data:
+8 -4
View File
@@ -1,4 +1,3 @@
version: '3.3'
services:
traefik:
image: "traefik:v2.3"
@@ -18,10 +17,11 @@ services:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
miniflux:
image: miniflux/miniflux:latest
image: ${MINIFLUX_IMAGE:-miniflux/miniflux:latest}
container_name: miniflux
depends_on:
- db
db:
condition: service_healthy
expose:
- "8080"
environment:
@@ -37,12 +37,16 @@ services:
- "traefik.http.routers.miniflux.entrypoints=websecure"
- "traefik.http.routers.miniflux.tls.certresolver=myresolver"
db:
image: postgres:latest
image: postgres:15
container_name: postgres
environment:
- POSTGRES_USER=miniflux
- POSTGRES_PASSWORD=secret
volumes:
- miniflux-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "miniflux"]
interval: 10s
start_period: 30s
volumes:
miniflux-db:
+31 -31
View File
@@ -20,7 +20,7 @@
"panels": [
{
"collapsed": false,
"datasource": null,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
@@ -104,7 +104,7 @@
"type": "bargauge"
},
{
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
@@ -289,7 +289,7 @@
}
},
{
"datasource": null,
"datasource": "Prometheus",
"description": "",
"fieldConfig": {
"defaults": {
@@ -332,7 +332,7 @@
"pluginVersion": "7.1.5",
"targets": [
{
"expr": "go_memstats_sys_bytes{app=\"miniflux\"}",
"expr": "go_memstats_sys_bytes{job=\"miniflux\"}",
"interval": "",
"legendFormat": "{{ instance }} - Memory Used",
"refId": "A"
@@ -348,7 +348,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
@@ -440,7 +440,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
@@ -529,7 +529,7 @@
},
{
"collapsed": false,
"datasource": null,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
@@ -546,7 +546,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
@@ -590,7 +590,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_memstats_sys_bytes{app=\"miniflux\"}",
"expr": "go_memstats_sys_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -699,7 +699,7 @@
"steppedLine": false,
"targets": [
{
"expr": "process_open_fds{app=\"miniflux\"}",
"expr": "process_open_fds{job=\"miniflux\"}",
"interval": "",
"legendFormat": "{{instance }} - Open File Descriptors",
"refId": "A"
@@ -748,7 +748,7 @@
},
{
"collapsed": false,
"datasource": null,
"datasource": "Prometheus",
"gridPos": {
"h": 1,
"w": 24,
@@ -816,7 +816,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_memstats_alloc_bytes{app=\"miniflux\"}",
"expr": "go_memstats_alloc_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 2,
@@ -826,7 +826,7 @@
"step": 4
},
{
"expr": "rate(go_memstats_alloc_bytes_total{app=\"miniflux\"}[30s])",
"expr": "rate(go_memstats_alloc_bytes_total{job=\"miniflux\"}[30s])",
"format": "time_series",
"interval": "",
"intervalFactor": 2,
@@ -836,7 +836,7 @@
"step": 4
},
{
"expr": "go_memstats_stack_inuse_bytes{app=\"miniflux\"}",
"expr": "go_memstats_stack_inuse_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 2,
@@ -846,7 +846,7 @@
"step": 4
},
{
"expr": "go_memstats_heap_inuse_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_inuse_bytes{job=\"miniflux\"}",
"format": "time_series",
"hide": false,
"interval": "",
@@ -904,7 +904,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {}
@@ -945,13 +945,13 @@
"steppedLine": false,
"targets": [
{
"expr": "go_goroutines{app=\"miniflux\"}",
"expr": "go_goroutines{job=\"miniflux\"}",
"interval": "",
"legendFormat": "{{ instance }} - Goroutines",
"refId": "A"
},
{
"expr": "go_threads{app=\"miniflux\"}",
"expr": "go_threads{job=\"miniflux\"}",
"interval": "",
"legendFormat": "{{ instance }} - OS threads",
"refId": "B"
@@ -1003,7 +1003,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
@@ -1046,7 +1046,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_memstats_stack_inuse_bytes{app=\"miniflux\"}",
"expr": "go_memstats_stack_inuse_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1054,7 +1054,7 @@
"refId": "A"
},
{
"expr": "go_memstats_stack_sys_bytes{app=\"miniflux\"}",
"expr": "go_memstats_stack_sys_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1108,7 +1108,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
@@ -1150,7 +1150,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_memstats_heap_alloc_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_alloc_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1158,7 +1158,7 @@
"refId": "B"
},
{
"expr": "go_memstats_heap_sys_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_sys_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1166,7 +1166,7 @@
"refId": "A"
},
{
"expr": "go_memstats_heap_idle_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_idle_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1174,7 +1174,7 @@
"refId": "C"
},
{
"expr": "go_memstats_heap_inuse_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_inuse_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1182,7 +1182,7 @@
"refId": "D"
},
{
"expr": "go_memstats_heap_released_bytes{app=\"miniflux\"}",
"expr": "go_memstats_heap_released_bytes{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1282,7 +1282,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_gc_duration_seconds{app=\"miniflux\"}",
"expr": "go_gc_duration_seconds{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 2,
@@ -1339,7 +1339,7 @@
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"datasource": "Prometheus",
"fieldConfig": {
"defaults": {
"custom": {},
@@ -1383,7 +1383,7 @@
"steppedLine": false,
"targets": [
{
"expr": "go_memstats_mallocs_total{app=\"miniflux\"} - go_memstats_frees_total{app=\"miniflux\"}",
"expr": "go_memstats_mallocs_total{job=\"miniflux\"} - go_memstats_frees_total{job=\"miniflux\"}",
"format": "time_series",
"interval": "",
"intervalFactor": 1,
@@ -1462,4 +1462,4 @@
"title": "Miniflux",
"uid": "vSaPgcFMk",
"version": 23
}
}
+7
View File
@@ -0,0 +1,7 @@
System-V init for e.g. http://devuan.org
Assumes an executable `/usr/local/bin/miniflux`.
Configure in `etc/default/miniflux`
+6
View File
@@ -0,0 +1,6 @@
# sourced by /etc/init.d/miniflux
# see cluster port in pg_lsclusters and ls -Al /var/run/postgresql/
export DATABASE_URL='host=/var/run/postgresql/ port=5433 user=miniflux password=<my secrect db password> dbname=miniflux sslmode=disable'
export LISTEN_ADDR='127.0.0.1:8081'
export BASE_URL='https://<my miniflux domain> and path/'
+127
View File
@@ -0,0 +1,127 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: miniflux
# Required-Start: $syslog $network
# Required-Stop: $syslog
# Should-Start: postgresql
# Should-Stop: postgresql
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: A rss reader
# Description: A RSS reader
### END INIT INFO
# Author: Danny Boisvert
# Do NOT "set -e"
# PATH should only include /usr/* if it runs after the mountnfs.sh script
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Miniflux"
NAME=miniflux
SERVICEVERBOSE=yes
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME
WORKINGDIR=/usr/local/bin
DAEMON=$WORKINGDIR/$NAME
DAEMON_ARGS=""
USER=nobody
# Read configuration variable file if it is present
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0
# Load the VERBOSE setting and other rcS variables
. /lib/init/vars.sh
# Define LSB log_* functions.
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
# and status_of_proc is working.
. /lib/lsb/init-functions
#
# Function that starts the daemon/service
#
do_start()
{
# Return
# 0 if daemon has been started
# 1 if daemon was already running
# 2 if daemon could not be started
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
--test --chdir $WORKINGDIR --chuid $USER \\
--exec $DAEMON -- $DAEMON_ARGS > /dev/null \\
|| return 1"
sh -c "USER=$USER start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile \\
--background --chdir $WORKINGDIR --chuid $USER \\
--exec $DAEMON -- $DAEMON_ARGS \\
|| return 2"
}
#
# Function that stops the daemon/service
#
do_stop()
{
# Return
# 0 if daemon has been stopped
# 1 if daemon was already stopped
# 2 if daemon could not be stopped
# other if a failure occurred
start-stop-daemon --stop --quiet --retry=TERM/1/KILL/5 --pidfile $PIDFILE --name $NAME
RETVAL="$?"
[ "$RETVAL" = 2 ] && return 2
start-stop-daemon --stop --quiet --oknodo --retry=0/1/KILL/5 --exec $DAEMON
[ "$?" = 2 ] && return 2
# Many daemons don't delete their pidfiles when they exit.
rm -f $PIDFILE
return "$RETVAL"
}
case "$1" in
start)
[ "$SERVICEVERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
do_start
case "$?" in
0|1) [ "$SERVICEVERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$SERVICEVERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
stop)
[ "$SERVICEVERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
do_stop
case "$?" in
0|1) [ "$SERVICEVERBOSE" != no ] && log_end_msg 0 ;;
2) [ "$SERVICEVERBOSE" != no ] && log_end_msg 1 ;;
esac
;;
status)
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
;;
restart|force-reload)
log_daemon_msg "Restarting $DESC" "$NAME"
do_stop
case "$?" in
0|1)
do_start
case "$?" in
0) log_end_msg 0 ;;
1) log_end_msg 1 ;; # Old process is still running
*) log_end_msg 1 ;; # Failed to start
esac
;;
*)
# Failed to stop
log_end_msg 1
;;
esac
;;
*)
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
exit 3
;;
esac
+9 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package crypto // import "miniflux.app/crypto"
@@ -10,6 +9,8 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"golang.org/x/crypto/bcrypt"
)
// HashFromBytes returns a SHA-256 checksum of the input.
@@ -42,3 +43,8 @@ func GenerateRandomString(size int) string {
func GenerateRandomStringHex(size int) string {
return hex.EncodeToString(GenerateRandomBytes(size))
}
func HashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package crypto implements helpers related to cryptography.
*/
package crypto // import "miniflux.app/crypto"
+5 -4
View File
@@ -1,19 +1,19 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package database // import "miniflux.app/database"
import (
"database/sql"
"fmt"
"time"
// Postgresql driver import
_ "github.com/lib/pq"
)
// NewConnectionPool configures the database connection pool.
func NewConnectionPool(dsn string, minConnections, maxConnections int) (*sql.DB, error) {
func NewConnectionPool(dsn string, minConnections, maxConnections int, connectionLifetime time.Duration) (*sql.DB, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
@@ -21,6 +21,7 @@ func NewConnectionPool(dsn string, minConnections, maxConnections int) (*sql.DB,
db.SetMaxOpenConns(maxConnections)
db.SetMaxIdleConns(minConnections)
db.SetConnMaxLifetime(connectionLifetime)
return db, nil
}
+235 -15
View File
@@ -1,6 +1,5 @@
// Copyright 2020 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package database // import "miniflux.app/database"
@@ -10,13 +9,14 @@ import (
var schemaVersion = len(migrations)
// Order is important. Add new migrations at the end of the list.
var migrations = []func(tx *sql.Tx) error{
func(tx *sql.Tx) (err error) {
sql := `
CREATE TABLE schema_version (
version text not null
);
CREATE TABLE users (
id serial not null,
username text not null unique,
@@ -28,7 +28,7 @@ var migrations = []func(tx *sql.Tx) error{
last_login_at timestamp with time zone,
primary key (id)
);
CREATE TABLE sessions (
id serial not null,
user_id int not null,
@@ -40,7 +40,7 @@ var migrations = []func(tx *sql.Tx) error{
unique (user_id, token),
foreign key (user_id) references users(id) on delete cascade
);
CREATE TABLE categories (
id serial not null,
user_id int not null,
@@ -49,7 +49,7 @@ var migrations = []func(tx *sql.Tx) error{
unique (user_id, title),
foreign key (user_id) references users(id) on delete cascade
);
CREATE TABLE feeds (
id bigserial not null,
user_id int not null,
@@ -67,9 +67,9 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade,
foreign key (category_id) references categories(id) on delete cascade
);
CREATE TYPE entry_status as enum('unread', 'read', 'removed');
CREATE TABLE entries (
id bigserial not null,
user_id int not null,
@@ -86,9 +86,9 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade,
foreign key (feed_id) references feeds(id) on delete cascade
);
CREATE INDEX entries_feed_idx on entries using btree(feed_id);
CREATE TABLE enclosures (
id bigserial not null,
user_id int not null,
@@ -100,7 +100,7 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade,
foreign key (entry_id) references entries(id) on delete cascade
);
CREATE TABLE icons (
id bigserial not null,
hash text not null unique,
@@ -108,14 +108,14 @@ var migrations = []func(tx *sql.Tx) error{
content bytea not null,
primary key (id)
);
CREATE TABLE feed_icons (
feed_id bigint not null,
icon_id bigint not null,
primary key(feed_id, icon_id),
foreign key (feed_id) references feeds(id) on delete cascade,
foreign key (icon_id) references icons(id) on delete cascade
);
);
`
_, err = tx.Exec(sql)
return err
@@ -409,7 +409,7 @@ var migrations = []func(tx *sql.Tx) error{
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE feeds
ALTER TABLE feeds
ADD COLUMN blocklist_rules text not null default '',
ADD COLUMN keeplist_rules text not null default ''
`
@@ -496,4 +496,224 @@ var migrations = []func(tx *sql.Tx) error{
`)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
CREATE INDEX entries_feed_url_idx ON entries(feed_id, url);
CREATE INDEX entries_user_status_feed_idx ON entries(user_id, status, feed_id);
CREATE INDEX entries_user_status_changed_idx ON entries(user_id, status, changed_at);
`)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
CREATE TABLE acme_cache (
key varchar(400) not null primary key,
data bytea not null,
updated_at timestamptz not null
);
`)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE feeds ADD COLUMN allow_self_signed_certificates boolean not null default false
`)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `ALTER TABLE feeds ADD COLUMN cookie text default ''`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
`)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE feeds ADD COLUMN hide_globally boolean not null default false
`)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN telegram_bot_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN telegram_bot_token text default '';
ALTER TABLE integrations ADD COLUMN telegram_bot_chat_id text default '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN googlereader_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN googlereader_username text default '';
ALTER TABLE integrations ADD COLUMN googlereader_password text default '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN espial_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN espial_url text default '';
ALTER TABLE integrations ADD COLUMN espial_api_key text default '';
ALTER TABLE integrations ADD COLUMN espial_tags text default 'miniflux';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN linkding_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN linkding_url text default '';
ALTER TABLE integrations ADD COLUMN linkding_api_key text default '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE feeds ADD COLUMN url_rewrite_rules text not null default ''
`)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE users ADD COLUMN default_reading_speed int default 265;
ALTER TABLE users ADD COLUMN cjk_reading_speed int default 500;
`)
return
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
`)
return
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE integrations ADD COLUMN wallabag_only_url bool default 'f';
`)
return
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE users ADD COLUMN categories_sorting_order text not null default 'unread_count';
`)
return
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN matrix_bot_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN matrix_bot_user text default '';
ALTER TABLE integrations ADD COLUMN matrix_bot_password text default '';
ALTER TABLE integrations ADD COLUMN matrix_bot_url text default '';
ALTER TABLE integrations ADD COLUMN matrix_bot_chat_id text default '';
`
_, err = tx.Exec(sql)
return
},
func(tx *sql.Tx) (err error) {
sql := `ALTER TABLE users ADD COLUMN double_tap boolean default 't'`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
_, err = tx.Exec(`
ALTER TABLE entries ADD COLUMN tags text[] default '{}';
`)
return
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE users RENAME double_tap TO gesture_nav;
ALTER TABLE users ALTER COLUMN gesture_nav SET DATA TYPE text using case when gesture_nav = true then 'tap' when gesture_nav = false then 'none' end;
ALTER TABLE users ALTER COLUMN gesture_nav SET default 'tap';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN linkding_tags text default '';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE feeds ADD COLUMN no_media_player boolean default 'f';
ALTER TABLE enclosures ADD COLUMN media_progression int default 0;
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN linkding_mark_as_unread bool default 'f';
`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
// Delete duplicated rows
sql := `
DELETE FROM enclosures a USING enclosures b
WHERE a.id < b.id
AND a.user_id = b.user_id
AND a.entry_id = b.entry_id
AND a.url = b.url;
`
_, err = tx.Exec(sql)
if err != nil {
return err
}
// Remove previous index
_, err = tx.Exec(`DROP INDEX enclosures_user_entry_url_idx`)
if err != nil {
return err
}
// Create unique index
_, err = tx.Exec(`CREATE UNIQUE INDEX enclosures_user_entry_url_unique_idx ON enclosures(user_id, entry_id, md5(url))`)
if err != nil {
return err
}
return nil
},
func(tx *sql.Tx) (err error) {
sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
_, err = tx.Exec(sql)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN notion_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN notion_token text default '';
ALTER TABLE integrations ADD COLUMN notion_page_id text default '';
`
_, err = tx.Exec(sql)
return err
},
}
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Miniflux is a feed reader application.
*/
package main // import "miniflux.app"
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package errors handles localized errors.
*/
package errors // import "miniflux.app/errors"
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package errors // import "miniflux.app/errors"
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package fever implements Fever API endpoints.
*/
package fever // import "miniflux.app/fever"
+53 -55
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package fever // import "miniflux.app/fever"
@@ -15,6 +14,7 @@ import (
"miniflux.app/integration"
"miniflux.app/logger"
"miniflux.app/model"
"miniflux.app/proxy"
"miniflux.app/storage"
"github.com/gorilla/mux"
@@ -22,7 +22,7 @@ import (
// Serve handles Fever API calls.
func Serve(router *mux.Router, store *storage.Storage) {
handler := &handler{store}
handler := &handler{store, router}
sr := router.PathPrefix("/fever").Subrouter()
sr.Use(newMiddleware(store).serve)
@@ -30,7 +30,8 @@ func Serve(router *mux.Router, store *storage.Storage) {
}
type handler struct {
store *storage.Storage
store *storage.Storage
router *mux.Router
}
func (h *handler) serve(w http.ResponseWriter, r *http.Request) {
@@ -61,13 +62,13 @@ func (h *handler) serve(w http.ResponseWriter, r *http.Request) {
/*
A request with the groups argument will return two additional members:
groups contains an array of group objects
feeds_groups contains an array of feeds_group objects
groups contains an array of group objects
feeds_groups contains an array of feeds_group objects
A group object has the following members:
id (positive integer)
title (utf-8 string)
id (positive integer)
title (utf-8 string)
The feeds_group object is documented under “Feeds/Groups Relationships.”
@@ -76,7 +77,6 @@ an is_spark equal to 0.
The “Sparks” super group is not included in this response and is composed of all feeds with an
is_spark equal to 1.
*/
func (h *handler) handleGroups(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
@@ -107,18 +107,18 @@ func (h *handler) handleGroups(w http.ResponseWriter, r *http.Request) {
/*
A request with the feeds argument will return two additional members:
feeds contains an array of group objects
feeds_groups contains an array of feeds_group objects
feeds contains an array of group objects
feeds_groups contains an array of feeds_group objects
A feed object has the following members:
id (positive integer)
favicon_id (positive integer)
title (utf-8 string)
url (utf-8 string)
site_url (utf-8 string)
is_spark (boolean integer)
last_updated_on_time (Unix timestamp/integer)
id (positive integer)
favicon_id (positive integer)
title (utf-8 string)
url (utf-8 string)
site_url (utf-8 string)
is_spark (boolean integer)
last_updated_on_time (Unix timestamp/integer)
The feeds_group object is documented under “Feeds/Groups Relationships.”
@@ -165,12 +165,12 @@ func (h *handler) handleFeeds(w http.ResponseWriter, r *http.Request) {
/*
A request with the favicons argument will return one additional member:
favicons contains an array of favicon objects
favicons contains an array of favicon objects
A favicon object has the following members:
id (positive integer)
data (base64 encoded image data; prefixed by image type)
id (positive integer)
data (base64 encoded image data; prefixed by image type)
An example data value:
@@ -206,20 +206,20 @@ func (h *handler) handleFavicons(w http.ResponseWriter, r *http.Request) {
/*
A request with the items argument will return two additional members:
items contains an array of item objects
total_items contains the total number of items stored in the database (added in API version 2)
items contains an array of item objects
total_items contains the total number of items stored in the database (added in API version 2)
An item object has the following members:
id (positive integer)
feed_id (positive integer)
title (utf-8 string)
author (utf-8 string)
html (utf-8 string)
url (utf-8 string)
is_saved (boolean integer)
is_read (boolean integer)
created_on_time (Unix timestamp/integer)
id (positive integer)
feed_id (positive integer)
title (utf-8 string)
author (utf-8 string)
html (utf-8 string)
url (utf-8 string)
is_saved (boolean integer)
is_read (boolean integer)
created_on_time (Unix timestamp/integer)
Most servers wont have enough memory allocated to PHP to dump all items at once.
Three optional arguments control determine the items included in the response.
@@ -232,7 +232,6 @@ Three optional arguments control determine the items included in the response.
Use the with_ids argument with a comma-separated list of item ids to request (a maximum of 50) specific items.
(added in API version 2)
*/
func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
var result itemsResponse
@@ -242,8 +241,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithLimit(50)
builder.WithOrder("id")
builder.WithDirection(model.DefaultSortingDirection)
builder.WithSorting("id", model.DefaultSortingDirection)
switch {
case request.HasQueryParam(r, "since_id"):
@@ -256,11 +254,11 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
maxID := request.QueryInt64Param(r, "max_id", 0)
if maxID == 0 {
logger.Debug("[Fever] Fetching most recent items for user #%d", userID)
builder.WithDirection("desc")
builder.WithSorting("id", "DESC")
} else if maxID > 0 {
logger.Debug("[Fever] Fetching items before #%d for user #%d", maxID, userID)
builder.BeforeEntryID(maxID)
builder.WithDirection("desc")
builder.WithSorting("id", "DESC")
}
case request.HasQueryParam(r, "with_ids"):
csvItemIDs := request.QueryStringParam(r, "with_ids", "")
@@ -310,7 +308,7 @@ func (h *handler) handleItems(w http.ResponseWriter, r *http.Request) {
FeedID: entry.FeedID,
Title: entry.Title,
Author: entry.Author,
HTML: entry.Content,
HTML: proxy.AbsoluteProxyRewriter(h.router, r.Host, entry.Content),
URL: entry.URL,
IsSaved: isSaved,
IsRead: isRead,
@@ -327,7 +325,8 @@ The unread_item_ids and saved_item_ids arguments can be used to keep your local
with the remote Fever installation.
A request with the unread_item_ids argument will return one additional member:
unread_item_ids (string/comma-separated list of positive integers)
unread_item_ids (string/comma-separated list of positive integers)
*/
func (h *handler) handleUnreadItems(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
@@ -365,7 +364,7 @@ func (h *handler) handleSavedItems(w http.ResponseWriter, r *http.Request) {
logger.Debug("[Fever] Fetching saved items for user #%d", userID)
builder := h.store.NewEntryQueryBuilder(userID)
builder.WithStarred()
builder.WithStarred(true)
entryIDs, err := builder.GetEntryIDs()
if err != nil {
@@ -384,9 +383,9 @@ func (h *handler) handleSavedItems(w http.ResponseWriter, r *http.Request) {
}
/*
mark=item
as=? where ? is replaced with read, saved or unsaved
id=? where ? is replaced with the id of the item to modify
mark=item
as=? where ? is replaced with read, saved or unsaved
id=? where ? is replaced with the id of the item to modify
*/
func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
@@ -448,10 +447,10 @@ func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) {
}
/*
mark=feed
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local clients most recent items API request
mark=feed
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local clients most recent items API request
*/
func (h *handler) handleWriteFeeds(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
@@ -474,10 +473,10 @@ func (h *handler) handleWriteFeeds(w http.ResponseWriter, r *http.Request) {
}
/*
mark=group
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local clients most recent items API request
mark=group
as=read
id=? where ? is replaced with the id of the feed or group to modify
before=? where ? is replaced with the Unix timestamp of the the local clients most recent items API request
*/
func (h *handler) handleWriteGroups(w http.ResponseWriter, r *http.Request) {
userID := request.UserID(r)
@@ -510,9 +509,8 @@ func (h *handler) handleWriteGroups(w http.ResponseWriter, r *http.Request) {
/*
A feeds_group object has the following members:
group_id (positive integer)
feed_ids (string/comma-separated list of positive integers)
group_id (positive integer)
feed_ids (string/comma-separated list of positive integers)
*/
func (h *handler) buildFeedGroups(feeds model.Feeds) []feedsGroups {
feedsGroupedByCategory := make(map[int64][]string)
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package fever // import "miniflux.app/fever"
+4 -6
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package fever // import "miniflux.app/fever"
@@ -23,8 +22,8 @@ func (b *baseResponse) SetCommonValues() {
/*
The default response is a JSON object containing two members:
api_version contains the version of the API responding (positive integer)
auth whether the request was successfully authenticated (boolean integer)
api_version contains the version of the API responding (positive integer)
auth whether the request was successfully authenticated (boolean integer)
The API can also return XML by passing xml as the optional value of the api argument like so:
@@ -37,7 +36,6 @@ at least one additional member:
last_refreshed_on_time contains the time of the most recently refreshed (not updated)
feed (Unix timestamp/integer)
*/
func newBaseResponse() baseResponse {
r := baseResponse{}
-221
View File
@@ -1,221 +0,0 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// +build ignore
package main
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"text/template"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/js"
)
const tpl = `// Code generated by go generate; DO NOT EDIT.
package {{ .Package }} // import "miniflux.app/{{ .ImportPath }}"
var {{ .Map }} = map[string]string{
{{ range $constant, $content := .Files }}` + "\t" + `"{{ $constant }}": ` + "`{{ $content }}`" + `,
{{ end }}}
var {{ .Map }}Checksums = map[string]string{
{{ range $constant, $content := .Checksums }}` + "\t" + `"{{ $constant }}": "{{ $content }}",
{{ end }}}
`
var bundleTpl = template.Must(template.New("").Parse(tpl))
type Bundle struct {
Package string
Map string
ImportPath string
Files map[string]string
Checksums map[string]string
}
func (b *Bundle) Write(filename string) {
f, err := os.Create(filename)
if err != nil {
panic(err)
}
defer f.Close()
bundleTpl.Execute(f, b)
}
func NewBundle(pkg, mapName, importPath string) *Bundle {
return &Bundle{
Package: pkg,
Map: mapName,
ImportPath: importPath,
Files: make(map[string]string),
Checksums: make(map[string]string),
}
}
func readFile(filename string) []byte {
data, err := ioutil.ReadFile(filename)
if err != nil {
panic(err)
}
return data
}
func checksum(data []byte) string {
return fmt.Sprintf("%x", sha256.Sum256(data))
}
func basename(filename string) string {
return path.Base(filename)
}
func stripExtension(filename string) string {
filename = strings.TrimSuffix(filename, path.Ext(filename))
return strings.Replace(filename, " ", "_", -1)
}
func glob(pattern string) []string {
// There is no Glob function in path package, so we have to use filepath and replace in case of Windows
files, _ := filepath.Glob(pattern)
for i := range files {
if strings.Contains(files[i], "\\") {
files[i] = strings.Replace(files[i], "\\", "/", -1)
}
}
return files
}
func concat(files []string) string {
var b strings.Builder
for _, file := range files {
b.Write(readFile(file))
}
return b.String()
}
func generateJSBundle(bundleFile string, bundleFiles map[string][]string, prefixes, suffixes map[string]string) {
bundle := NewBundle("static", "Javascripts", "ui/static")
m := minify.New()
m.AddFunc("text/javascript", js.Minify)
for name, srcFiles := range bundleFiles {
var b strings.Builder
if prefix, found := prefixes[name]; found {
b.WriteString(prefix)
}
b.WriteString(concat(srcFiles))
if suffix, found := suffixes[name]; found {
b.WriteString(suffix)
}
minifiedData, err := m.String("text/javascript", b.String())
if err != nil {
panic(err)
}
bundle.Files[name] = minifiedData
bundle.Checksums[name] = checksum([]byte(minifiedData))
}
bundle.Write(bundleFile)
}
func generateCSSBundle(bundleFile string, themes map[string][]string) {
bundle := NewBundle("static", "Stylesheets", "ui/static")
m := minify.New()
m.AddFunc("text/css", css.Minify)
for theme, srcFiles := range themes {
data := concat(srcFiles)
minifiedData, err := m.String("text/css", data)
if err != nil {
panic(err)
}
bundle.Files[theme] = minifiedData
bundle.Checksums[theme] = checksum([]byte(minifiedData))
}
bundle.Write(bundleFile)
}
func generateBinaryBundle(bundleFile string, srcFiles []string) {
bundle := NewBundle("static", "Binaries", "ui/static")
for _, srcFile := range srcFiles {
data := readFile(srcFile)
filename := basename(srcFile)
encodedData := base64.StdEncoding.EncodeToString(data)
bundle.Files[filename] = string(encodedData)
bundle.Checksums[filename] = checksum(data)
}
bundle.Write(bundleFile)
}
func generateBundle(bundleFile, pkg, mapName string, srcFiles []string) {
bundle := NewBundle(pkg, mapName, pkg)
for _, srcFile := range srcFiles {
data := readFile(srcFile)
filename := stripExtension(basename(srcFile))
bundle.Files[filename] = string(data)
bundle.Checksums[filename] = checksum(data)
}
bundle.Write(bundleFile)
}
func main() {
generateJSBundle("ui/static/js.go", map[string][]string{
"app": []string{
"ui/static/js/dom_helper.js",
"ui/static/js/touch_handler.js",
"ui/static/js/keyboard_handler.js",
"ui/static/js/request_builder.js",
"ui/static/js/modal_handler.js",
"ui/static/js/app.js",
"ui/static/js/bootstrap.js",
},
"service-worker": []string{
"ui/static/js/service_worker.js",
},
}, map[string]string{
"app": "(function(){'use strict';",
"sw": "'use strict';",
}, map[string]string{
"app": "})();",
})
generateCSSBundle("ui/static/css.go", map[string][]string{
"light_serif": []string{"ui/static/css/light.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
"light_sans_serif": []string{"ui/static/css/light.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
"dark_serif": []string{"ui/static/css/dark.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
"dark_sans_serif": []string{"ui/static/css/dark.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
"system_serif": []string{"ui/static/css/system.css", "ui/static/css/serif.css", "ui/static/css/common.css"},
"system_sans_serif": []string{"ui/static/css/system.css", "ui/static/css/sans_serif.css", "ui/static/css/common.css"},
})
generateBinaryBundle("ui/static/bin.go", glob("ui/static/bin/*"))
generateBundle("template/views.go", "template", "templateViewsMap", glob("template/html/*.html"))
generateBundle("template/common.go", "template", "templateCommonMap", glob("template/html/common/*.html"))
generateBundle("locale/translations.go", "locale", "translations", glob("locale/translations/*.json"))
}
+35 -20
View File
@@ -1,27 +1,42 @@
module miniflux.app
// +heroku goVersion go1.11
// +heroku goVersion go1.19
require (
github.com/PuerkitoBio/goquery v1.6.0
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 // indirect
github.com/PuerkitoBio/goquery v1.8.1
github.com/coreos/go-oidc/v3 v3.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.9.0
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/prometheus/client_golang v1.9.0
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d
github.com/stretchr/testify v1.6.1 // indirect
github.com/tdewolff/minify/v2 v2.9.10 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.0 // indirect
mvdan.cc/xurls/v2 v2.2.0
github.com/lib/pq v1.10.9
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530
github.com/mccutchen/go-httpbin/v2 v2.10.0
github.com/prometheus/client_golang v1.16.0
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0
github.com/tdewolff/minify/v2 v2.12.7
github.com/yuin/goldmark v1.5.4
golang.org/x/crypto v0.11.0
golang.org/x/net v0.12.0
golang.org/x/oauth2 v0.10.0
golang.org/x/term v0.10.0
mvdan.cc/xurls/v2 v2.5.0
)
go 1.11
require (
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
golang.org/x/sys v0.10.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)
go 1.19
+97 -553
View File
@@ -1,585 +1,129 @@
cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94=
github.com/PuerkitoBio/goquery v1.6.0/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bradfitz/gomemcache v0.0.0-20170208213004-1952afaa557d/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/coreos/go-oidc/v3 v3.6.0 h1:AKVxfYw1Gmkn/w96z0DbT/B/xFnzTd3MkZvWLjF4n/o=
github.com/coreos/go-oidc/v3 v3.6.0/go.mod h1:ZpHUsHBucTUj6WOkrP4E20UPynbLZzhTQ1XKCXkxyPc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.0 h1:gh8fMGz0rlOv/1WmRZm7OgncIOTsAj21iNJot48omJQ=
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.3-0.20170329110642-4da3e2cfbabc/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5 h1:yrv1uUvgXH/tEat+wdvJMRJ4g51GlIydtDpU9pFjaaI=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 h1:5vu7C+63KTbsSNnLhrgB98Sqy8MNVSW8FdhkcWA/3Rk=
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686/go.mod h1:sam69Hju0uq+5uvLJUMDlsKlQ21Vrs1Kd/1YFPNYdOU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/lint v0.0.0-20170918230701-e5d664eb928e/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.1.1-0.20171103154506-982329095285/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-dap v0.2.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
github.com/google/go-dap v0.3.0/go.mod h1:5q8aYQFnHOAZEMP+6vmq25HKYAEwE+LF5yh7JKrrhSQ=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U=
github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2 h1:9kYRAm87/M5VL+HWegDGIorBWDiErrZrksLKTJBF2IQ=
github.com/mitchellh/go-server-timing v1.0.2-0.20201108055052-feb680ab92c2/go.mod h1:8mKJVJkyMI20ifXXO0zlV3sh3FrtjvltAeBqz38clBE=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/peterh/liner v1.2.0/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mccutchen/go-httpbin/v2 v2.10.0 h1:8EUgpxSmbXEtmBgfZhtVngCdmMkc0MQnUz2JXfIrT+E=
github.com/mccutchen/go-httpbin/v2 v2.10.0/go.mod h1:f4DUXYlU6yH0V81O4lJIwqpmYdTXXmYwzxMnYEimFPk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.9.0 h1:Rrch9mh17XcxvEu9D9DEpb4isxjGBtcevQjKvxPRQIU=
github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0 h1:4fgOnadei3EZvgRwxJ7RMpG1k1pOZth5Pc13tyspaKM=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d h1:4Jn2kzSKcpxxwpJdyWxfNWKCYe4Uki2VibkExYmBLiA=
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d/go.mod h1:3vfmZI6aJd5Rb9W2TQ0Nmupl+qem21R05+hmCscI0Bk=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v0.0.0-20170901052352-ee1bd8ee15a1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v0.0.0-20170901151539-12bd96e66386/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20170901120850-7aff26db30c1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0 h1:qSaU9YAEIxk/ozcmY1hiauktAYTpbwYIrPdQ0L2E8UM=
github.com/rylans/getlang v0.0.0-20201227074721-9e7f44ff8aa0/go.mod h1:3vfmZI6aJd5Rb9W2TQ0Nmupl+qem21R05+hmCscI0Bk=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify v1.1.0 h1:nxHQi1ML+g3ZbZHffiZ6eC7vMqNvSRfX3KB5Y5y/kfw=
github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo=
github.com/tdewolff/minify/v2 v2.9.10 h1:p+ifTTl+JMFFLDYNAm7nxQ9XuCG10HTW00wlPAZ7aoE=
github.com/tdewolff/minify/v2 v2.9.10/go.mod h1:U1Nc+/YBSB0FPEarqcgkYH3Ep4DNyyIbOyl5P4eWMuo=
github.com/tdewolff/parse/v2 v2.5.5 h1:b7ICJa4I/54JQGEGgTte8DiyJPKcC5g8V773QMzkeUM=
github.com/tdewolff/parse/v2 v2.5.5/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
go.starlark.net v0.0.0-20201006213952-227f4aabceb5/go.mod h1:f0znQkUKRrkk36XxWbGjMqQM8wGv/xHBVE2qc3B5oFU=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/arch v0.0.0-20190927153633-4e8777c89be4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA=
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d h1:dOiJ2n2cMwGLce/74I/QHMbnpk5GfY7InR8rczoMqRM=
golang.org/x/net v0.0.0-20201029221708-28c70e62bb1d/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20170517211232-f52d1811a629/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs=
golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.0.0-20170921000349-586095a6e407/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20170918111702-1e559d0a00ee/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.2.1-0.20170921194603-d4b75ebd4f9f/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.5.0 h1:OZ4sdq+Y+SHfYB7vfthi1Ei8b0vkP8ZPQgUfUwdUSqo=
gopkg.in/square/go-jose.v2 v2.5.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
mvdan.cc/xurls v1.1.0 h1:kj0j2lonKseISJCiq1Tfk+iTv65dDGCl0rTbanXJGGc=
mvdan.cc/xurls/v2 v2.2.0 h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=
mvdan.cc/xurls/v2 v2.2.0/go.mod h1:EV1RMtya9D6G5DMYPGD8zTQzaHet6Jh8gFlRgGRJeO8=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
File diff suppressed because it is too large Load Diff
+207
View File
@@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/googlereader"
import (
"context"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"net/http"
"strings"
"miniflux.app/http/request"
"miniflux.app/http/response"
"miniflux.app/http/response/json"
"miniflux.app/logger"
"miniflux.app/model"
"miniflux.app/storage"
)
type middleware struct {
store *storage.Storage
}
func newMiddleware(s *storage.Storage) *middleware {
return &middleware{s}
}
func (m *middleware) clientLogin(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
var username, password, output string
var integration *model.Integration
err := r.ParseForm()
if err != nil {
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not parse form", clientIP)
json.Unauthorized(w, r)
return
}
username = r.Form.Get("Email")
password = r.Form.Get("Passwd")
output = r.Form.Get("output")
if username == "" || password == "" {
logger.Error("[GoogleReader][Login] [ClientIP=%s] Empty username or password", clientIP)
json.Unauthorized(w, r)
return
}
if err = m.store.GoogleReaderUserCheckPassword(username, password); err != nil {
logger.Error("[GoogleReader][Login] [ClientIP=%s] Invalid username or password: %s", clientIP, username)
json.Unauthorized(w, r)
return
}
logger.Info("[GoogleReader][Login] [ClientIP=%s] User authenticated: %s", clientIP, username)
if integration, err = m.store.GoogleReaderUserGetIntegration(username); err != nil {
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not load integration: %s", clientIP, username)
json.Unauthorized(w, r)
return
}
m.store.SetLastLogin(integration.UserID)
token := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
logger.Info("[GoogleReader][Login] [ClientIP=%s] Created token: %s", clientIP, token)
result := login{SID: token, LSID: token, Auth: token}
if output == "json" {
json.OK(w, r, result)
return
}
builder := response.New(w, r)
builder.WithHeader("Content-Type", "text/plain; charset=UTF-8")
builder.WithBody(result.String())
builder.Write()
}
func (m *middleware) token(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
if !request.IsAuthenticated(r) {
logger.Error("[GoogleReader][Token] [ClientIP=%s] User is not authenticated", clientIP)
json.Unauthorized(w, r)
return
}
token := request.GoolgeReaderToken(r)
if token == "" {
logger.Error("[GoogleReader][Token] [ClientIP=%s] User does not have token: %s", clientIP, request.UserID(r))
json.Unauthorized(w, r)
return
}
logger.Info("[GoogleReader][Token] [ClientIP=%s] token: %s", clientIP, token)
w.Header().Add("Content-Type", "text/plain; charset=UTF-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(token))
}
func (m *middleware) handleCORS(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func (m *middleware) apiKeyAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
clientIP := request.ClientIP(r)
var token string
if r.Method == http.MethodPost {
err := r.ParseForm()
if err != nil {
logger.Error("[GoogleReader][Login] [ClientIP=%s] Could not parse form", clientIP)
Unauthorized(w, r)
return
}
token = r.Form.Get("T")
if token == "" {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Post-Form T field is empty", clientIP)
Unauthorized(w, r)
return
}
} else {
authorization := r.Header.Get("Authorization")
if authorization == "" {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No token provided", clientIP)
Unauthorized(w, r)
return
}
fields := strings.Fields(authorization)
if len(fields) != 2 {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
Unauthorized(w, r)
return
}
if fields[0] != "GoogleLogin" {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not begin with GoogleLogin - '%s'", clientIP, authorization)
Unauthorized(w, r)
return
}
auths := strings.Split(fields[1], "=")
if len(auths) != 2 {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
Unauthorized(w, r)
return
}
if auths[0] != "auth" {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Authorization header does not have the expected structure GoogleLogin auth=xxxxxx - '%s'", clientIP, authorization)
Unauthorized(w, r)
return
}
token = auths[1]
}
parts := strings.Split(token, "/")
if len(parts) != 2 {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Auth token does not have the expected structure username/hash - '%s'", clientIP, token)
Unauthorized(w, r)
return
}
var integration *model.Integration
var user *model.User
var err error
if integration, err = m.store.GoogleReaderUserGetIntegration(parts[0]); err != nil {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] token: %s", clientIP, token)
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No user found with the given google reader username: %s", clientIP, parts[0])
Unauthorized(w, r)
return
}
expectedToken := getAuthToken(integration.GoogleReaderUsername, integration.GoogleReaderPassword)
if expectedToken != token {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] Token does not match: %s", clientIP, token)
Unauthorized(w, r)
return
}
if user, err = m.store.UserByID(integration.UserID); err != nil {
logger.Error("[GoogleReader][Auth] [ClientIP=%s] No user found with the userID: %d", clientIP, integration.UserID)
Unauthorized(w, r)
return
}
m.store.SetLastLogin(integration.UserID)
ctx := r.Context()
ctx = context.WithValue(ctx, request.UserIDContextKey, user.ID)
ctx = context.WithValue(ctx, request.UserTimezoneContextKey, user.Timezone)
ctx = context.WithValue(ctx, request.IsAdminUserContextKey, user.IsAdmin)
ctx = context.WithValue(ctx, request.IsAuthenticatedContextKey, true)
ctx = context.WithValue(ctx, request.GoogleReaderToken, token)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func getAuthToken(username, password string) string {
token := hex.EncodeToString(hmac.New(sha1.New, []byte(username+password)).Sum(nil))
token = username + "/" + token
return token
}
+144
View File
@@ -0,0 +1,144 @@
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package googlereader // import "miniflux.app/googlereader"
import (
"fmt"
"net/http"
"miniflux.app/http/response"
"miniflux.app/logger"
)
type login struct {
SID string `json:"SID,omitempty"`
LSID string `json:"LSID,omitempty"`
Auth string `json:"Auth,omitempty"`
}
func (l login) String() string {
return fmt.Sprintf("SID=%s\nLSID=%s\nAuth=%s\n", l.SID, l.LSID, l.Auth)
}
type userInfo struct {
UserID string `json:"userId"`
UserName string `json:"userName"`
UserProfileID string `json:"userProfileId"`
UserEmail string `json:"userEmail"`
}
type subscription struct {
ID string `json:"id"`
Title string `json:"title"`
Categories []subscriptionCategory `json:"categories"`
URL string `json:"url"`
HTMLURL string `json:"htmlUrl"`
IconURL string `json:"iconUrl"`
}
type quickAddResponse struct {
NumResults int64 `json:"numResults"`
Query string `json:"query,omitempty"`
StreamID string `json:"streamId,omitempty"`
StreamName string `json:"streamName,omitempty"`
}
type subscriptionCategory struct {
ID string `json:"id"`
Label string `json:"label,omitempty"`
Type string `json:"type,omitempty"`
}
type subscriptionsResponse struct {
Subscriptions []subscription `json:"subscriptions"`
}
type itemRef struct {
ID string `json:"id"`
DirectStreamIDs string `json:"directStreamIds,omitempty"`
TimestampUsec string `json:"timestampUsec,omitempty"`
}
type streamIDResponse struct {
ItemRefs []itemRef `json:"itemRefs"`
Continuation int `json:"continuation,omitempty,string"`
}
type tagsResponse struct {
Tags []subscriptionCategory `json:"tags"`
}
type streamContentItems struct {
Direction string `json:"direction"`
ID string `json:"id"`
Title string `json:"title"`
Self []contentHREF `json:"self"`
Alternate []contentHREFType `json:"alternate"`
Updated int64 `json:"updated"`
Items []contentItem `json:"items"`
Author string `json:"author"`
}
type contentItem struct {
ID string `json:"id"`
Categories []string `json:"categories"`
Title string `json:"title"`
CrawlTimeMsec string `json:"crawlTimeMsec"`
TimestampUsec string `json:"timestampUsec"`
Published int64 `json:"published"`
Updated int64 `json:"updated"`
Author string `json:"author"`
Alternate []contentHREFType `json:"alternate"`
Summary contentItemContent `json:"summary"`
Content contentItemContent `json:"content"`
Origin contentItemOrigin `json:"origin"`
Enclosure []contentItemEnclosure `json:"enclosure"`
Canonical []contentHREF `json:"canonical"`
}
type contentHREFType struct {
HREF string `json:"href"`
Type string `json:"type"`
}
type contentHREF struct {
HREF string `json:"href"`
}
type contentItemEnclosure struct {
URL string `json:"url"`
Type string `json:"type"`
}
type contentItemContent struct {
Direction string `json:"direction"`
Content string `json:"content"`
}
type contentItemOrigin struct {
StreamID string `json:"streamId"`
Title string `json:"title"`
HTMLUrl string `json:"htmlUrl"`
}
// Unauthorized sends a not authorized error to the client.
func Unauthorized(w http.ResponseWriter, r *http.Request) {
logger.Error("[HTTP:Unauthorized] %s", r.URL)
builder := response.New(w, r)
builder.WithStatus(http.StatusUnauthorized)
builder.WithHeader("Content-Type", "text/plain")
builder.WithHeader("X-Reader-Google-Bad-Token", "true")
builder.WithBody("Unauthorized")
builder.Write()
}
// OK sends a ok response to the client.
func OK(w http.ResponseWriter, r *http.Request) {
logger.Info("[HTTP:OK] %s", r.URL)
builder := response.New(w, r)
builder.WithStatus(http.StatusOK)
builder.WithHeader("Content-Type", "text/plain")
builder.WithBody("OK")
builder.Write()
}
+62 -29
View File
@@ -1,16 +1,15 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/http/client"
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@@ -21,7 +20,6 @@ import (
"miniflux.app/errors"
"miniflux.app/logger"
"miniflux.app/timer"
url_helper "miniflux.app/url"
)
const (
@@ -30,30 +28,30 @@ const (
)
var (
errInvalidCertificate = "Invalid SSL certificate (original error: %q)"
errTemporaryNetworkOperation = "This website is temporarily unreachable (original error: %q)"
errPermanentNetworkOperation = "This website is permanently unreachable (original error: %q)"
errRequestTimeout = "Website unreachable, the request timed out after %d seconds"
errInvalidCertificate = "Invalid SSL certificate (original error: %q)"
errNetworkOperation = "This website is unreachable (original error: %q)"
errRequestTimeout = "Website unreachable, the request timed out after %d seconds"
)
// Client builds and executes HTTP requests.
type Client struct {
inputURL string
requestURL string
requestEtagHeader string
requestLastModifiedHeader string
requestAuthorizationHeader string
requestUsername string
requestPassword string
requestUserAgent string
requestCookie string
customHeaders map[string]string
useProxy bool
doNotFollowRedirects bool
useProxy bool
doNotFollowRedirects bool
ClientTimeout int
ClientMaxBodySize int64
ClientProxyURL string
ClientTimeout int
ClientMaxBodySize int64
ClientProxyURL string
AllowSelfSignedCertificates bool
}
// New initializes a new HTTP client.
@@ -88,13 +86,13 @@ func (c *Client) String() string {
}
return fmt.Sprintf(
`InputURL=%q RequestURL=%q ETag=%s LastModified=%s Auth=%v UserAgent=%q`,
`InputURL=%q ETag=%s LastMod=%s Auth=%v UserAgent=%q Verify=%v`,
c.inputURL,
c.requestURL,
etagHeader,
lastModifiedHeader,
c.requestAuthorizationHeader != "" || (c.requestUsername != "" && c.requestPassword != ""),
c.requestUserAgent,
!c.AllowSelfSignedCertificates,
)
}
@@ -113,9 +111,15 @@ func (c *Client) WithAuthorization(authorization string) *Client {
return c
}
// WithCustomHeaders defines custom HTTP headers.
func (c *Client) WithCustomHeaders(customHeaders map[string]string) *Client {
c.customHeaders = customHeaders
return c
}
// WithCacheHeaders defines caching headers.
func (c *Client) WithCacheHeaders(etagHeader, lastModifiedHeader string) *Client {
c.requestLastModifiedHeader = etagHeader
c.requestEtagHeader = etagHeader
c.requestLastModifiedHeader = lastModifiedHeader
return c
}
@@ -140,6 +144,14 @@ func (c *Client) WithUserAgent(userAgent string) *Client {
return c
}
// WithCookie defines the Cookies to use for HTTP requests.
func (c *Client) WithCookie(cookie string) *Client {
if cookie != "" {
c.requestCookie = cookie
}
return c
}
// Get performs a GET HTTP request.
func (c *Client) Get() (*Response, error) {
request, err := c.buildRequest(http.MethodGet, nil)
@@ -177,6 +189,22 @@ func (c *Client) PostJSON(data interface{}) (*Response, error) {
return c.executeRequest(request)
}
// PatchJSON performs a Patch HTTP request with a JSON payload.
func (c *Client) PatchJSON(data interface{}) (*Response, error) {
b, err := json.Marshal(data)
if err != nil {
return nil, err
}
request, err := c.buildRequest(http.MethodPatch, bytes.NewReader(b))
if err != nil {
return nil, err
}
request.Header.Add("Content-Type", "application/json")
return c.executeRequest(request)
}
func (c *Client) executeRequest(request *http.Request) (*Response, error) {
defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[HttpClient] inputURL=%s", c.inputURL))
@@ -197,17 +225,11 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
case x509.CertificateInvalidError, x509.HostnameError:
err = errors.NewLocalizedError(errInvalidCertificate, uerr.Err)
case *net.OpError:
if uerr.Err.(*net.OpError).Temporary() {
err = errors.NewLocalizedError(errTemporaryNetworkOperation, uerr.Err)
} else {
err = errors.NewLocalizedError(errPermanentNetworkOperation, uerr.Err)
}
err = errors.NewLocalizedError(errNetworkOperation, uerr.Err)
case net.Error:
nerr := uerr.Err.(net.Error)
if nerr.Timeout() {
err = errors.NewLocalizedError(errRequestTimeout, c.ClientTimeout)
} else if nerr.Temporary() {
err = errors.NewLocalizedError(errTemporaryNetworkOperation, nerr)
}
}
}
@@ -219,7 +241,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
return nil, fmt.Errorf("client: response too large (%d bytes)", resp.ContentLength)
}
buf, err := ioutil.ReadAll(resp.Body)
buf, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("client: error while reading body %v", err)
}
@@ -252,8 +274,7 @@ func (c *Client) executeRequest(request *http.Request) (*Response, error) {
}
func (c *Client) buildRequest(method string, body io.Reader) (*http.Request, error) {
c.requestURL = url_helper.RequestURI(c.inputURL)
request, err := http.NewRequest(method, c.requestURL, body)
request, err := http.NewRequest(method, c.inputURL, body)
if err != nil {
return nil, err
}
@@ -289,6 +310,10 @@ func (c *Client) buildClient() http.Client {
IdleConnTimeout: 10 * time.Second,
}
if c.AllowSelfSignedCertificates {
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
if c.doNotFollowRedirects {
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
@@ -330,6 +355,14 @@ func (c *Client) buildHeaders() http.Header {
headers.Add("Authorization", c.requestAuthorizationHeader)
}
if c.requestCookie != "" {
headers.Add("Cookie", c.requestCookie)
}
for key, value := range c.customHeaders {
headers.Add(key, value)
}
headers.Add("Connection", "close")
return headers
}
+26 -9
View File
@@ -1,15 +1,32 @@
// Copyright 2020 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/http/client"
import (
"fmt"
"net/http/httptest"
"os"
"testing"
"github.com/mccutchen/go-httpbin/v2/httpbin"
)
var srv *httptest.Server
func TestMain(m *testing.M) {
srv = httptest.NewServer(httpbin.New())
exitCode := m.Run()
srv.Close()
os.Exit(exitCode)
}
func MakeClient(path string) *Client {
return New(fmt.Sprintf("%s%s", srv.URL, path))
}
func TestClientWithDelay(t *testing.T) {
clt := New("http://httpbin.org/delay/5")
clt := MakeClient("/delay/5")
clt.ClientTimeout = 1
_, err := clt.Get()
if err == nil {
@@ -18,8 +35,8 @@ func TestClientWithDelay(t *testing.T) {
}
func TestClientWithError(t *testing.T) {
clt := New("http://httpbin.org/status/502")
clt.ClientTimeout = 1
clt := MakeClient("/status/502")
clt.ClientTimeout = 5
response, err := clt.Get()
if err != nil {
t.Fatal(err)
@@ -30,12 +47,12 @@ func TestClientWithError(t *testing.T) {
}
if !response.HasServerFailure() {
t.Fatal(`A 500 error is considered as server failure`)
t.Fatal(`A 502 error is considered as server failure`)
}
}
func TestClientWithResponseTooLarge(t *testing.T) {
clt := New("http://httpbin.org/bytes/100")
clt := MakeClient("/bytes/100")
clt.ClientMaxBodySize = 10
_, err := clt.Get()
if err == nil {
@@ -44,7 +61,7 @@ func TestClientWithResponseTooLarge(t *testing.T) {
}
func TestClientWithBasicAuth(t *testing.T) {
clt := New("http://httpbin.org/basic-auth/testuser/testpassword")
clt := MakeClient("/basic-auth/testuser/testpassword")
clt.WithCredentials("testuser", "testpassword")
_, err := clt.Get()
if err != nil {
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package client provides an HTTP client builder.
*/
package client // import "miniflux.app/http/client"
+5 -7
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/http/client"
@@ -8,7 +7,6 @@ import (
"bytes"
"fmt"
"io"
"io/ioutil"
"regexp"
"strings"
"unicode/utf8"
@@ -43,7 +41,7 @@ func (r *Response) String() string {
)
}
// IsNotFound returns true if the resource doesn't exists anymore.
// IsNotFound returns true if the resource doesn't exist anymore.
func (r *Response) IsNotFound() bool {
return r.StatusCode == 404 || r.StatusCode == 410
}
@@ -87,7 +85,7 @@ func (r *Response) IsModified(etag, lastModified string) bool {
// - Feeds with encoding specified only in XML document and not in HTTP header
// - Feeds with wrong encoding defined and already in UTF-8
func (r *Response) EnsureUnicodeBody() (err error) {
buffer, err := ioutil.ReadAll(r.Body)
buffer, err := io.ReadAll(r.Body)
if err != nil {
return err
}
@@ -116,6 +114,6 @@ func (r *Response) EnsureUnicodeBody() (err error) {
// BodyAsString returns the response body as string.
func (r *Response) BodyAsString() string {
bytes, _ := ioutil.ReadAll(r.Body)
bytes, _ := io.ReadAll(r.Body)
return string(bytes)
}
+5 -6
View File
@@ -1,12 +1,11 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package client // import "miniflux.app/http/client"
import (
"bytes"
"io/ioutil"
"os"
"strings"
"testing"
"unicode/utf8"
@@ -96,7 +95,7 @@ func TestToString(t *testing.T) {
r := &Response{Body: strings.NewReader(input)}
if r.BodyAsString() != input {
t.Error(`Unexpected ouput`)
t.Error(`Unexpected output`)
}
}
@@ -129,7 +128,7 @@ func TestEnsureUnicodeWithHTMLDocuments(t *testing.T) {
}
for _, tc := range unicodeTestCases {
content, err := ioutil.ReadFile("testdata/" + tc.filename)
content, err := os.ReadFile("testdata/" + tc.filename)
if err != nil {
t.Fatalf(`Unable to read file %q: %v`, tc.filename, err)
}
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2017 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package cookie // import "miniflux.app/http/cookie"
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package cookie provides functions to build cookies.
*/
package cookie // import "miniflux.app/http/cookie"
+20 -8
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
@@ -10,7 +9,7 @@ import (
"strings"
)
// FindClientIP returns client real IP address.
// FindClientIP returns the client real IP address based on trusted Reverse-Proxy HTTP headers.
func FindClientIP(r *http.Request) string {
headers := []string{"X-Forwarded-For", "X-Real-Ip"}
for _, header := range headers {
@@ -19,6 +18,7 @@ func FindClientIP(r *http.Request) string {
if value != "" {
addresses := strings.Split(value, ",")
address := strings.TrimSpace(addresses[0])
address = dropIPv6zone(address)
if net.ParseIP(address) != nil {
return address
@@ -27,12 +27,16 @@ func FindClientIP(r *http.Request) string {
}
// Fallback to TCP/IP source IP address.
var remoteIP string
if strings.ContainsRune(r.RemoteAddr, ':') {
remoteIP, _, _ = net.SplitHostPort(r.RemoteAddr)
} else {
return FindRemoteIP(r)
}
// FindRemoteIP returns remote client IP address.
func FindRemoteIP(r *http.Request) string {
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
remoteIP = r.RemoteAddr
}
remoteIP = dropIPv6zone(remoteIP)
// When listening on a Unix socket, RemoteAddr is empty.
if remoteIP == "" {
@@ -41,3 +45,11 @@ func FindClientIP(r *http.Request) string {
return remoteIP
}
func dropIPv6zone(address string) string {
i := strings.IndexByte(address, '%')
if i != -1 {
address = address[:i]
}
return address
}
+26 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
@@ -19,6 +18,21 @@ func TestFindClientIPWithoutHeaders(t *testing.T) {
if ip := FindClientIP(r); ip != "192.168.0.1" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "fe80::14c2:f039:edc7:edc7%eth0"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
r = &http.Request{RemoteAddr: "[fe80::14c2:f039:edc7:edc7%eth0]:4242"}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
}
func TestFindClientIPWithXFFHeader(t *testing.T) {
@@ -40,6 +54,15 @@ func TestFindClientIPWithXFFHeader(t *testing.T) {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with single IPv6 address with zone
headers = http.Header{}
headers.Set("X-Forwarded-For", "fe80::14c2:f039:edc7:edc7%eth0")
r = &http.Request{RemoteAddr: "192.168.0.1:4242", Header: headers}
if ip := FindClientIP(r); ip != "fe80::14c2:f039:edc7:edc7" {
t.Fatalf(`Unexpected result, got: %q`, ip)
}
// Test with single IPv4 address.
headers = http.Header{}
headers.Set("X-Forwarded-For", "70.41.3.18")
+8 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
@@ -25,8 +24,14 @@ const (
FlashErrorMessageContextKey
PocketRequestTokenContextKey
ClientIPContextKey
GoogleReaderToken
)
// GoolgeReaderToken returns the google reader token if it exists.
func GoolgeReaderToken(r *http.Request) string {
return getContextStringValue(r, GoogleReaderToken)
}
// IsAdminUser checks if the logged user is administrator.
func IsAdminUser(r *http.Request) bool {
return getContextBoolValue(r, IsAdminUserContextKey)
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package request contains helper functions to work with the HTTP request.
*/
package request // import "miniflux.app/http/request"
+17 -4
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
@@ -72,7 +71,21 @@ func QueryStringParamList(r *http.Request, param string) []string {
// QueryIntParam returns a query string parameter as integer.
func QueryIntParam(r *http.Request, param string, defaultValue int) int {
return int(QueryInt64Param(r, param, int64(defaultValue)))
value := r.URL.Query().Get(param)
if value == "" {
return defaultValue
}
val, err := strconv.ParseInt(value, 10, 0)
if err != nil {
return defaultValue
}
if val < 0 {
return defaultValue
}
return int(val)
}
// QueryInt64Param returns a query string parameter as int64.
+2 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package request // import "miniflux.app/http/request"
+9 -5
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package response // import "miniflux.app/http/response"
@@ -12,6 +11,8 @@ import (
"net/http"
"strings"
"time"
"miniflux.app/logger"
)
const compressionThreshold = 1024
@@ -88,7 +89,10 @@ func (b *Builder) Write() {
case io.Reader:
// Compression not implemented in this case
b.writeHeaders()
io.Copy(b.w, v)
_, err := io.Copy(b.w, v)
if err != nil {
logger.Error("%v", err)
}
}
}
@@ -96,7 +100,7 @@ func (b *Builder) writeHeaders() {
b.headers["X-XSS-Protection"] = "1; mode=block"
b.headers["X-Content-Type-Options"] = "nosniff"
b.headers["X-Frame-Options"] = "DENY"
b.headers["Content-Security-Policy"] = "default-src 'self'; img-src *; media-src *; frame-src *"
b.headers["Referrer-Policy"] = "no-referrer"
for key, value := range b.headers {
b.w.Header().Set(key, value)
+5 -7
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package response // import "miniflux.app/http/response"
@@ -29,10 +28,9 @@ func TestResponseHasCommonHeaders(t *testing.T) {
resp := w.Result()
headers := map[string]string{
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
"Content-Security-Policy": "default-src 'self'; img-src *; media-src *; frame-src *",
"X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"X-Frame-Options": "DENY",
}
for header, expected := range headers {
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package response contains everything related to HTTP responses.
*/
package response // import "miniflux.app/http/response"
-10
View File
@@ -1,10 +0,0 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
/*
Package html contains HTML response functions.
*/
package html // import "miniflux.app/http/response/html"
+17 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package html // import "miniflux.app/http/response/html"
@@ -26,6 +25,7 @@ func ServerError(w http.ResponseWriter, r *http.Request, err error) {
builder := response.New(w, r)
builder.WithStatus(http.StatusInternalServerError)
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(err)
@@ -38,6 +38,7 @@ func BadRequest(w http.ResponseWriter, r *http.Request, err error) {
builder := response.New(w, r)
builder.WithStatus(http.StatusBadRequest)
builder.WithHeader("Content-Security-Policy", `default-src 'self'`)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithBody(err)
@@ -72,3 +73,16 @@ func NotFound(w http.ResponseWriter, r *http.Request) {
func Redirect(w http.ResponseWriter, r *http.Request, uri string) {
http.Redirect(w, r, uri, http.StatusFound)
}
// RequestedRangeNotSatisfiable sends a range not satisfiable error to the client.
func RequestedRangeNotSatisfiable(w http.ResponseWriter, r *http.Request, contentRange string) {
logger.Error("[HTTP:Range Not Satisfiable] %s", r.URL)
builder := response.New(w, r)
builder.WithStatus(http.StatusRequestedRangeNotSatisfiable)
builder.WithHeader("Content-Type", "text/html; charset=utf-8")
builder.WithHeader("Cache-Control", "no-cache, max-age=0, must-revalidate, no-store")
builder.WithHeader("Content-Range", contentRange)
builder.WithBody("Range Not Satisfiable")
builder.Write()
}
+31 -3
View File
@@ -1,6 +1,5 @@
// Copyright 2018 Frédéric Guillot. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package html // import "miniflux.app/http/response/html"
@@ -210,3 +209,32 @@ func TestRedirectResponse(t *testing.T) {
t.Fatalf(`Unexpected redirect location, got %q instead of %q`, actualResult, expectedResult)
}
}
func TestRequestedRangeNotSatisfiable(t *testing.T) {
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
RequestedRangeNotSatisfiable(w, r, "bytes */12777")
})
handler.ServeHTTP(w, r)
resp := w.Result()
defer resp.Body.Close()
expectedStatusCode := http.StatusRequestedRangeNotSatisfiable
if resp.StatusCode != expectedStatusCode {
t.Fatalf(`Unexpected status code, got %d instead of %d`, resp.StatusCode, expectedStatusCode)
}
expectedContentRangeHeader := "bytes */12777"
actualContentRangeHeader := resp.Header.Get("Content-Range")
if actualContentRangeHeader != expectedContentRangeHeader {
t.Fatalf(`Unexpected content range header, got %q instead of %q`, actualContentRangeHeader, expectedContentRangeHeader)
}
}

Some files were not shown because too many files have changed in this diff Show More