mirror of
				https://github.com/nkanaev/yarr.git
				synced 2025-10-30 22:43:29 +00:00 
			
		
		
		
	Compare commits
	
		
			7 Commits
		
	
	
		
			24fa90c699
			...
			b6d53e5807
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b6d53e5807 | ||
|  | a895145586 | ||
|  | 5aec3b4dab | ||
|  | d787060a24 | ||
|  | c1a29418eb | ||
|  | 17847f999c | ||
|  | 3adcddc70c | 
| @@ -18,7 +18,7 @@ Then run one of the corresponding commands: | |||||||
|     make serve          # starts a server at http://localhost:7070 |     make serve          # starts a server at http://localhost:7070 | ||||||
| 
 | 
 | ||||||
|     # ... or build a docker image |     # ... or build a docker image | ||||||
|     docker build -t yarr . |     docker build -t yarr -f etc/dockerfile . | ||||||
| 
 | 
 | ||||||
| ## ARM compilation | ## ARM compilation | ||||||
| 
 | 
 | ||||||
| @@ -26,7 +26,7 @@ The instructions below are to cross-compile *yarr* to `Linux/ARM*`. | |||||||
| 
 | 
 | ||||||
| Build: | Build: | ||||||
| 
 | 
 | ||||||
|     docker build -t yarr.arm -f dockerfile.arm . |     docker build -t yarr.arm -f etc/dockerfile.arm . | ||||||
| 
 | 
 | ||||||
| Test: | Test: | ||||||
| 
 | 
 | ||||||
| @@ -4,6 +4,8 @@ | |||||||
| - (fix) duplicate articles caused by the same feed addition (thanks to @adaszko) | - (fix) duplicate articles caused by the same feed addition (thanks to @adaszko) | ||||||
| - (fix) relative article links (thanks to @adazsko for the report) | - (fix) relative article links (thanks to @adazsko for the report) | ||||||
| - (fix) atom article links stored in id element (thanks to @adazsko for the report) | - (fix) atom article links stored in id element (thanks to @adazsko for the report) | ||||||
|  | - (fix) parsing atom feed titles (thanks to @wnh) | ||||||
|  | - (fix) sorting same-day batch articles (thanks to @lamescholar for the report) | ||||||
|  |  | ||||||
| # v2.4 (2023-08-15) | # v2.4 (2023-08-15) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -28,17 +28,17 @@ RUN env \ | |||||||
|         CGO_ENABLED=1 \ |         CGO_ENABLED=1 \ | ||||||
|         GOOS=linux GOARCH=arm64 \ |         GOOS=linux GOARCH=arm64 \ | ||||||
|     go build \ |     go build \ | ||||||
|         -tags "sqlite_foreign_keys release linux" \ |         -tags "sqlite_foreign_keys linux" \ | ||||||
|         -ldflags="-s -w" \ |         -ldflags="-s -w" \ | ||||||
|         -o /root/out/yarr.arm64 src/main.go |         -o /root/out/yarr.arm64 ./cmd/yarr | ||||||
| 
 | 
 | ||||||
| RUN env \ | RUN env \ | ||||||
|         CC=arm-linux-gnueabihf-gcc \ |         CC=arm-linux-gnueabihf-gcc \ | ||||||
|         CGO_ENABLED=1 \ |         CGO_ENABLED=1 \ | ||||||
|         GOOS=linux GOARCH=arm GOARM=7 \ |         GOOS=linux GOARCH=arm GOARM=7 \ | ||||||
|     go build \ |     go build \ | ||||||
|         -tags "sqlite_foreign_keys release linux" \ |         -tags "sqlite_foreign_keys linux" \ | ||||||
|         -ldflags="-s -w" \ |         -ldflags="-s -w" \ | ||||||
|         -o /root/out/yarr.arm7 src/main.go |         -o /root/out/yarr.arm7 ./cmd/yarr | ||||||
| 
 | 
 | ||||||
| CMD ["/bin/bash"] | CMD ["/bin/bash"] | ||||||
							
								
								
									
										16
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								makefile
									
									
									
									
									
								
							| @@ -8,26 +8,26 @@ GO_LDFLAGS := $(GO_LDFLAGS) -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITH | |||||||
|  |  | ||||||
| build_default: | build_default: | ||||||
| 	mkdir -p _output | 	mkdir -p _output | ||||||
| 	go build -tags "sqlite_foreign_keys release" -ldflags="$(GO_LDFLAGS)" -o _output/yarr src/main.go | 	go build -tags "sqlite_foreign_keys" -ldflags="$(GO_LDFLAGS)" -o _output/yarr ./cmd/yarr | ||||||
|  |  | ||||||
| build_macos: | build_macos: | ||||||
| 	mkdir -p _output/macos | 	mkdir -p _output/macos | ||||||
| 	GOOS=darwin GOARCH=amd64 go build -tags "sqlite_foreign_keys release macos" -ldflags="$(GO_LDFLAGS)" -o _output/macos/yarr src/main.go | 	GOOS=darwin GOARCH=amd64 go build -tags "sqlite_foreign_keys macos" -ldflags="$(GO_LDFLAGS)" -o _output/macos/yarr ./cmd/yarr | ||||||
| 	cp src/platform/icon.png _output/macos/icon.png | 	cp src/platform/icon.png _output/macos/icon.png | ||||||
| 	go run bin/package_macos.go -outdir _output/macos -version "$(VERSION)" | 	go run ./cmd/package_macos -outdir _output/macos -version "$(VERSION)" | ||||||
|  |  | ||||||
| build_linux: | build_linux: | ||||||
| 	mkdir -p _output/linux | 	mkdir -p _output/linux | ||||||
| 	GOOS=linux GOARCH=amd64 go build -tags "sqlite_foreign_keys release linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr src/main.go | 	GOOS=linux GOARCH=amd64 go build -tags "sqlite_foreign_keys linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr ./cmd/yarr | ||||||
|  |  | ||||||
| build_windows: | build_windows: | ||||||
| 	mkdir -p _output/windows | 	mkdir -p _output/windows | ||||||
| 	go run bin/generate_versioninfo.go -version "$(VERSION)" -outfile src/platform/versioninfo.rc | 	go run ./cmd/generate_versioninfo -version "$(VERSION)" -outfile src/platform/versioninfo.rc | ||||||
| 	windres -i src/platform/versioninfo.rc -O coff -o src/platform/versioninfo.syso | 	windres -i src/platform/versioninfo.rc -O coff -o src/platform/versioninfo.syso | ||||||
| 	GOOS=windows GOARCH=amd64 go build -tags "sqlite_foreign_keys release windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe src/main.go | 	GOOS=windows GOARCH=amd64 go build -tags "sqlite_foreign_keys windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe ./cmd/yarr | ||||||
|  |  | ||||||
| serve: | serve: | ||||||
| 	go run -tags "sqlite_foreign_keys" src/main.go -db local.db | 	go run -tags "sqlite_foreign_keys" ./cmd/yarr -db local.db | ||||||
|  |  | ||||||
| test: | test: | ||||||
| 	cd src && go test -tags "sqlite_foreign_keys release" ./... | 	cd src && go test -tags "sqlite_foreign_keys" ./... | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								readme.md
									
									
									
									
									
								
							| @@ -10,27 +10,29 @@ The app is a single binary with an embedded database (SQLite). | |||||||
| ## usage | ## usage | ||||||
|  |  | ||||||
| The latest prebuilt binaries for Linux/MacOS/Windows AMD64 are available | The latest prebuilt binaries for Linux/MacOS/Windows AMD64 are available | ||||||
| [here](https://github.com/nkanaev/yarr/releases/latest). | [here](https://github.com/nkanaev/yarr/releases/latest). Installation instructions: | ||||||
|  |  | ||||||
| ### macos | * MacOS | ||||||
|  |  | ||||||
| Download `yarr-*-macos64.zip`, unzip it, place `yarr.app` in `/Applications` folder, [open the app][macos-open], click the anchor menu bar icon, select "Open". |   Download `yarr-*-macos64.zip`, unzip it, place `yarr.app` in `/Applications` folder, [open the app][macos-open], click the anchor menu bar icon, select "Open". | ||||||
|  |  | ||||||
|  | * Windows | ||||||
|  |  | ||||||
|  |   Download `yarr-*-windows64.zip`, unzip it, open `yarr.exe`, click the anchor system tray icon, select "Open". | ||||||
|  |  | ||||||
|  | * Linux | ||||||
|  |  | ||||||
|  |   Download `yarr-*-linux64.zip`, unzip it, place `yarr` in `$HOME/.local/bin` | ||||||
|  | and run [the script](etc/install-linux.sh). | ||||||
|  |  | ||||||
| [macos-open]: https://support.apple.com/en-gb/guide/mac-help/mh40616/mac | [macos-open]: https://support.apple.com/en-gb/guide/mac-help/mh40616/mac | ||||||
|  |  | ||||||
| ### windows |  | ||||||
|  |  | ||||||
| Download `yarr-*-windows64.zip`, unzip it, open `yarr.exe`, click the anchor system tray icon, select "Open". |  | ||||||
|  |  | ||||||
| ### linux |  | ||||||
|  |  | ||||||
| Download `yarr-*-linux64.zip`, unzip it, place `yarr` in `$HOME/.local/bin` |  | ||||||
| and run [the script](etc/install-linux.sh). |  | ||||||
|  |  | ||||||
| For self-hosting, see `yarr -h` for auth, tls & server configuration flags. | For self-hosting, see `yarr -h` for auth, tls & server configuration flags. | ||||||
| For building from source code, see [build.md](build.md) |  | ||||||
|  |  | ||||||
| For Fever API support, see [fever.md](fever.md). | See more: | ||||||
|  |  | ||||||
|  | * [Building from source code](doc/build.md) | ||||||
|  | * [Fever API support](doc/fever.md) | ||||||
|  |  | ||||||
| ## credits | ## credits | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,3 @@ | |||||||
| //go:build release |  | ||||||
| // +build release |  | ||||||
|  |  | ||||||
| package assets | package assets | ||||||
|  |  | ||||||
| import "embed" | import "embed" | ||||||
|   | |||||||
| @@ -47,6 +47,8 @@ type atomLinks []atomLink | |||||||
| func (a *atomText) Text() string { | func (a *atomText) Text() string { | ||||||
| 	if a.Type == "html" { | 	if a.Type == "html" { | ||||||
| 		return htmlutil.ExtractText(a.Data) | 		return htmlutil.ExtractText(a.Data) | ||||||
|  | 	} else if a.Type == "xhtml" { | ||||||
|  | 		return htmlutil.ExtractText(a.XML) | ||||||
| 	} | 	} | ||||||
| 	return a.Data | 	return a.Data | ||||||
| } | } | ||||||
|   | |||||||
| @@ -94,6 +94,44 @@ func TestAtomHTMLTitle(t *testing.T) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestAtomXHTMLTitle(t *testing.T) { | ||||||
|  | 	feed, _ := Parse(strings.NewReader(` | ||||||
|  | 		<?xml version="1.0" encoding="utf-8"?> | ||||||
|  | 		<feed xmlns="http://www.w3.org/2005/Atom"> | ||||||
|  | 			<entry><title type="xhtml">say <code>what</code>?</entry> | ||||||
|  | 		</feed> | ||||||
|  | 	`)) | ||||||
|  | 	have := feed.Items[0].Title | ||||||
|  | 	want := "say what?" | ||||||
|  | 	if !reflect.DeepEqual(want, have) { | ||||||
|  | 		t.Logf("want: %#v", want) | ||||||
|  | 		t.Logf("have: %#v", have) | ||||||
|  | 		t.FailNow() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestAtomXHTMLNestedTitle(t *testing.T) { | ||||||
|  | 	feed, _ := Parse(strings.NewReader(` | ||||||
|  | 		<?xml version="1.0" encoding="utf-8"?> | ||||||
|  | 		<feed xmlns="http://www.w3.org/2005/Atom"> | ||||||
|  | 			<entry> | ||||||
|  | 				<title type="xhtml"> | ||||||
|  | 					<div xmlns="http://www.w3.org/1999/xhtml"> | ||||||
|  | 						<a href="https://example.com">Link to Example</a> | ||||||
|  | 					</div> | ||||||
|  | 				</title> | ||||||
|  | 			</entry> | ||||||
|  | 		</feed> | ||||||
|  | 	`)) | ||||||
|  | 	have := feed.Items[0].Title | ||||||
|  | 	want := "Link to Example" | ||||||
|  | 	if !reflect.DeepEqual(want, have) { | ||||||
|  | 		t.Logf("want: %#v", want) | ||||||
|  | 		t.Logf("have: %#v", have) | ||||||
|  | 		t.FailNow() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestAtomImageLink(t *testing.T) { | func TestAtomImageLink(t *testing.T) { | ||||||
| 	feed, _ := Parse(strings.NewReader(` | 	feed, _ := Parse(strings.NewReader(` | ||||||
| 		<?xml version="1.0" encoding="UTF-8"?> | 		<?xml version="1.0" encoding="UTF-8"?> | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import ( | |||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"log" | 	"log" | ||||||
|  | 	"sort" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -75,6 +76,25 @@ type MarkFilter struct { | |||||||
| 	Before *time.Time | 	Before *time.Time | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ItemList []Item | ||||||
|  |  | ||||||
|  | func (list ItemList) Len() int { | ||||||
|  |     return len(list) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (list ItemList) SortKey(i int) string { | ||||||
|  |     return list[i].Date.Format(time.RFC3339) + "::" + list[i].GUID | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (list ItemList) Less(i, j int) bool { | ||||||
|  |     return list.SortKey(i) < list.SortKey(j) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (list ItemList) Swap(i, j int) { | ||||||
|  |     list[i], list[j] = list[j], list[i] | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
| func (s *Storage) CreateItems(items []Item) bool { | func (s *Storage) CreateItems(items []Item) bool { | ||||||
| 	tx, err := s.db.Begin() | 	tx, err := s.db.Begin() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -84,7 +104,10 @@ func (s *Storage) CreateItems(items []Item) bool { | |||||||
|  |  | ||||||
| 	now := time.Now().UTC() | 	now := time.Now().UTC() | ||||||
|  |  | ||||||
| 	for _, item := range items { |     itemsSorted := ItemList(items) | ||||||
|  |     sort.Sort(itemsSorted) | ||||||
|  |  | ||||||
|  | 	for _, item := range itemsSorted { | ||||||
| 		_, err = tx.Exec(` | 		_, err = tx.Exec(` | ||||||
| 			insert into items ( | 			insert into items ( | ||||||
| 				guid, feed_id, title, link, date, | 				guid, feed_id, title, link, date, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user