45 Commits

Author SHA1 Message Date
nkanaev
348693fa95 github: set workflow trigger conditions 2025-03-12 22:48:05 +00:00
nkanaev
b40c6fc9e4 github: run build after test 2025-03-12 22:22:40 +00:00
nkanaev
73fd637b23 github: test workflow 2025-03-12 22:15:18 +00:00
Nazar Kanaev
b8afa82a81 support multiple media links 2025-03-11 11:15:09 +00:00
nkanaev
097a2da5cb go fmt 2025-03-04 17:05:41 +00:00
nkanaev
e6d32946c1 add aria-pressed tag for the corresponding UI elements 2025-03-04 16:51:30 +00:00
nkanaev
fe4eaa4b8d fix start_url for manifest.json 2025-03-04 14:41:23 +00:00
nkanaev
48a671b285 add header accessibility tags 2025-03-04 14:12:46 +00:00
nkanaev
011c9c7668 add theme toggle button labels 2025-03-04 14:08:42 +00:00
nkanaev
f06fc1f750 add systray icon tooltip 2025-03-04 14:03:52 +00:00
dependabot[bot]
0e88d4284d Bump golang.org/x/net from 0.23.0 to 0.33.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.23.0 to 0.33.0.
- [Commits](https://github.com/golang/net/compare/v0.23.0...v0.33.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-04 13:15:37 +00:00
nkanaev
1615c6869f fix tests 2025-03-04 13:14:54 +00:00
Andre Heider
800f43b299 upload artifacts to push actions too 2025-03-04 12:46:07 +00:00
Andre Heider
15bff0a0c4 add linux arm and arm64 builders 2025-03-04 12:46:07 +00:00
Andre Heider
e1481f4aac don't use deprecated github actions 2025-03-04 12:46:07 +00:00
Andre Heider
7ef97ee6db offer common youtube playlists as feed 2025-03-04 12:37:27 +00:00
Nadia Santalla
d785fe4c5a Dockerfile: cache go build directories across builds 2025-03-04 12:22:17 +00:00
Nazar Kanaev
5254df53dc update docs 2024-12-04 21:44:10 +00:00
tillcash
7301eab99c Add Dynamic Disable Functionality to Navigation Buttons 2024-12-04 21:42:36 +00:00
Nazar Kanaev
ad138c3017 show article content in the list if the title is missing 2024-12-04 21:36:44 +00:00
Nazar Kanaev
b09c95d7ea navigation buttons (+ fix keyboard navigation in 1-pane view) 2024-12-04 15:57:34 +00:00
Nazar Kanaev
64611a0dd3 update changelog 2024-12-04 14:28:10 +00:00
Donovan Glover
321ad7608f fix: use noreferrer for external links
Based on the existing noreferrer code in the code base.
2024-11-28 12:58:00 +00:00
Nazar Kanaev
2a8b6ea935 update changelog 2024-11-25 21:50:38 +00:00
Nazar Kanaev
e9cbea500b autogenerate feed item guids 2024-11-25 21:35:04 +00:00
Nazar Kanaev
223039b2c6 do not send referrer for external images 2024-11-25 21:01:45 +00:00
funkycode
7402dfc4e6 fix the build 2024-11-09 23:10:43 +00:00
Tim Shaffer
6b12715506 Only read config dir if db is not provided. 2024-11-01 10:27:59 +00:00
Aidan Holm
2dc58c5c8e Fix user agent using hardcoded 1.0 version. 2024-10-30 09:49:33 +00:00
Nazar Kanaev
0cef51c6ac add sample links 2024-10-07 22:22:56 +01:00
Nazar Kanaev
2a4d974965 go fmt 2024-10-07 12:20:45 +01:00
dependabot[bot]
f71792d6a5 Bump actions/download-artifact from 2 to 4.1.7 in /.github/workflows
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 4.1.7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2...v4.1.7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-29 21:36:53 +01:00
Nazar Kanaev
b571042c5d change item table index 2024-06-21 12:29:09 +01:00
Nazar Kanaev
349c966c63 sqlite parameters 2024-06-21 10:56:48 +01:00
Nazar Kanaev
4a42b239cc update changelog 2024-06-16 11:39:23 +01:00
Karol Kosek
b9b3d2350c atom: Stop unescaping special HTML characters
The HTML data in Atom is escaped because the data needs to put as a
string to an XML file. If we are accessing it by reading the string
value, then it is already unescaped, as opposed to getting the raw
XML data.

XHTML data don't need to be unescaped either since the elements are
already encoded as is in tree. :)

Closes #198
2024-06-16 11:35:32 +01:00
nkanaev
b13cd85f0b update makefile 2024-05-14 13:22:14 +01:00
Jason Rogena
daffd721eb Allow overriding the GOARCH in the makefile
To allow for building yarr for other architectures, make the GOARCH
env var overridable in the makefile.

Signed-off-by: Jason Rogena <null+github.com@rogena.me>
2024-05-12 22:57:18 +01:00
dependabot[bot]
24232d72e9 Bump golang.org/x/net from 0.17.0 to 0.23.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-24 14:03:30 +01:00
Nazar Kanaev
4983e18e23 switch to articles feed on click 2024-04-17 22:04:14 +01:00
Nazar Kanaev
e1954e4cba update changelog 2024-04-17 21:45:25 +01:00
Nazar Kanaev
58420ae52b editable link fixes 2024-04-17 21:44:16 +01:00
Adam Szkoda
b01f71de1a Editable feed link 2024-04-17 21:26:09 +01:00
Nazar Kanaev
379aaed39e update changelog 2024-04-17 21:19:04 +01:00
Nazar Kanaev
dc20932060 apply selected theme to the login page 2024-04-17 21:17:04 +01:00
68 changed files with 1702 additions and 415 deletions

View File

@@ -1,8 +1,14 @@
name: build name: Build
on: on:
push: push:
tags: ['v*', 'test*'] tags:
- v*
workflow_dispatch:
workflow_run:
workflows: [Test]
types:
- completed
jobs: jobs:
build_macos: build_macos:
@@ -18,7 +24,7 @@ jobs:
with: with:
go-version: '^1.17' go-version: '^1.17'
- name: Cache Go Modules - name: Cache Go Modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -27,7 +33,7 @@ jobs:
- name: "Build" - name: "Build"
run: make build_macos run: make build_macos
- name: Upload - name: Upload
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: macos name: macos
path: _output/macos/yarr.app path: _output/macos/yarr.app
@@ -45,7 +51,7 @@ jobs:
with: with:
go-version: '^1.17' go-version: '^1.17'
- name: Cache Go Modules - name: Cache Go Modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@@ -54,7 +60,7 @@ jobs:
- name: "Build" - name: "Build"
run: make build_windows run: make build_windows
- name: Upload - name: Upload
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: windows name: windows
path: _output/windows/yarr.exe path: _output/windows/yarr.exe
@@ -72,38 +78,99 @@ jobs:
with: with:
go-version: '^1.17' go-version: '^1.17'
- name: Cache Go Modules - name: Cache Go Modules
uses: actions/cache@v2 uses: actions/cache@v4
with: with:
path: ~/go/pkg/mod path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} key: ${{ runner.os }}-amd64-go-${{ hashFiles('**/go.sum') }}
restore-keys: | restore-keys: |
${{ runner.os }}-go- ${{ runner.os }}-go-amd64
- name: "Build" - name: "Build"
run: make build_linux run: make build_linux
- name: Upload - name: Upload
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v4
with: with:
name: linux name: linux
path: _output/linux/yarr path: _output/linux/yarr
build_linux-arm:
name: Build for Linux ARM
runs-on: ubuntu-22.04
steps:
- name: Install dependencies
run: >
sudo apt-get install -y
gcc-arm-linux-gnueabihf
libc6-dev-armhf-cross
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: "Setup Go"
uses: actions/setup-go@v2
with:
go-version: '^1.17'
- name: Cache Go Modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-armv7-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-armv7
- name: "Build"
env:
CC: arm-linux-gnueabihf-gcc
GOARCH: arm
GOARM: 7
run: make build_linux
- name: Upload
uses: actions/upload-artifact@v4
with:
name: linux_arm
path: _output/linux/yarr
build_linux-arm64:
name: Build for Linux ARM64
runs-on: ubuntu-22.04
steps:
- name: Install dependencies
run: >
sudo apt-get install -y
gcc-aarch64-linux-gnu
libc6-dev-arm64-cross
- name: "Checkout"
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: "Setup Go"
uses: actions/setup-go@v2
with:
go-version: '^1.17'
- name: Cache Go Modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-arm64-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-arm64
- name: "Build"
env:
CC: aarch64-linux-gnu-gcc
GOARCH: arm64
run: make build_linux
- name: Upload
uses: actions/upload-artifact@v4
with:
name: linux_arm64
path: _output/linux/yarr
create_release: create_release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ !contains(github.ref, 'test') }} if: ${{ startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'test') }}
needs: [build_macos, build_windows, build_linux] needs: [build_macos, build_windows, build_linux, build_linux-arm, build_linux-arm64]
steps: steps:
- name: Create Release
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
prerelease: true
- name: Download Artifacts - name: Download Artifacts
uses: actions/download-artifact@v2 uses: actions/download-artifact@v4.1.7
with: with:
path: . path: .
- name: Preparation - name: Preparation
@@ -111,34 +178,24 @@ jobs:
ls -R ls -R
chmod u+x macos/Contents/MacOS/yarr chmod u+x macos/Contents/MacOS/yarr
chmod u+x linux/yarr chmod u+x linux/yarr
chmod u+x linux_arm/yarr
chmod u+x linux_arm64/yarr
mv macos yarr.app && zip -r yarr-macos.zip yarr.app mv macos yarr.app && zip -r yarr-${GITHUB_REF_NAME}-macos64.zip yarr.app
mv windows/yarr.exe . && zip yarr-windows.zip yarr.exe ( cd windows && zip ../yarr-${GITHUB_REF_NAME}-windows64.zip yarr.exe )
mv linux/yarr . && zip yarr-linux.zip yarr ( cd linux && zip ../yarr-${GITHUB_REF_NAME}-linux64.zip yarr )
- name: Upload MacOS ( cd linux_arm && zip ../yarr-${GITHUB_REF_NAME}-linux_arm.zip yarr )
uses: actions/upload-release-asset@v1 ( cd linux_arm64 && zip ../yarr-${GITHUB_REF_NAME}-linux_arm64.zip yarr )
- name: Upload Release
uses: softprops/action-gh-release@v2
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} draft: true
asset_path: ./yarr-macos.zip prerelease: true
asset_name: yarr-${{ github.ref }}-macos64.zip files: |
asset_content_type: application/zip yarr-${{ github.ref_name }}-macos64.zip
- name: Upload Windows yarr-${{ github.ref_name }}-windows64.zip
uses: actions/upload-release-asset@v1 yarr-${{ github.ref_name }}-linux64.zip
env: yarr-${{ github.ref_name }}-linux_arm.zip
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} yarr-${{ github.ref_name }}-linux_arm64.zip
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./yarr-windows.zip
asset_name: yarr-${{ github.ref }}-windows64.zip
asset_content_type: application/zip
- name: Upload Linux
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./yarr-linux.zip
asset_name: yarr-${{ github.ref }}-linux64.zip
asset_content_type: application/zip

19
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Test
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '^1.18'
- name: Run tests
run: make test

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
/_output /_output
/yarr /yarr
*.db *.db
*.db-shm
*.db-wal
*.syso *.syso
versioninfo.rc versioninfo.rc

View File

@@ -13,6 +13,7 @@ import (
"github.com/nkanaev/yarr/src/platform" "github.com/nkanaev/yarr/src/platform"
"github.com/nkanaev/yarr/src/server" "github.com/nkanaev/yarr/src/server"
"github.com/nkanaev/yarr/src/storage" "github.com/nkanaev/yarr/src/storage"
"github.com/nkanaev/yarr/src/worker"
) )
var Version string = "0.0" var Version string = "0.0"
@@ -89,12 +90,12 @@ func main() {
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
} }
if db == "" {
configPath, err := os.UserConfigDir() configPath, err := os.UserConfigDir()
if err != nil { if err != nil {
log.Fatal("Failed to get config dir: ", err) log.Fatal("Failed to get config dir: ", err)
} }
if db == "" {
storagePath := filepath.Join(configPath, "yarr") storagePath := filepath.Join(configPath, "yarr")
if err := os.MkdirAll(storagePath, 0755); err != nil { if err := os.MkdirAll(storagePath, 0755); err != nil {
log.Fatal("Failed to create app config dir: ", err) log.Fatal("Failed to create app config dir: ", err)
@@ -105,6 +106,7 @@ func main() {
log.Printf("using db file %s", db) log.Printf("using db file %s", db)
var username, password string var username, password string
var err error
if authfile != "" { if authfile != "" {
f, err := os.Open(authfile) f, err := os.Open(authfile)
if err != nil { if err != nil {
@@ -131,6 +133,7 @@ func main() {
log.Fatal("Failed to initialise database: ", err) log.Fatal("Failed to initialise database: ", err)
} }
worker.SetVersion(Version)
srv := server.NewServer(store, addr) srv := server.NewServer(store, addr)
if basepath != "" { if basepath != "" {

View File

@@ -1,11 +1,22 @@
# upcoming # upcoming
- (new) Fever API support (thanks to @icefed) - (new) Fever API support (thanks to @icefed)
- (new) editable feed link (thanks to @adaszko)
- (new) switch to feed by clicking the title in the article page (thanks to @tarasglek for suggestion)
- (new) support multiple media links
- (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) parsing atom feed titles (thanks to @wnh)
- (fix) sorting same-day batch articles (thanks to @lamescholar for the report) - (fix) sorting same-day batch articles (thanks to @lamescholar for the report)
- (fix) showing login page in the selected theme (thanks to @feddiriko for the report)
- (fix) parsing atom feeds with html elements (thanks to @tillcash & @toBeOfUse for the report, @krkk for the fix)
- (fix) parsing feeds with missing guids (thanks to @hoyii for the report)
- (fix) sending actual client version to servers (thanks to @aidanholm)
- (fix) error caused by missing config dir (thanks to @timster)
- (etc) load external images with no-referrer policy (thanks to @tillcash for the report)
- (etc) open external links with no-referrer policy (thanks to @donovanglover)
- (etc) show article content in the list if title is missing (thanks to @asimpson for suggestion)
# v2.4 (2023-08-15) # v2.4 (2023-08-15)

68
doc/samples.yml Normal file
View File

@@ -0,0 +1,68 @@
- site: https://vimeo.com/channels/staffpicks/videos
feed: https://vimeo.com/channels/staffpicks/videos/rss
tags: [vimeo, image]
- site: https://www.youtube.com/@everyframeapainting/videos
feed: https://www.youtube.com/feeds/videos.xml?channel_id=UCjFqcJQXGZ6T6sxyFB-5i6A"
tags: [youtube, image]
- site: https://iwdrm.tumblr.com/
feed: https://iwdrm.tumblr.com/rss
tags: [tumblr, image]
- site: https://falseknees.tumblr.com/
feed: https://falseknees.tumblr.com/rss
tags: [tumblr, image]
- site: https://accidentallyquadratic.tumblr.com/
feed: https://accidentallyquadratic.tumblr.com/rss
info: text blog with code sections
tags: [tumblr, text, code]
- site: https://www.flickr.com/photos/maratsafin/
feed: https://www.flickr.com/services/feeds/photos_public.gne?id=59021497@N07&lang=en-us&format=atom
tags: [flickr, image]
- site: https://www.reddit.com/r/comics
feed: https://www.reddit.com/r/comics.rss
tags: [reddit, image]
- site: https://www.reddit.com/r/AITAH
feed: https://www.reddit.com/r/AITAH.rss
tags: [reddit, text]
- site: https://idothei.wordpress.com/
feed: https://idothei.wordpress.com/feed/
tags: [wordpress, text]
- site: https://www.vidarholen.net/contents/blog/
feed: https://www.vidarholen.net/contents/blog/?feed=rss2
tags: [wordpress, text]
- site: https://blog.posthaven.com/
feed: https://blog.posthaven.com/posts.atom
tags: [posthaven, text]
- site: https://medium.com/@dailynewsletter
feed: https://medium.com/feed/@dailynewsletter
tags: [medium, text]
- site: https://thereveal.substack.com/
feed: https://thereveal.substack.com/feed
tags: [substack, text]
- site: https://tema.livejournal.com/
feed: https://tema.livejournal.com/data/rss
tags: [livejournal, text]
- site: https://mametter.hatenablog.com/
feed: https://mametter.hatenablog.com/feed
tags: [hatena, text]
- site: https://juliepowell.blogspot.com/
feed: https://juliepowell.blogspot.com/feeds/posts/default
tags: [blogger, text]
- site: https://micro.blog/val
feed: https://micro.blog/posts/val
tags: [json, microblog]

View File

@@ -2,7 +2,8 @@ FROM golang:alpine3.18 AS build
RUN apk add build-base git RUN apk add build-base git
WORKDIR /src WORKDIR /src
COPY . . COPY . .
RUN make build_linux RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/root/go/pkg \
make build_linux
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache ca-certificates && \ RUN apk add --no-cache ca-certificates && \

8
go.mod
View File

@@ -1,11 +1,11 @@
module github.com/nkanaev/yarr module github.com/nkanaev/yarr
go 1.17 go 1.18
require ( require (
github.com/mattn/go-sqlite3 v1.14.7 github.com/mattn/go-sqlite3 v1.14.7
golang.org/x/net v0.17.0 golang.org/x/net v0.33.0
golang.org/x/sys v0.13.0 golang.org/x/sys v0.28.0
) )
require golang.org/x/text v0.13.0 // indirect require golang.org/x/text v0.21.0 // indirect

49
go.sum
View File

@@ -1,45 +1,70 @@
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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-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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1,33 +1,34 @@
VERSION=2.4 VERSION=2.4
GITHASH=$(shell git rev-parse --short=8 HEAD) GITHASH=$(shell git rev-parse --short=8 HEAD)
CGO_ENABLED=1 GO_TAGS = sqlite_foreign_keys sqlite_json
GO_LDFLAGS = -s -w -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITHASH)'
GO_LDFLAGS = -s -w export GOARCH ?= amd64
GO_LDFLAGS := $(GO_LDFLAGS) -X 'main.Version=$(VERSION)' -X 'main.GitHash=$(GITHASH)' export CGO_ENABLED = 1
build_default: build_default:
mkdir -p _output mkdir -p _output
go build -tags "sqlite_foreign_keys" -ldflags="$(GO_LDFLAGS)" -o _output/yarr ./cmd/yarr go build -tags "$(GO_TAGS)" -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 macos" -ldflags="$(GO_LDFLAGS)" -o _output/macos/yarr ./cmd/yarr GOOS=darwin go build -tags "$(GO_TAGS) 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 ./cmd/package_macos -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 linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr ./cmd/yarr GOOS=linux go build -tags "$(GO_TAGS) linux" -ldflags="$(GO_LDFLAGS)" -o _output/linux/yarr ./cmd/yarr
build_windows: build_windows:
mkdir -p _output/windows mkdir -p _output/windows
go run ./cmd/generate_versioninfo -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 windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe ./cmd/yarr GOOS=windows go build -tags "$(GO_TAGS) windows" -ldflags="$(GO_LDFLAGS) -H windowsgui" -o _output/windows/yarr.exe ./cmd/yarr
serve: serve:
go run -tags "sqlite_foreign_keys" ./cmd/yarr -db local.db go run -tags "$(GO_TAGS)" ./cmd/yarr -db local.db
test: test:
go test -tags "sqlite_foreign_keys" ./... go test -tags "$(GO_TAGS)" ./...

View File

@@ -25,18 +25,21 @@
<div class="flex-grow-1"></div> <div class="flex-grow-1"></div>
<button class="toolbar-item" <button class="toolbar-item"
:class="{active: filterSelected == 'unread'}" :class="{active: filterSelected == 'unread'}"
:aria-pressed="filterSelected == 'unread'"
title="Unread" title="Unread"
@click="filterSelected = 'unread'"> @click="filterSelected = 'unread'">
<span class="icon">{% inline "circle-full.svg" %}</span> <span class="icon">{% inline "circle-full.svg" %}</span>
</button> </button>
<button class="toolbar-item" <button class="toolbar-item"
:class="{active: filterSelected == 'starred'}" :class="{active: filterSelected == 'starred'}"
:aria-pressed="filterSelected == 'starred'"
title="Starred" title="Starred"
@click="filterSelected = 'starred'"> @click="filterSelected = 'starred'">
<span class="icon">{% inline "star-full.svg" %}</span> <span class="icon">{% inline "star-full.svg" %}</span>
</button> </button>
<button class="toolbar-item" <button class="toolbar-item"
:class="{active: filterSelected == ''}" :class="{active: filterSelected == ''}"
:aria-pressed="filterSelected == ''"
title="All" title="All"
@click="filterSelected = ''"> @click="filterSelected = ''">
<span class="icon">{% inline "assorted.svg" %}</span> <span class="icon">{% inline "assorted.svg" %}</span>
@@ -59,10 +62,12 @@
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<header class="dropdown-header">Theme</header> <header class="dropdown-header" role="heading" aria-level="2">Theme</header>
<div class="row text-center m-0"> <div class="row text-center m-0">
<button class="btn btn-link col-4 px-0 rounded-0" <button class="btn btn-link col-4 px-0 rounded-0"
:class="'theme-'+t" :class="'theme-'+t"
:aria-label="t"
:aria-pressed="theme.name == t"
@click.stop="theme.name = t" @click.stop="theme.name = t"
v-for="t in ['light', 'sepia', 'night']"> v-for="t in ['light', 'sepia', 'night']">
<span class="icon" v-if="theme.name == t">{% inline "check.svg" %}</span> <span class="icon" v-if="theme.name == t">{% inline "check.svg" %}</span>
@@ -71,25 +76,25 @@
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<header class="dropdown-header">Auto Refresh</header> <header class="dropdown-header" role="heading" aria-level="2">Auto Refresh</header>
<div class="row text-center m-0"> <div class="row text-center m-0">
<button class="dropdown-item col-4 px-0" :class="{active: !refreshRate}" @click.stop="refreshRate = 0">0</button> <button class="dropdown-item col-4 px-0" :aria-pressed="!refreshRate" :class="{active: !refreshRate}" @click.stop="refreshRate = 0">0</button>
<button class="dropdown-item col-4 px-0" :class="{active: refreshRate == 10}" @click.stop="refreshRate = 10">10m</button> <button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 10" :class="{active: refreshRate == 10}" @click.stop="refreshRate = 10">10m</button>
<button class="dropdown-item col-4 px-0" :class="{active: refreshRate == 30}" @click.stop="refreshRate = 30">30m</button> <button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 30" :class="{active: refreshRate == 30}" @click.stop="refreshRate = 30">30m</button>
<button class="dropdown-item col-4 px-0" :class="{active: refreshRate == 60}" @click.stop="refreshRate = 60">1h</button> <button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 60" :class="{active: refreshRate == 60}" @click.stop="refreshRate = 60">1h</button>
<button class="dropdown-item col-4 px-0" :class="{active: refreshRate == 120}" @click.stop="refreshRate = 120">2h</button> <button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 120" :class="{active: refreshRate == 120}" @click.stop="refreshRate = 120">2h</button>
<button class="dropdown-item col-4 px-0" :class="{active: refreshRate == 240}" @click.stop="refreshRate = 240">4h</button> <button class="dropdown-item col-4 px-0" :aria-pressed="refreshRate == 240" :class="{active: refreshRate == 240}" @click.stop="refreshRate = 240">4h</button>
</div> </div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<header class="dropdown-header">Show first</header> <header class="dropdown-header" role="heading" aria-level="2">Show first</header>
<div class="d-flex text-center"> <div class="d-flex text-center">
<button class="dropdown-item px-0" :class="{active: itemSortNewestFirst}" @click.stop="itemSortNewestFirst=true">New</button> <button class="dropdown-item px-0" :aria-pressed="itemSortNewestFirst" :class="{active: itemSortNewestFirst}" @click.stop="itemSortNewestFirst=true">New</button>
<button class="dropdown-item px-0" :class="{active: !itemSortNewestFirst}" @click.stop="itemSortNewestFirst=false">Old</button> <button class="dropdown-item px-0" :aria-pressed="!itemSortNewestFirst" :class="{active: !itemSortNewestFirst}" @click.stop="itemSortNewestFirst=false">Old</button>
</div> </div>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<header class="dropdown-header">Subscriptions</header> <header class="dropdown-header" role="heading" aria-level="2">Subscriptions</header>
<form id="opml-import-form" enctype="multipart/form-data" tabindex="-1"> <form id="opml-import-form" enctype="multipart/form-data" tabindex="-1">
<input type="file" <input type="file"
id="opml-import" id="opml-import"
@@ -206,12 +211,12 @@
<template v-slot:button> <template v-slot:button>
<span class="icon">{% inline "more-horizontal.svg" %}</span> <span class="icon">{% inline "more-horizontal.svg" %}</span>
</template> </template>
<header class="dropdown-header">{{ current.feed.title }}</header> <header class="dropdown-header" role="heading" aria-level="2">{{ current.feed.title }}</header>
<a class="dropdown-item" :href="current.feed.link" target="_blank" v-if="current.feed.link"> <a class="dropdown-item" :href="current.feed.link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" v-if="current.feed.link">
<span class="icon mr-1">{% inline "globe.svg" %}</span> <span class="icon mr-1">{% inline "globe.svg" %}</span>
Website Website
</a> </a>
<a class="dropdown-item" :href="current.feed.feed_link" target="_blank" v-if="current.feed.feed_link"> <a class="dropdown-item" :href="current.feed.feed_link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" v-if="current.feed.feed_link">
<span class="icon mr-1">{% inline "rss.svg" %}</span> <span class="icon mr-1">{% inline "rss.svg" %}</span>
Feed Link Feed Link
</a> </a>
@@ -220,8 +225,12 @@
<span class="icon mr-1">{% inline "edit.svg" %}</span> <span class="icon mr-1">{% inline "edit.svg" %}</span>
Rename Rename
</button> </button>
<button class="dropdown-item" @click="updateFeedLink(current.feed)" v-if="current.feed.feed_link">
<span class="icon mr-1">{% inline "edit.svg" %}</span>
Change Link
</button>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<header class="dropdown-header">Move to...</header> <header class="dropdown-header" role="heading" aria-level="2">Move to...</header>
<button class="dropdown-item" <button class="dropdown-item"
v-if="folder.id != current.feed.folder_id" v-if="folder.id != current.feed.folder_id"
v-for="folder in folders" v-for="folder in folders"
@@ -251,7 +260,7 @@
<template v-slot:button> <template v-slot:button>
<span class="icon">{% inline "more-horizontal.svg" %}</span> <span class="icon">{% inline "more-horizontal.svg" %}</span>
</template> </template>
<header class="dropdown-header">{{ current.folder.title }}</header> <header class="dropdown-header" role="heading" aria-level="2">{{ current.folder.title }}</header>
<button class="dropdown-item" @click="renameFolder(current.folder)"> <button class="dropdown-item" @click="renameFolder(current.folder)">
<span class="icon mr-1">{% inline "edit.svg" %}</span> <span class="icon mr-1">{% inline "edit.svg" %}</span>
Rename Rename
@@ -322,10 +331,16 @@
title="Read Here"> title="Read Here">
<span class="icon" :class="{'icon-loading': loading.readability}">{% inline "book-open.svg" %}</span> <span class="icon" :class="{'icon-loading': loading.readability}">{% inline "book-open.svg" %}</span>
</button> </button>
<a class="toolbar-item" :href="itemSelectedDetails.link" target="_blank" title="Open Link"> <a class="toolbar-item" :href="itemSelectedDetails.link" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer" title="Open Link">
<span class="icon">{% inline "external-link.svg" %}</span> <span class="icon">{% inline "external-link.svg" %}</span>
</a> </a>
<div class="flex-grow-1"></div> <div class="flex-grow-1"></div>
<button class="toolbar-item" @click="navigateToItem(-1)" title="Previous Article" :disabled="itemSelected == items[0].id">
<span class="icon">{% inline "chevron-left.svg" %}</span>
</button>
<button class="toolbar-item" @click="navigateToItem(+1)" title="Next Article" :disabled="itemSelected == items[items.length - 1].id">
<span class="icon">{% inline "chevron-right.svg" %}</span>
</button>
<button class="toolbar-item" @click="itemSelected=null" title="Close Article"> <button class="toolbar-item" @click="itemSelected=null" title="Close Article">
<span class="icon">{% inline "x.svg" %}</span> <span class="icon">{% inline "x.svg" %}</span>
</button> </button>
@@ -338,13 +353,23 @@
<div class="content-wrapper"> <div class="content-wrapper">
<h1><b>{{ itemSelectedDetails.title || 'untitled' }}</b></h1> <h1><b>{{ itemSelectedDetails.title || 'untitled' }}</b></h1>
<div class="text-muted"> <div class="text-muted">
<div>{{ (feedsById[itemSelectedDetails.feed_id] || {}).title }}</div> <div>
<span class="cursor-pointer" @click="feedSelected = 'feed:'+(feedsById[itemSelectedDetails.feed_id] || {}).id">
{{ (feedsById[itemSelectedDetails.feed_id] || {}).title }}
</span>
</div>
<time>{{ formatDate(itemSelectedDetails.date) }}</time> <time>{{ formatDate(itemSelectedDetails.date) }}</time>
</div> </div>
<hr> <hr>
<div v-if="!itemSelectedReadability"> <div v-if="!itemSelectedReadability">
<img :src="itemSelectedDetails.image" v-if="itemSelectedDetails.image" class="mb-3"> <div v-if="contentImages.length">
<audio class="w-100" controls v-if="itemSelectedDetails.podcast_url" :src="itemSelectedDetails.podcast_url"></audio> <figure v-for="media in contentImages">
<img :src="media.url" loading="lazy">
<figcaption v-if="media.description" v-html="media.description"></figcaption>
</figure>
</div>
<audio class="w-100" controls v-for="media in contentAudios" :src="media.url"></audio>
<video class="w-100" controls v-for="media in contentVideos" :src="media.url"></video>
</div> </div>
<div v-html="itemSelectedContent"></div> <div v-html="itemSelectedContent"></div>
</div> </div>

View File

@@ -2,6 +2,26 @@
var TITLE = document.title var TITLE = document.title
function scrollto(target, scroll) {
var padding = 10
var targetRect = target.getBoundingClientRect()
var scrollRect = scroll.getBoundingClientRect()
// target
var relativeOffset = targetRect.y - scrollRect.y
var absoluteOffset = relativeOffset + scroll.scrollTop
if (padding <= relativeOffset && relativeOffset + targetRect.height <= scrollRect.height - padding) return
var newPos = scroll.scrollTop
if (relativeOffset < padding) {
newPos = absoluteOffset - padding
} else {
newPos = absoluteOffset - scrollRect.height + targetRect.height + padding
}
scroll.scrollTop = Math.round(newPos)
}
var debounce = function(callback, wait) { var debounce = function(callback, wait) {
var timeout var timeout
return function() { return function() {
@@ -278,6 +298,18 @@ var vm = new Vue({
return this.itemSelectedDetails.content || '' return this.itemSelectedDetails.content || ''
}, },
contentImages: function() {
if (!this.itemSelectedDetails) return []
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'image')
},
contentAudios: function() {
if (!this.itemSelectedDetails) return []
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'audio')
},
contentVideos: function() {
if (!this.itemSelectedDetails) return []
return (this.itemSelectedDetails.media_links || []).filter(l => l.type === 'video')
}
}, },
watch: { watch: {
'theme': { 'theme': {
@@ -407,7 +439,7 @@ var vm = new Vue({
vm.feeds = values[1] vm.feeds = values[1]
}) })
}, },
refreshItems: function(loadMore) { refreshItems: function(loadMore = false) {
if (this.feedSelected === null) { if (this.feedSelected === null) {
vm.items = [] vm.items = []
vm.itemsHasMore = false vm.itemsHasMore = false
@@ -420,7 +452,7 @@ var vm = new Vue({
} }
this.loading.items = true this.loading.items = true
api.items.list(query).then(function(data) { return api.items.list(query).then(function(data) {
if (loadMore) { if (loadMore) {
vm.items = vm.items.concat(data.list) vm.items = vm.items.concat(data.list)
} else { } else {
@@ -443,13 +475,17 @@ var vm = new Vue({
var scale = (parseFloat(getComputedStyle(document.documentElement).fontSize) || 16) / 16 var scale = (parseFloat(getComputedStyle(document.documentElement).fontSize) || 16) / 16
var el = this.$refs.itemlist var el = this.$refs.itemlist
if (el.scrollHeight === 0) return false // element is invisible (responsive design)
var closeToBottom = (el.scrollHeight - el.scrollTop - el.offsetHeight) < bottomSpace * scale var closeToBottom = (el.scrollHeight - el.scrollTop - el.offsetHeight) < bottomSpace * scale
return closeToBottom return closeToBottom
}, },
loadMoreItems: function(event, el) { loadMoreItems: function(event, el) {
if (!this.itemsHasMore) return if (!this.itemsHasMore) return
if (this.loading.items) return if (this.loading.items) return
if (this.itemListCloseToBottom()) this.refreshItems(true) if (this.itemListCloseToBottom()) return this.refreshItems(true)
if (this.itemSelected && this.itemSelected === this.items[this.items.length - 1].id) return this.refreshItems(true)
}, },
markItemsRead: function() { markItemsRead: function() {
var query = this.getItemsQuery() var query = this.getItemsQuery()
@@ -523,6 +559,14 @@ var vm = new Vue({
}) })
} }
}, },
updateFeedLink: function(feed) {
var newLink = prompt('Enter feed link', feed.feed_link)
if (newLink) {
api.feeds.update(feed.id, {feed_link: newLink}).then(function() {
feed.feed_link = newLink
})
}
},
renameFeed: function(feed) { renameFeed: function(feed) {
var newTitle = prompt('Enter new title', feed.title) var newTitle = prompt('Enter new title', feed.title)
if (newTitle) { if (newTitle) {
@@ -675,6 +719,65 @@ var vm = new Vue({
this.filteredFolderStats = statsFolders this.filteredFolderStats = statsFolders
this.filteredTotalStats = statsTotal this.filteredTotalStats = statsTotal
}, },
// navigation helper, navigate relative to selected item
navigateToItem: function(relativePosition) {
let vm = this
if (vm.itemSelected == null) {
// if no item is selected, select first
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
return
}
var itemPosition = vm.items.findIndex(function(x) { return x.id === vm.itemSelected })
if (itemPosition === -1) {
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
return
}
var newPosition = itemPosition + relativePosition
if (newPosition < 0 || newPosition >= vm.items.length) return
vm.itemSelected = vm.items[newPosition].id
vm.$nextTick(function() {
var scroll = document.querySelector('#item-list-scroll')
var handle = scroll.querySelector('input[type=radio]:checked')
var target = handle && handle.parentElement
if (target && scroll) scrollto(target, scroll)
vm.loadMoreItems()
})
},
// navigation helper, navigate relative to selected feed
navigateToFeed: function(relativePosition) {
let vm = this
var navigationList = Array.from(document.querySelectorAll('#col-feed-list input[name=feed]'))
.filter(function(r) { return r.offsetParent !== null && r.value !== 'folder:null' })
.map(function(r) { return r.value })
var currentFeedPosition = navigationList.indexOf(vm.feedSelected)
if (currentFeedPosition == -1) {
vm.feedSelected = ''
return
}
var newPosition = currentFeedPosition+relativePosition
if (newPosition < 0 || newPosition >= navigationList.length) return
vm.feedSelected = navigationList[newPosition]
vm.$nextTick(function() {
var scroll = document.querySelector('#feed-list-scroll')
var handle = scroll.querySelector('input[type=radio]:checked')
var target = handle && handle.parentElement
if (target && scroll) scrollto(target, scroll)
})
},
} }
}) })

View File

@@ -1,79 +1,4 @@
function scrollto(target, scroll) {
var padding = 10
var targetRect = target.getBoundingClientRect()
var scrollRect = scroll.getBoundingClientRect()
// target
var relativeOffset = targetRect.y - scrollRect.y
var absoluteOffset = relativeOffset + scroll.scrollTop
if (padding <= relativeOffset && relativeOffset + targetRect.height <= scrollRect.height - padding) return
var newPos = scroll.scrollTop
if (relativeOffset < padding) {
newPos = absoluteOffset - padding
} else {
newPos = absoluteOffset - scrollRect.height + targetRect.height + padding
}
scroll.scrollTop = Math.round(newPos)
}
var helperFunctions = { var helperFunctions = {
// navigation helper, navigate relative to selected item
navigateToItem: function(relativePosition) {
if (vm.itemSelected == null) {
// if no item is selected, select first
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
return
}
var itemPosition = vm.items.findIndex(function(x) { return x.id === vm.itemSelected })
if (itemPosition === -1) {
if (vm.items.length !== 0) vm.itemSelected = vm.items[0].id
return
}
var newPosition = itemPosition + relativePosition
if (newPosition < 0 || newPosition >= vm.items.length) return
vm.itemSelected = vm.items[newPosition].id
vm.$nextTick(function() {
var scroll = document.querySelector('#item-list-scroll')
var handle = scroll.querySelector('input[type=radio]:checked')
var target = handle && handle.parentElement
if (target && scroll) scrollto(target, scroll)
})
},
// navigation helper, navigate relative to selected feed
navigateToFeed: function(relativePosition) {
var navigationList = Array.from(document.querySelectorAll('#col-feed-list input[name=feed]'))
.filter(function(r) { return r.offsetParent !== null && r.value !== 'folder:null' })
.map(function(r) { return r.value })
var currentFeedPosition = navigationList.indexOf(vm.feedSelected)
if (currentFeedPosition == -1) {
vm.feedSelected = ''
return
}
var newPosition = currentFeedPosition+relativePosition
if (newPosition < 0 || newPosition >= navigationList.length) return
vm.feedSelected = navigationList[newPosition]
vm.$nextTick(function() {
var scroll = document.querySelector('#feed-list-scroll')
var handle = scroll.querySelector('input[type=radio]:checked')
var target = handle && handle.parentElement
if (target && scroll) scrollto(target, scroll)
})
},
scrollContent: function(direction) { scrollContent: function(direction) {
var padding = 40 var padding = 40
var scroll = document.querySelector('.content') var scroll = document.querySelector('.content')
@@ -92,7 +17,7 @@ var helperFunctions = {
var shortcutFunctions = { var shortcutFunctions = {
openItemLink: function() { openItemLink: function() {
if (vm.itemSelectedDetails && vm.itemSelectedDetails.link) { if (vm.itemSelectedDetails && vm.itemSelectedDetails.link) {
window.open(vm.itemSelectedDetails.link, '_blank') window.open(vm.itemSelectedDetails.link, '_blank', 'noopener,noreferrer')
} }
}, },
toggleReadability: function() { toggleReadability: function() {
@@ -118,16 +43,16 @@ var shortcutFunctions = {
document.getElementById("searchbar").focus() document.getElementById("searchbar").focus()
}, },
nextItem(){ nextItem(){
helperFunctions.navigateToItem(+1) vm.navigateToItem(+1)
}, },
previousItem() { previousItem() {
helperFunctions.navigateToItem(-1) vm.navigateToItem(-1)
}, },
nextFeed(){ nextFeed(){
helperFunctions.navigateToFeed(+1) vm.navigateToFeed(+1)
}, },
previousFeed() { previousFeed() {
helperFunctions.navigateToFeed(-1) vm.navigateToFeed(-1)
}, },
scrollForward: function() { scrollForward: function() {
helperFunctions.scrollContent(+1) helperFunctions.scrollContent(+1)

View File

@@ -22,7 +22,7 @@
} }
</style> </style>
</head> </head>
<body> <body class="theme-{% .settings.theme_name %}">
<form action="" method="post"> <form action="" method="post">
<img src="./static/graphicarts/anchor.svg" alt=""> <img src="./static/graphicarts/anchor.svg" alt="">
{% if .error %} {% if .error %}

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"regexp" "regexp"
"strings" "strings"
"unicode"
"golang.org/x/net/html" "golang.org/x/net/html"
) )
@@ -61,3 +62,16 @@ func ExtractText(content string) string {
text = whitespaceRegex.ReplaceAllLiteralString(text, " ") text = whitespaceRegex.ReplaceAllLiteralString(text, " ")
return text return text
} }
func TruncateText(input string, size int) string {
runes := []rune(input)
if len(runes) <= size {
return input
}
for i := size - 1; i > 0; i-- {
if unicode.IsSpace(runes[i]) {
return string(runes[:i]) + " ..."
}
}
return input
}

View File

@@ -24,3 +24,21 @@ func TestExtractText(t *testing.T) {
} }
} }
} }
func TestTruncateText(t *testing.T) {
input := "Lorem ipsum — классический текст-«рыба»"
size := 30
want := "Lorem ipsum — классический ..."
have := TruncateText(input, size)
if want != have {
t.Errorf("\nsize: %d\nwant: %#v\nhave: %#v", size, want, have)
}
size = 1000
want = input
have = TruncateText(input, size)
if want != have {
t.Errorf("\nsize: %d\nwant: %#v\nhave: %#v", size, want, have)
}
}

View File

@@ -167,7 +167,7 @@ func getExtraAttributes(tagName string) ([]string, []string) {
case "iframe": case "iframe":
return []string{"sandbox", "loading"}, []string{`sandbox="allow-scripts allow-same-origin allow-popups"`, `loading="lazy"`} return []string{"sandbox", "loading"}, []string{`sandbox="allow-scripts allow-same-origin allow-popups"`, `loading="lazy"`}
case "img": case "img":
return []string{"loading"}, []string{`loading="lazy"`} return []string{"loading"}, []string{`loading="lazy"`, `referrerpolicy="no-referrer"`}
default: default:
return nil, nil return nil, nil
} }

View File

@@ -8,10 +8,11 @@ import "testing"
func TestValidInput(t *testing.T) { func TestValidInput(t *testing.T) {
input := `<p>This is a <strong>text</strong> with an image: <img src="http://example.org/" alt="Test" loading="lazy">.</p>` input := `<p>This is a <strong>text</strong> with an image: <img src="http://example.org/" alt="Test" loading="lazy">.</p>`
output := Sanitize("http://example.org/", input) want := `<p>This is a <strong>text</strong> with an image: <img src="http://example.org/" alt="Test" loading="lazy" referrerpolicy="no-referrer">.</p>`
have := Sanitize("http://example.org/", input)
if input != output { if have != want {
t.Errorf(`Wrong output: "%s" != "%s"`, input, output) t.Errorf("Wrong output: \nwant: %#v\nhave: %#v", want, have)
} }
} }
@@ -27,31 +28,31 @@ func TestImgWithTextDataURL(t *testing.T) {
func TestImgWithDataURL(t *testing.T) { func TestImgWithDataURL(t *testing.T) {
input := `<img src="data:image/gif;base64,test" alt="Example">` input := `<img src="data:image/gif;base64,test" alt="Example">`
expected := `<img src="data:image/gif;base64,test" alt="Example" loading="lazy">` want := `<img src="data:image/gif;base64,test" alt="Example" loading="lazy" referrerpolicy="no-referrer">`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if output != expected { if have != want {
t.Errorf(`Wrong output: %s`, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }
func TestImgWithSrcset(t *testing.T) { func TestImgWithSrcset(t *testing.T) {
input := `<img srcset="example-320w.jpg, example-480w.jpg 1.5x, example-640w.jpg 2x, example-640w.jpg 640w" src="example-640w.jpg" alt="Example">` input := `<img srcset="example-320w.jpg, example-480w.jpg 1.5x, example-640w.jpg 2x, example-640w.jpg 640w" src="example-640w.jpg" alt="Example">`
expected := `<img srcset="http://example.org/example-320w.jpg, http://example.org/example-480w.jpg 1.5x, http://example.org/example-640w.jpg 2x, http://example.org/example-640w.jpg 640w" src="http://example.org/example-640w.jpg" alt="Example" loading="lazy">` want := `<img srcset="http://example.org/example-320w.jpg, http://example.org/example-480w.jpg 1.5x, http://example.org/example-640w.jpg 2x, http://example.org/example-640w.jpg 640w" src="http://example.org/example-640w.jpg" alt="Example" loading="lazy" referrerpolicy="no-referrer">`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if output != expected { if have != want {
t.Errorf(`Wrong output: %s`, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }
func TestImgWithSrcsetAndDataURL(t *testing.T) { func TestImgWithSrcsetAndDataURL(t *testing.T) {
input := `<img srcset="data:image/gif;base64,test" src="http://example.org/example-320w.jpg" alt="Example">` input := `<img srcset="data:image/gif;base64,test" src="http://example.org/example-320w.jpg" alt="Example">`
expected := `<img srcset="data:image/gif;base64,test" src="http://example.org/example-320w.jpg" alt="Example" loading="lazy">` want := `<img srcset="data:image/gif;base64,test" src="http://example.org/example-320w.jpg" alt="Example" loading="lazy" referrerpolicy="no-referrer">`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if output != expected { if have != want {
t.Errorf(`Wrong output: %s`, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }
@@ -67,16 +68,16 @@ func TestSourceWithSrcsetAndMedia(t *testing.T) {
func TestMediumImgWithSrcset(t *testing.T) { func TestMediumImgWithSrcset(t *testing.T) {
input := `<img alt="Image for post" class="t u v ef aj" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" width="2730" height="3407">` input := `<img alt="Image for post" class="t u v ef aj" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" width="2730" height="3407">`
expected := `<img alt="Image for post" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" loading="lazy">` want := `<img alt="Image for post" src="https://miro.medium.com/max/5460/1*aJ9JibWDqO81qMfNtqgqrw.jpeg" srcset="https://miro.medium.com/max/552/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 276w, https://miro.medium.com/max/1000/1*aJ9JibWDqO81qMfNtqgqrw.jpeg 500w" sizes="500px" loading="lazy" referrerpolicy="no-referrer">`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if output != expected { if have != want {
t.Errorf(`Wrong output: %s`, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }
func TestSelfClosingTags(t *testing.T) { func TestSelfClosingTags(t *testing.T) {
input := `<p>This <br> is a <strong>text</strong> <br/>with an image: <img src="http://example.org/" alt="Test" loading="lazy"/>.</p>` input := `<p>This <br> is a <strong>text</strong><br/>.</p>`
output := Sanitize("http://example.org/", input) output := Sanitize("http://example.org/", input)
if input != output { if input != output {
@@ -95,11 +96,11 @@ func TestTable(t *testing.T) {
func TestRelativeURL(t *testing.T) { func TestRelativeURL(t *testing.T) {
input := `This <a href="/test.html">link is relative</a> and this image: <img src="../folder/image.png"/>` input := `This <a href="/test.html">link is relative</a> and this image: <img src="../folder/image.png"/>`
expected := `This <a href="http://example.org/test.html" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">link is relative</a> and this image: <img src="http://example.org/folder/image.png" loading="lazy"/>` want := `This <a href="http://example.org/test.html" rel="noopener noreferrer" target="_blank" referrerpolicy="no-referrer">link is relative</a> and this image: <img src="http://example.org/folder/image.png" loading="lazy" referrerpolicy="no-referrer"/>`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if expected != output { if want != have {
t.Errorf(`Wrong output: "%s" != "%s"`, expected, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }
@@ -165,11 +166,11 @@ func TestInvalidNestedTag(t *testing.T) {
func TestValidIFrame(t *testing.T) { func TestValidIFrame(t *testing.T) {
input := `<iframe src="http://example.org/"></iframe>` input := `<iframe src="http://example.org/"></iframe>`
expected := `<iframe src="http://example.org/" sandbox="allow-scripts allow-same-origin allow-popups" loading="lazy"></iframe>` want := `<iframe src="http://example.org/" sandbox="allow-scripts allow-same-origin allow-popups" loading="lazy"></iframe>`
output := Sanitize("http://example.org/", input) have := Sanitize("http://example.org/", input)
if expected != output { if want != have {
t.Errorf("Wrong output:\nwant: %s\nhave: %s", expected, output) t.Errorf("Wrong output:\nwant: %s\nhave: %s", want, have)
} }
} }

View File

@@ -1,6 +1,7 @@
package scraper package scraper
import ( import (
"net/url"
"strings" "strings"
"github.com/nkanaev/yarr/src/content/htmlutil" "github.com/nkanaev/yarr/src/content/htmlutil"
@@ -35,6 +36,18 @@ func FindFeeds(body string, base string) map[string]string {
link := htmlutil.AbsoluteUrl(href, base) link := htmlutil.AbsoluteUrl(href, base)
if link != "" { if link != "" {
candidates[link] = name candidates[link] = name
l, err := url.Parse(link)
if err == nil && l.Host == "www.youtube.com" && l.Path == "/feeds/videos.xml" {
// https://wiki.archiveteam.org/index.php/YouTube/Technical_details#Playlists
channelID, found := strings.CutPrefix(l.Query().Get("channel_id"), "UC")
if found {
const url string = "https://www.youtube.com/feeds/videos.xml?playlist_id="
candidates[url+"UULF"+channelID] = name + " - Videos"
candidates[url+"UULV"+channelID] = name + " - Live Streams"
candidates[url+"UUSH"+channelID] = name + " - Short videos"
}
}
} }
} }

View File

@@ -3,7 +3,6 @@ package parser
import ( import (
"encoding/xml" "encoding/xml"
"html"
"io" "io"
"strings" "strings"
@@ -58,7 +57,7 @@ func (a *atomText) String() string {
if a.Type == "xhtml" { if a.Type == "xhtml" {
data = a.XML data = a.XML
} }
return html.UnescapeString(strings.TrimSpace(data)) return strings.TrimSpace(data)
} }
func (links atomLinks) First(rel string) string { func (links atomLinks) First(rel string) string {
@@ -90,6 +89,8 @@ func ParseAtom(r io.Reader) (*Feed, error) {
guidFromID = srcitem.ID + "::" + srcitem.Updated guidFromID = srcitem.ID + "::" + srcitem.Updated
} }
mediaLinks := srcitem.mediaLinks()
link := firstNonEmpty(srcitem.OrigLink, srcitem.Links.First("alternate"), srcitem.Links.First(""), linkFromID) link := firstNonEmpty(srcitem.OrigLink, srcitem.Links.First("alternate"), srcitem.Links.First(""), linkFromID)
dstfeed.Items = append(dstfeed.Items, Item{ dstfeed.Items = append(dstfeed.Items, Item{
GUID: firstNonEmpty(guidFromID, srcitem.ID, link), GUID: firstNonEmpty(guidFromID, srcitem.ID, link),
@@ -97,8 +98,7 @@ func ParseAtom(r io.Reader) (*Feed, error) {
URL: link, URL: link,
Title: srcitem.Title.Text(), Title: srcitem.Title.Text(),
Content: firstNonEmpty(srcitem.Content.String(), srcitem.Summary.String(), srcitem.firstMediaDescription()), Content: firstNonEmpty(srcitem.Content.String(), srcitem.Summary.String(), srcitem.firstMediaDescription()),
ImageURL: srcitem.firstMediaThumbnail(), MediaLinks: mediaLinks,
AudioURL: "",
}) })
} }
return dstfeed, nil return dstfeed, nil

View File

@@ -45,8 +45,6 @@ func TestAtom(t *testing.T) {
URL: "http://example.org/2003/12/13/atom03.html", URL: "http://example.org/2003/12/13/atom03.html",
Title: "Atom-Powered Robots Run Amok", Title: "Atom-Powered Robots Run Amok",
Content: `<div xmlns="http://www.w3.org/1999/xhtml"><p>This is the entry content.</p></div>`, Content: `<div xmlns="http://www.w3.org/1999/xhtml"><p>This is the entry content.</p></div>`,
ImageURL: "",
AudioURL: "",
}, },
}, },
} }
@@ -141,9 +139,15 @@ func TestAtomImageLink(t *testing.T) {
</entry> </entry>
</feed> </feed>
`)) `))
have := feed.Items[0].ImageURL if len(feed.Items[0].MediaLinks) != 1 {
want := `https://example.com/image.png?width=100&height=100` t.Fatalf("Expected 1 media link, got: %#v", feed.Items[0].MediaLinks)
if want != have { }
have := feed.Items[0].MediaLinks[0]
want := MediaLink{
URL: `https://example.com/image.png?width=100&height=100`,
Type: "image",
}
if !reflect.DeepEqual(want, have) {
t.Fatalf("item.image_url doesn't match\nwant: %#v\nhave: %#v\n", want, have) t.Fatalf("item.image_url doesn't match\nwant: %#v\nhave: %#v\n", want, have)
} }
} }
@@ -165,8 +169,8 @@ func TestAtomImageLinkDuplicated(t *testing.T) {
if want != have { if want != have {
t.Fatalf("want: %#v\nhave: %#v\n", want, have) t.Fatalf("want: %#v\nhave: %#v\n", want, have)
} }
if feed.Items[0].ImageURL != "" { if len(feed.Items[0].MediaLinks) != 0 {
t.Fatal("item.image_url must be unset if present in the content") t.Fatal("item media link must be excluded if present in the content")
} }
} }
@@ -214,3 +218,19 @@ func TestAtomLinkInID(t *testing.T) {
t.Fatalf("\nwant: %#v\nhave: %#v\n", want, have) t.Fatalf("\nwant: %#v\nhave: %#v\n", want, have)
} }
} }
func TestAtomDoesntEscapeHTMLTags(t *testing.T) {
feed, _ := Parse(strings.NewReader(`
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<entry><summary type="html">&amp;lt;script&amp;gt;alert(1);&amp;lt;/script&amp;gt;</summary></entry>
</feed>
`))
have := feed.Items[0].Content
want := "&lt;script&gt;alert(1);&lt;/script&gt;"
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want)
t.Logf("have: %#v", have)
t.FailNow()
}
}

View File

@@ -2,6 +2,7 @@ package parser
import ( import (
"bytes" "bytes"
"crypto/sha256"
"encoding/xml" "encoding/xml"
"errors" "errors"
"fmt" "fmt"
@@ -119,6 +120,7 @@ func ParseAndFix(r io.Reader, baseURL, fallbackEncoding string) (*Feed, error) {
} }
feed.TranslateURLs(baseURL) feed.TranslateURLs(baseURL)
feed.SetMissingDatesTo(time.Now()) feed.SetMissingDatesTo(time.Now())
feed.SetMissingGUIDs()
return feed, nil return feed, nil
} }
@@ -132,11 +134,14 @@ func (feed *Feed) cleanup() {
feed.Items[i].Title = strings.TrimSpace(htmlutil.ExtractText(item.Title)) feed.Items[i].Title = strings.TrimSpace(htmlutil.ExtractText(item.Title))
feed.Items[i].Content = strings.TrimSpace(item.Content) feed.Items[i].Content = strings.TrimSpace(item.Content)
if item.ImageURL != "" && strings.Contains(item.Content, item.ImageURL) { if len(feed.Items[i].MediaLinks) > 0 {
feed.Items[i].ImageURL = "" mediaLinks := make([]MediaLink, 0)
for _, link := range item.MediaLinks {
if !strings.Contains(item.Content, link.URL) {
mediaLinks = append(mediaLinks, link)
} }
if item.AudioURL != "" && strings.Contains(item.Content, item.AudioURL) { }
feed.Items[i].AudioURL = "" feed.Items[i].MediaLinks = mediaLinks
} }
} }
} }
@@ -168,3 +173,12 @@ func (feed *Feed) TranslateURLs(base string) error {
} }
return nil return nil
} }
func (feed *Feed) SetMissingGUIDs() {
for i, item := range feed.Items {
if item.GUID == "" {
id := strings.Join([]string{item.Title, item.Date.Format(time.RFC3339), item.URL}, ";;")
feed.Items[i].GUID = fmt.Sprintf("%x", sha256.Sum256([]byte(id)))
}
}
}

View File

@@ -150,3 +150,32 @@ func TestParseCleanIllegalCharsInNonUTF8(t *testing.T) {
t.Fatalf("invalid feed, got: %v", feed) t.Fatalf("invalid feed, got: %v", feed)
} }
} }
func TestParseMissingGUID(t *testing.T) {
data := `
<?xml version="1.0" encoding="windows-1251"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<item>
<title>foo</title>
</item>
<item>
<title>bar</title>
</item>
</channel>
</rss>
`
feed, err := ParseAndFix(strings.NewReader(data), "", "")
if err != nil {
t.Fatal(err)
}
if len(feed.Items) != 2 {
t.Fatalf("expected 2 items, got %d", len(feed.Items))
}
if feed.Items[0].GUID == "" || feed.Items[1].GUID == "" {
t.Fatalf("item GUIDs are missing, got %#v", feed.Items)
}
if feed.Items[0].GUID == feed.Items[1].GUID {
t.Fatalf("item GUIDs are not unique, got %#v", feed.Items)
}
}

View File

@@ -1,5 +1,9 @@
package parser package parser
import (
"strings"
)
type media struct { type media struct {
MediaGroups []mediaGroup `xml:"http://search.yahoo.com/mrss/ group"` MediaGroups []mediaGroup `xml:"http://search.yahoo.com/mrss/ group"`
MediaContents []mediaContent `xml:"http://search.yahoo.com/mrss/ content"` MediaContents []mediaContent `xml:"http://search.yahoo.com/mrss/ content"`
@@ -8,12 +12,17 @@ type media struct {
} }
type mediaGroup struct { type mediaGroup struct {
MediaContent []mediaContent `xml:"http://search.yahoo.com/mrss/ content"`
MediaThumbnails []mediaThumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"` MediaThumbnails []mediaThumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"`
MediaDescriptions []mediaDescription `xml:"http://search.yahoo.com/mrss/ description"` MediaDescriptions []mediaDescription `xml:"http://search.yahoo.com/mrss/ description"`
} }
type mediaContent struct { type mediaContent struct {
MediaThumbnails []mediaThumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"` MediaThumbnails []mediaThumbnail `xml:"http://search.yahoo.com/mrss/ thumbnail"`
MediaType string `xml:"type,attr"`
MediaMedium string `xml:"medium,attr"`
MediaURL string `xml:"url,attr"`
MediaDescription mediaDescription `xml:"http://search.yahoo.com/mrss/ description"`
} }
type mediaThumbnail struct { type mediaThumbnail struct {
@@ -22,7 +31,7 @@ type mediaThumbnail struct {
type mediaDescription struct { type mediaDescription struct {
Type string `xml:"type,attr"` Type string `xml:"type,attr"`
Description string `xml:",chardata"` Text string `xml:",chardata"`
} }
func (m *media) firstMediaThumbnail() string { func (m *media) firstMediaThumbnail() string {
@@ -44,12 +53,59 @@ func (m *media) firstMediaThumbnail() string {
func (m *media) firstMediaDescription() string { func (m *media) firstMediaDescription() string {
for _, d := range m.MediaDescriptions { for _, d := range m.MediaDescriptions {
return plain2html(d.Description) return plain2html(d.Text)
} }
for _, g := range m.MediaGroups { for _, g := range m.MediaGroups {
for _, d := range g.MediaDescriptions { for _, d := range g.MediaDescriptions {
return plain2html(d.Description) return plain2html(d.Text)
} }
} }
return "" return ""
} }
func (m *media) mediaLinks() []MediaLink {
links := make([]MediaLink, 0)
for _, thumbnail := range m.MediaThumbnails {
links = append(links, MediaLink{URL: thumbnail.URL, Type: "image"})
}
for _, group := range m.MediaGroups {
for _, thumbnail := range group.MediaThumbnails {
links = append(links, MediaLink{
URL: thumbnail.URL,
Type: "image",
})
}
}
for _, content := range m.MediaContents {
if content.MediaURL != "" {
url := content.MediaURL
description := content.MediaDescription.Text
if strings.HasPrefix(content.MediaType, "image/") {
links = append(links, MediaLink{URL: url, Type: "image", Description: description})
} else if strings.HasPrefix(content.MediaType, "audio/") {
links = append(links, MediaLink{URL: url, Type: "audio", Description: description})
} else if strings.HasPrefix(content.MediaType, "video/") {
links = append(links, MediaLink{URL: url, Type: "video", Description: description})
} else if content.MediaMedium == "image" || content.MediaMedium == "audio" || content.MediaMedium == "video" {
links = append(links, MediaLink{URL: url, Type: content.MediaMedium, Description: description})
} else {
if len(content.MediaThumbnails) > 0 {
links = append(links, MediaLink{
URL: content.MediaThumbnails[0].URL,
Type: "image",
})
}
}
}
for _, thumbnail := range content.MediaThumbnails {
links = append(links, MediaLink{
URL: thumbnail.URL,
Type: "image",
})
}
}
if len(links) == 0 {
return nil
}
return links
}

View File

@@ -15,6 +15,11 @@ type Item struct {
Title string Title string
Content string Content string
ImageURL string MediaLinks []MediaLink
AudioURL string }
type MediaLink struct {
URL string
Type string
Description string
} }

View File

@@ -74,14 +74,14 @@ func ParseRSS(r io.Reader) (*Feed, error) {
SiteURL: srcfeed.Link, SiteURL: srcfeed.Link,
} }
for _, srcitem := range srcfeed.Items { for _, srcitem := range srcfeed.Items {
podcastURL := "" mediaLinks := srcitem.mediaLinks()
for _, e := range srcitem.Enclosures { for _, e := range srcitem.Enclosures {
if strings.HasPrefix(e.Type, "audio/") { if strings.HasPrefix(e.Type, "audio/") {
podcastURL = e.URL podcastURL := e.URL
if srcitem.OrigEnclosureLink != "" && strings.Contains(podcastURL, path.Base(srcitem.OrigEnclosureLink)) { if srcitem.OrigEnclosureLink != "" && strings.Contains(podcastURL, path.Base(srcitem.OrigEnclosureLink)) {
podcastURL = srcitem.OrigEnclosureLink podcastURL = srcitem.OrigEnclosureLink
} }
mediaLinks = append(mediaLinks, MediaLink{URL: podcastURL, Type: "audio"})
break break
} }
} }
@@ -96,9 +96,8 @@ func ParseRSS(r io.Reader) (*Feed, error) {
Date: dateParse(firstNonEmpty(srcitem.DublinCoreDate, srcitem.PubDate)), Date: dateParse(firstNonEmpty(srcitem.DublinCoreDate, srcitem.PubDate)),
URL: firstNonEmpty(srcitem.OrigLink, srcitem.Link, permalink), URL: firstNonEmpty(srcitem.OrigLink, srcitem.Link, permalink),
Title: srcitem.Title, Title: srcitem.Title,
Content: firstNonEmpty(srcitem.ContentEncoded, srcitem.Description), Content: firstNonEmpty(srcitem.ContentEncoded, srcitem.Description, srcitem.firstMediaDescription()),
AudioURL: podcastURL, MediaLinks: mediaLinks,
ImageURL: srcitem.firstMediaThumbnail(),
}) })
} }
return dstfeed, nil return dstfeed, nil

View File

@@ -75,9 +75,15 @@ func TestRSSMediaContentThumbnail(t *testing.T) {
</channel> </channel>
</rss> </rss>
`)) `))
have := feed.Items[0].ImageURL if len(feed.Items[0].MediaLinks) != 1 {
want := "https://i.vimeocdn.com/video/1092705247_960.jpg" t.Fatalf("Expected 1 media link, got %#v", feed.Items[0].MediaLinks)
if have != want { }
have := feed.Items[0].MediaLinks[0]
want := MediaLink{
URL: "https://i.vimeocdn.com/video/1092705247_960.jpg",
Type: "image",
}
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want) t.Logf("want: %#v", want)
t.Logf("have: %#v", have) t.Logf("have: %#v", have)
t.FailNow() t.FailNow()
@@ -127,9 +133,15 @@ func TestRSSPodcast(t *testing.T) {
</channel> </channel>
</rss> </rss>
`)) `))
have := feed.Items[0].AudioURL if len(feed.Items[0].MediaLinks) != 1 {
want := "http://example.com/audio.ext" t.Fatal("Invalid media links")
if want != have { }
have := feed.Items[0].MediaLinks[0]
want := MediaLink{
URL: "http://example.com/audio.ext",
Type: "audio",
}
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want) t.Logf("want: %#v", want)
t.Logf("have: %#v", have) t.Logf("have: %#v", have)
t.FailNow() t.FailNow()
@@ -147,9 +159,15 @@ func TestRSSOpusPodcast(t *testing.T) {
</channel> </channel>
</rss> </rss>
`)) `))
have := feed.Items[0].AudioURL if len(feed.Items[0].MediaLinks) != 1 {
want := "http://example.com/audio.ext" t.Fatal("Invalid media links")
if want != have { }
have := feed.Items[0].MediaLinks[0]
want := MediaLink{
URL: "http://example.com/audio.ext",
Type: "audio",
}
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want) t.Logf("want: %#v", want)
t.Logf("have: %#v", have) t.Logf("have: %#v", have)
t.FailNow() t.FailNow()
@@ -176,8 +194,9 @@ func TestRSSPodcastDuplicated(t *testing.T) {
if want != have { if want != have {
t.Fatalf("content doesn't match\nwant: %#v\nhave: %#v\n", want, have) t.Fatalf("content doesn't match\nwant: %#v\nhave: %#v\n", want, have)
} }
if feed.Items[0].AudioURL != "" {
t.Fatal("item.audio_url must be unset if present in the content") if len(feed.Items[0].MediaLinks) != 0 {
t.Fatal("item media must be excluded if present in the content")
} }
} }
@@ -223,8 +242,47 @@ func TestRSSIsPermalink(t *testing.T) {
}, },
} }
for i := 0; i < len(want); i++ { for i := 0; i < len(want); i++ {
if want[i] != have[i] { if !reflect.DeepEqual(want, have) {
t.Errorf("Failed to handle isPermalink\nwant: %#v\nhave: %#v\n", want[i], have[i]) t.Errorf("Failed to handle isPermalink\nwant: %#v\nhave: %#v\n", want[i], have[i])
} }
} }
} }
func TestRSSMultipleMedia(t *testing.T) {
feed, _ := Parse(strings.NewReader(`
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
<channel>
<item>
<guid isPermaLink="true">http://example.com/posts/1</guid>
<media:content url="https://example.com/path/to/image1.png" type="image/png" fileSize="1000" medium="image">
<media:description type="plain">description 1</media:description>
</media:content>
<media:content url="https://example.com/path/to/image2.png" type="image/png" fileSize="2000" medium="image">
<media:description type="plain">description 2</media:description>
</media:content>
<media:content url="https://example.com/path/to/video1.mp4" type="video/mp4" fileSize="2000" medium="image">
<media:description type="plain">video description</media:description>
</media:content>
</item>
</channel>
</rss>
`))
have := feed.Items
want := []Item{
{
GUID: "http://example.com/posts/1",
URL: "http://example.com/posts/1",
MediaLinks: []MediaLink{
{URL:"https://example.com/path/to/image1.png", Type:"image", Description:"description 1"},
{URL:"https://example.com/path/to/image2.png", Type:"image", Description:"description 2"},
{URL:"https://example.com/path/to/video1.mp4", Type:"video", Description:"video description"},
},
},
}
if !reflect.DeepEqual(want, have) {
t.Logf("want: %#v", want)
t.Logf("have: %#v", have)
t.Fatal("invalid rss")
}
}

View File

@@ -11,6 +11,7 @@ import (
func Start(s *server.Server) { func Start(s *server.Server) {
systrayOnReady := func() { systrayOnReady := func() {
systray.SetIcon(Icon) systray.SetIcon(Icon)
systray.SetTooltip("yarr")
menuOpen := systray.AddMenuItem("Open", "") menuOpen := systray.AddMenuItem("Open", "")
systray.AddSeparator() systray.AddSeparator()

View File

@@ -6,6 +6,7 @@ import (
"github.com/nkanaev/yarr/src/assets" "github.com/nkanaev/yarr/src/assets"
"github.com/nkanaev/yarr/src/server/router" "github.com/nkanaev/yarr/src/server/router"
"github.com/nkanaev/yarr/src/storage"
) )
type Middleware struct { type Middleware struct {
@@ -13,6 +14,7 @@ type Middleware struct {
Password string Password string
BasePath string BasePath string
Public []string Public []string
DB *storage.Storage
} }
func unsafeMethod(method string) bool { func unsafeMethod(method string) bool {
@@ -46,12 +48,15 @@ func (m *Middleware) Handler(c *router.Context) {
c.Redirect(rootUrl) c.Redirect(rootUrl)
return return
} else { } else {
c.HTML(http.StatusOK, assets.Template("login.html"), map[string]string{ c.HTML(http.StatusOK, assets.Template("login.html"), map[string]interface{}{
"username": username, "username": username,
"error": "Invalid username/password", "error": "Invalid username/password",
"settings": m.DB.GetSettings(),
}) })
return return
} }
} }
c.HTML(http.StatusOK, assets.Template("login.html"), nil) c.HTML(http.StatusOK, assets.Template("login.html"), map[string]interface{}{
"settings": m.DB.GetSettings(),
})
} }

View File

@@ -35,6 +35,7 @@ func (s *Server) handler() http.Handler {
Username: s.Username, Username: s.Username,
Password: s.Password, Password: s.Password,
Public: []string{"/static", "/fever"}, Public: []string{"/static", "/fever"},
DB: s.db,
} }
r.Use(a.Handler) r.Use(a.Handler)
} }
@@ -86,7 +87,7 @@ func (s *Server) handleManifest(c *router.Context) {
"short_name": "yarr", "short_name": "yarr",
"description": "yet another rss reader", "description": "yet another rss reader",
"display": "standalone", "display": "standalone",
"start_url": s.BasePath, "start_url": "/" + strings.TrimPrefix(s.BasePath, "/"),
"icons": []map[string]interface{}{ "icons": []map[string]interface{}{
{ {
"src": s.BasePath + "/static/graphicarts/favicon.png", "src": s.BasePath + "/static/graphicarts/favicon.png",
@@ -293,6 +294,11 @@ func (s *Server) handleFeed(c *router.Context) {
s.db.UpdateFeedFolder(id, &folderId) s.db.UpdateFeedFolder(id, &folderId)
} }
} }
if link, ok := body["feed_link"]; ok {
if reflect.TypeOf(link).Kind() == reflect.String {
s.db.UpdateFeedLink(id, link.(string))
}
}
c.Out.WriteHeader(http.StatusOK) c.Out.WriteHeader(http.StatusOK)
} else if c.Req.Method == "DELETE" { } else if c.Req.Method == "DELETE" {
s.db.DeleteFeed(id) s.db.DeleteFeed(id)
@@ -323,6 +329,9 @@ func (s *Server) handleItem(c *router.Context) {
} }
item.Content = sanitizer.Sanitize(item.Link, item.Content) item.Content = sanitizer.Sanitize(item.Link, item.Content)
for i, link := range item.MediaLinks {
item.MediaLinks[i].Description = sanitizer.Sanitize(item.Link, link.Description)
}
c.JSON(http.StatusOK, item) c.JSON(http.StatusOK, item)
} else if c.Req.Method == "PUT" { } else if c.Req.Method == "PUT" {
@@ -365,12 +374,19 @@ func (s *Server) handleItemList(c *router.Context) {
} }
newestFirst := query.Get("oldest_first") != "true" newestFirst := query.Get("oldest_first") != "true"
items := s.db.ListItems(filter, perPage+1, newestFirst, false) items := s.db.ListItems(filter, perPage+1, newestFirst, true)
hasMore := false hasMore := false
if len(items) == perPage+1 { if len(items) == perPage+1 {
hasMore = true hasMore = true
items = items[:perPage] items = items[:perPage]
} }
for i, item := range items {
if item.Title == "" {
text := htmlutil.ExtractText(item.Content)
items[i].Title = htmlutil.TruncateText(text, 140)
}
}
c.JSON(http.StatusOK, map[string]interface{}{ c.JSON(http.StatusOK, map[string]interface{}{
"list": items, "list": items,
"has_more": hasMore, "has_more": hasMore,

View File

@@ -71,6 +71,11 @@ func (s *Storage) UpdateFeedFolder(feedId int64, newFolderId *int64) bool {
return err == nil return err == nil
} }
func (s *Storage) UpdateFeedLink(feedId int64, newLink string) bool {
_, err := s.db.Exec(`update feeds set feed_link = ? where id = ?`, newLink, feedId)
return err == nil
}
func (s *Storage) UpdateFeedIcon(feedId int64, icon *[]byte) bool { func (s *Storage) UpdateFeedIcon(feedId int64, icon *[]byte) bool {
_, err := s.db.Exec(`update feeds set icon = ? where id = ?`, icon, feedId) _, err := s.db.Exec(`update feeds set icon = ? where id = ?`, icon, feedId)
return err == nil return err == nil

View File

@@ -1,6 +1,7 @@
package storage package storage
import ( import (
"database/sql/driver"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
@@ -44,6 +45,25 @@ func (s *ItemStatus) UnmarshalJSON(b []byte) error {
return nil return nil
} }
type MediaLink struct {
URL string `json:"url"`
Type string `json:"type"`
Description string `json:"description,omitempty"`
}
type MediaLinks []MediaLink
func (m *MediaLinks) Scan(src any) error {
if data, ok := src.([]byte); ok {
return json.Unmarshal(data, m)
}
return nil
}
func (m MediaLinks) Value() (driver.Value, error) {
return json.Marshal(m)
}
type Item struct { type Item struct {
Id int64 `json:"id"` Id int64 `json:"id"`
GUID string `json:"guid"` GUID string `json:"guid"`
@@ -53,8 +73,7 @@ type Item struct {
Content string `json:"content,omitempty"` Content string `json:"content,omitempty"`
Date time.Time `json:"date"` Date time.Time `json:"date"`
Status ItemStatus `json:"status"` Status ItemStatus `json:"status"`
ImageURL *string `json:"image"` MediaLinks MediaLinks `json:"media_links"`
AudioURL *string `json:"podcast_url"`
} }
type ItemFilter struct { type ItemFilter struct {
@@ -94,7 +113,6 @@ func (list ItemList) Swap(i, j int) {
list[i], list[j] = list[j], list[i] 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 {
@@ -111,13 +129,17 @@ func (s *Storage) CreateItems(items []Item) bool {
_, err = tx.Exec(` _, err = tx.Exec(`
insert into items ( insert into items (
guid, feed_id, title, link, date, guid, feed_id, title, link, date,
content, image, podcast_url, content, media_links,
date_arrived, status date_arrived, status
) )
values (?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%f', ?), ?, ?, ?, ?, ?) values (
?, ?, ?, ?, strftime('%Y-%m-%d %H:%M:%f', ?),
?, ?,
?, ?
)
on conflict (feed_id, guid) do nothing`, on conflict (feed_id, guid) do nothing`,
item.GUID, item.FeedId, item.Title, item.Link, item.Date, item.GUID, item.FeedId, item.Title, item.Link, item.Date,
item.Content, item.ImageURL, item.AudioURL, item.Content, item.MediaLinks,
now, UNREAD, now, UNREAD,
) )
if err != nil { if err != nil {
@@ -232,7 +254,7 @@ func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, with
order = "i.id desc" order = "i.id desc"
} }
selectCols := "i.id, i.guid, i.feed_id, i.title, i.link, i.date, i.status, i.image, i.podcast_url" selectCols := "i.id, i.guid, i.feed_id, i.title, i.link, i.date, i.status, i.media_links"
if withContent { if withContent {
selectCols += ", i.content" selectCols += ", i.content"
} else { } else {
@@ -255,7 +277,7 @@ func (s *Storage) ListItems(filter ItemFilter, limit int, newestFirst bool, with
err = rows.Scan( err = rows.Scan(
&x.Id, &x.GUID, &x.FeedId, &x.Id, &x.GUID, &x.FeedId,
&x.Title, &x.Link, &x.Date, &x.Title, &x.Link, &x.Date,
&x.Status, &x.ImageURL, &x.AudioURL, &x.Content, &x.Status, &x.MediaLinks, &x.Content,
) )
if err != nil { if err != nil {
log.Print(err) log.Print(err)
@@ -271,12 +293,12 @@ func (s *Storage) GetItem(id int64) *Item {
err := s.db.QueryRow(` err := s.db.QueryRow(`
select select
i.id, i.guid, i.feed_id, i.title, i.link, i.content, i.id, i.guid, i.feed_id, i.title, i.link, i.content,
i.date, i.status, i.image, i.podcast_url i.date, i.status, i.media_links
from items i from items i
where i.id = ? where i.id = ?
`, id).Scan( `, id).Scan(
&i.Id, &i.GUID, &i.FeedId, &i.Title, &i.Link, &i.Content, &i.Id, &i.GUID, &i.FeedId, &i.Title, &i.Link, &i.Content,
&i.Date, &i.Status, &i.ImageURL, &i.AudioURL, &i.Date, &i.Status, &i.MediaLinks,
) )
if err != nil { if err != nil {
log.Print(err) log.Print(err)

View File

@@ -77,12 +77,12 @@ func getItem(db *Storage, guid string) *Item {
err := db.db.QueryRow(` err := db.db.QueryRow(`
select select
i.id, i.guid, i.feed_id, i.title, i.link, i.content, i.id, i.guid, i.feed_id, i.title, i.link, i.content,
i.date, i.status, i.image, i.podcast_url i.date, i.status, i.media_links
from items i from items i
where i.guid = ? where i.guid = ?
`, guid).Scan( `, guid).Scan(
&i.Id, &i.GUID, &i.FeedId, &i.Title, &i.Link, &i.Content, &i.Id, &i.GUID, &i.FeedId, &i.Title, &i.Link, &i.Content,
&i.Date, &i.Status, &i.ImageURL, &i.AudioURL, &i.Date, &i.Status, &i.MediaLinks,
) )
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@@ -16,13 +16,17 @@ var migrations = []func(*sql.Tx) error{
m06_fill_missing_dates, m06_fill_missing_dates,
m07_add_feed_size, m07_add_feed_size,
m08_normalize_datetime, m08_normalize_datetime,
m09_change_item_index,
m10_add_item_medialinks,
} }
var maxVersion = int64(len(migrations)) var maxVersion = int64(len(migrations))
func migrate(db *sql.DB) error { func migrate(db *sql.DB) error {
var version int64 var version int64
db.QueryRow("pragma user_version").Scan(&version) if err := db.QueryRow("pragma user_version").Scan(&version); err != nil {
return err
}
if version >= maxVersion { if version >= maxVersion {
return nil return nil
@@ -294,3 +298,37 @@ func m08_normalize_datetime(tx *sql.Tx) error {
_, err = tx.Exec(`update items set date = strftime('%Y-%m-%d %H:%M:%f', date);`) _, err = tx.Exec(`update items set date = strftime('%Y-%m-%d %H:%M:%f', date);`)
return err return err
} }
func m09_change_item_index(tx *sql.Tx) error {
sql := `
drop index if exists idx_item_status;
create index if not exists idx_item__date_id_status on items(date,id,status);
`
_, err := tx.Exec(sql)
return err
}
func m10_add_item_medialinks(tx *sql.Tx) error {
sql := `
alter table items add column media_links blob;
update items set media_links =
iif(
coalesce(image, '') != '' and coalesce(podcast_url, '') != '',
json_array(json_object('type', 'image', 'url', image), json_object('type', 'audio', 'url', podcast_url)),
iif(
coalesce(image, '') != '',
json_array(json_object('type', 'image', 'url', image)),
iif(
coalesce(podcast_url, '') != '',
json_array(json_object('type', 'audio', 'url', podcast_url)),
null
)
)
);
alter table items drop column image;
alter table items drop column podcast_url;
`
_, err := tx.Exec(sql)
return err
}

View File

@@ -2,6 +2,9 @@ package storage
import ( import (
"database/sql" "database/sql"
"log"
"strings"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
@@ -10,14 +13,17 @@ type Storage struct {
} }
func New(path string) (*Storage, error) { func New(path string) (*Storage, error) {
if pos := strings.IndexRune(path, '?'); pos == -1 {
params := "_journal=WAL&_sync=NORMAL&_busy_timeout=5000&cache=shared"
log.Printf("opening db with params: %s", params)
path = path + "?" + params
}
db, err := sql.Open("sqlite3", path) db, err := sql.Open("sqlite3", path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: https://foxcpp.dev/articles/the-right-way-to-use-go-sqlite3
db.SetMaxOpenConns(1)
if err = migrate(db); err != nil { if err = migrate(db); err != nil {
return nil, err return nil, err
} }

View File

@@ -1,3 +1,4 @@
//go:build darwin || windows
// +build darwin windows // +build darwin windows
/* /*

View File

@@ -1,3 +1,4 @@
//go:build never
// +build never // +build never
package systray package systray

View File

@@ -1,3 +1,4 @@
//go:build darwin
// +build darwin // +build darwin
package systray package systray

View File

@@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package systray package systray

View File

@@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package systray package systray

View File

@@ -32,6 +32,10 @@ func (c *Client) getConditional(url, lastModified, etag string) (*http.Response,
var client *Client var client *Client
func SetVersion(num string) {
client.userAgent = "Yarr/" + num
}
func init() { func init() {
transport := &http.Transport{ transport := &http.Transport{
Proxy: http.ProxyFromEnvironment, Proxy: http.ProxyFromEnvironment,

View File

@@ -143,13 +143,9 @@ func ConvertItems(items []parser.Item, feed storage.Feed) []storage.Item {
result := make([]storage.Item, len(items)) result := make([]storage.Item, len(items))
for i, item := range items { for i, item := range items {
item := item item := item
var audioURL *string = nil mediaLinks := make(storage.MediaLinks, 0)
if item.AudioURL != "" { for _, link := range item.MediaLinks {
audioURL = &item.AudioURL mediaLinks = append(mediaLinks, storage.MediaLink(link))
}
var imageURL *string = nil
if item.ImageURL != "" {
imageURL = &item.ImageURL
} }
result[i] = storage.Item{ result[i] = storage.Item{
GUID: item.GUID, GUID: item.GUID,
@@ -159,8 +155,7 @@ func ConvertItems(items []parser.Item, feed storage.Feed) []storage.Item {
Content: item.Content, Content: item.Content,
Date: item.Date, Date: item.Date,
Status: storage.UNREAD, Status: storage.UNREAD,
ImageURL: imageURL, MediaLinks: mediaLinks,
AudioURL: audioURL,
} }
} }
return result return result

4
vendor/golang.org/x/net/LICENSE generated vendored
View File

@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

View File

@@ -78,16 +78,11 @@ example, to process each anchor node in depth-first order:
if err != nil { if err != nil {
// ... // ...
} }
var f func(*html.Node) for n := range doc.Descendants() {
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" { if n.Type == html.ElementNode && n.Data == "a" {
// Do something with n... // Do something with n...
} }
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
} }
}
f(doc)
The relevant specifications include: The relevant specifications include:
https://html.spec.whatwg.org/multipage/syntax.html and https://html.spec.whatwg.org/multipage/syntax.html and
@@ -104,7 +99,7 @@ tokenization, and tokenization and tree construction stages of the WHATWG HTML
parsing specification respectively. While the tokenizer parses and normalizes parsing specification respectively. While the tokenizer parses and normalizes
individual HTML tokens, only the parser constructs the DOM tree from the individual HTML tokens, only the parser constructs the DOM tree from the
tokenized HTML, as described in the tree construction stage of the tokenized HTML, as described in the tree construction stage of the
specification, dynamically modifying or extending the docuemnt's DOM tree. specification, dynamically modifying or extending the document's DOM tree.
If your use case requires semantically well-formed HTML documents, as defined by If your use case requires semantically well-formed HTML documents, as defined by
the WHATWG specification, the parser should be used rather than the tokenizer. the WHATWG specification, the parser should be used rather than the tokenizer.

View File

@@ -87,7 +87,7 @@ func parseDoctype(s string) (n *Node, quirks bool) {
} }
} }
if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" &&
strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { strings.EqualFold(lastAttr.Val, "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
quirks = true quirks = true
} }
} }

View File

@@ -40,8 +40,7 @@ func htmlIntegrationPoint(n *Node) bool {
if n.Data == "annotation-xml" { if n.Data == "annotation-xml" {
for _, a := range n.Attr { for _, a := range n.Attr {
if a.Key == "encoding" { if a.Key == "encoding" {
val := strings.ToLower(a.Val) if strings.EqualFold(a.Val, "text/html") || strings.EqualFold(a.Val, "application/xhtml+xml") {
if val == "text/html" || val == "application/xhtml+xml" {
return true return true
} }
} }

56
vendor/golang.org/x/net/html/iter.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.23
package html
import "iter"
// Ancestors returns an iterator over the ancestors of n, starting with n.Parent.
//
// Mutating a Node or its parents while iterating may have unexpected results.
func (n *Node) Ancestors() iter.Seq[*Node] {
_ = n.Parent // eager nil check
return func(yield func(*Node) bool) {
for p := n.Parent; p != nil && yield(p); p = p.Parent {
}
}
}
// ChildNodes returns an iterator over the immediate children of n,
// starting with n.FirstChild.
//
// Mutating a Node or its children while iterating may have unexpected results.
func (n *Node) ChildNodes() iter.Seq[*Node] {
_ = n.FirstChild // eager nil check
return func(yield func(*Node) bool) {
for c := n.FirstChild; c != nil && yield(c); c = c.NextSibling {
}
}
}
// Descendants returns an iterator over all nodes recursively beneath
// n, excluding n itself. Nodes are visited in depth-first preorder.
//
// Mutating a Node or its descendants while iterating may have unexpected results.
func (n *Node) Descendants() iter.Seq[*Node] {
_ = n.FirstChild // eager nil check
return func(yield func(*Node) bool) {
n.descendants(yield)
}
}
func (n *Node) descendants(yield func(*Node) bool) bool {
for c := range n.ChildNodes() {
if !yield(c) || !c.descendants(yield) {
return false
}
}
return true
}

View File

@@ -38,6 +38,10 @@ var scopeMarker = Node{Type: scopeMarkerNode}
// that it looks like "a<b" rather than "a&lt;b". For element nodes, DataAtom // that it looks like "a<b" rather than "a&lt;b". For element nodes, DataAtom
// is the atom for Data, or zero if Data is not a known tag name. // is the atom for Data, or zero if Data is not a known tag name.
// //
// Node trees may be navigated using the link fields (Parent,
// FirstChild, and so on) or a range loop over iterators such as
// [Node.Descendants].
//
// An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace. // An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace.
// Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and // Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and
// "svg" is short for "http://www.w3.org/2000/svg". // "svg" is short for "http://www.w3.org/2000/svg".

View File

@@ -840,6 +840,10 @@ func afterHeadIM(p *parser) bool {
p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) p.parseImpliedToken(StartTagToken, a.Body, a.Body.String())
p.framesetOK = true p.framesetOK = true
if p.tok.Type == ErrorToken {
// Stop parsing.
return true
}
return false return false
} }
@@ -1031,7 +1035,7 @@ func inBodyIM(p *parser) bool {
if p.tok.DataAtom == a.Input { if p.tok.DataAtom == a.Input {
for _, t := range p.tok.Attr { for _, t := range p.tok.Attr {
if t.Key == "type" { if t.Key == "type" {
if strings.ToLower(t.Val) == "hidden" { if strings.EqualFold(t.Val, "hidden") {
// Skip setting framesetOK = false // Skip setting framesetOK = false
return true return true
} }
@@ -1459,7 +1463,7 @@ func inTableIM(p *parser) bool {
return inHeadIM(p) return inHeadIM(p)
case a.Input: case a.Input:
for _, t := range p.tok.Attr { for _, t := range p.tok.Attr {
if t.Key == "type" && strings.ToLower(t.Val) == "hidden" { if t.Key == "type" && strings.EqualFold(t.Val, "hidden") {
p.addElement() p.addElement()
p.oe.pop() p.oe.pop()
return true return true

View File

@@ -910,9 +910,6 @@ func (z *Tokenizer) readTagAttrKey() {
return return
} }
switch c { switch c {
case ' ', '\n', '\r', '\t', '\f', '/':
z.pendingAttr[0].end = z.raw.end - 1
return
case '=': case '=':
if z.pendingAttr[0].start+1 == z.raw.end { if z.pendingAttr[0].start+1 == z.raw.end {
// WHATWG 13.2.5.32, if we see an equals sign before the attribute name // WHATWG 13.2.5.32, if we see an equals sign before the attribute name
@@ -920,7 +917,9 @@ func (z *Tokenizer) readTagAttrKey() {
continue continue
} }
fallthrough fallthrough
case '>': case ' ', '\n', '\r', '\t', '\f', '/', '>':
// WHATWG 13.2.5.33 Attribute name state
// We need to reconsume the char in the after attribute name state to support the / character
z.raw.end-- z.raw.end--
z.pendingAttr[0].end = z.raw.end z.pendingAttr[0].end = z.raw.end
return return
@@ -939,6 +938,11 @@ func (z *Tokenizer) readTagAttrVal() {
if z.err != nil { if z.err != nil {
return return
} }
if c == '/' {
// WHATWG 13.2.5.34 After attribute name state
// U+002F SOLIDUS (/) - Switch to the self-closing start tag state.
return
}
if c != '=' { if c != '=' {
z.raw.end-- z.raw.end--
return return

4
vendor/golang.org/x/sys/LICENSE generated vendored
View File

@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

View File

@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && go1.9 //go:build windows
// +build windows,go1.9
package windows package windows

View File

@@ -65,7 +65,7 @@ func LoadDLL(name string) (dll *DLL, err error) {
return d, nil return d, nil
} }
// MustLoadDLL is like LoadDLL but panics if load operation failes. // MustLoadDLL is like LoadDLL but panics if load operation fails.
func MustLoadDLL(name string) *DLL { func MustLoadDLL(name string) *DLL {
d, e := LoadDLL(name) d, e := LoadDLL(name)
if e != nil { if e != nil {

View File

@@ -1,9 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.12
// +build !go1.12
// This file is here to allow bodyless functions with go:linkname for Go 1.11
// and earlier (see https://golang.org/issue/23311).

View File

@@ -37,14 +37,17 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) {
return nil, err return nil, err
} }
defer DestroyEnvironmentBlock(block) defer DestroyEnvironmentBlock(block)
blockp := unsafe.Pointer(block) size := unsafe.Sizeof(*block)
for { for *block != 0 {
entry := UTF16PtrToString((*uint16)(blockp)) // find NUL terminator
if len(entry) == 0 { end := unsafe.Pointer(block)
break for *(*uint16)(end) != 0 {
end = unsafe.Add(end, size)
} }
env = append(env, entry)
blockp = unsafe.Add(blockp, 2*(len(entry)+1)) entry := unsafe.Slice(block, (uintptr(end)-uintptr(unsafe.Pointer(block)))/size)
env = append(env, UTF16ToString(entry))
block = (*uint16)(unsafe.Add(end, size))
} }
return env, nil return env, nil
} }

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build generate //go:build generate
// +build generate
package windows package windows

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && race //go:build windows && race
// +build windows,race
package windows package windows

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && !race //go:build windows && !race
// +build windows,!race
package windows package windows

View File

@@ -68,6 +68,7 @@ type UserInfo10 struct {
//sys NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) = netapi32.NetUserGetInfo //sys NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) = netapi32.NetUserGetInfo
//sys NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (neterr error) = netapi32.NetGetJoinInformation //sys NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (neterr error) = netapi32.NetGetJoinInformation
//sys NetApiBufferFree(buf *byte) (neterr error) = netapi32.NetApiBufferFree //sys NetApiBufferFree(buf *byte) (neterr error) = netapi32.NetApiBufferFree
//sys NetUserEnum(serverName *uint16, level uint32, filter uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32, resumeHandle *uint32) (neterr error) = netapi32.NetUserEnum
const ( const (
// do not reorder // do not reorder
@@ -893,7 +894,7 @@ type ACL struct {
aclRevision byte aclRevision byte
sbz1 byte sbz1 byte
aclSize uint16 aclSize uint16
aceCount uint16 AceCount uint16
sbz2 uint16 sbz2 uint16
} }
@@ -1086,6 +1087,27 @@ type EXPLICIT_ACCESS struct {
Trustee TRUSTEE Trustee TRUSTEE
} }
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
type ACE_HEADER struct {
AceType uint8
AceFlags uint8
AceSize uint16
}
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-access_allowed_ace
type ACCESS_ALLOWED_ACE struct {
Header ACE_HEADER
Mask ACCESS_MASK
SidStart uint32
}
const (
// Constants for AceType
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-ace_header
ACCESS_ALLOWED_ACE_TYPE = 0
ACCESS_DENIED_ACE_TYPE = 1
)
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions. // This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
type TrusteeValue uintptr type TrusteeValue uintptr
@@ -1157,6 +1179,7 @@ type OBJECTS_AND_NAME struct {
//sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD //sys makeSelfRelativeSD(absoluteSD *SECURITY_DESCRIPTOR, selfRelativeSD *SECURITY_DESCRIPTOR, selfRelativeSDSize *uint32) (err error) = advapi32.MakeSelfRelativeSD
//sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW //sys setEntriesInAcl(countExplicitEntries uint32, explicitEntries *EXPLICIT_ACCESS, oldACL *ACL, newACL **ACL) (ret error) = advapi32.SetEntriesInAclW
//sys GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (err error) = advapi32.GetAce
// Control returns the security descriptor control bits. // Control returns the security descriptor control bits.
func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) { func (sd *SECURITY_DESCRIPTOR) Control() (control SECURITY_DESCRIPTOR_CONTROL, revision uint32, err error) {

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows

View File

@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
// Package windows contains an interface to the low-level operating system // Package windows contains an interface to the low-level operating system
// primitives. OS details vary depending on the underlying system, and // primitives. OS details vary depending on the underlying system, and

View File

@@ -17,8 +17,10 @@ import (
"unsafe" "unsafe"
) )
type Handle uintptr type (
type HWND uintptr Handle uintptr
HWND uintptr
)
const ( const (
InvalidHandle = ^Handle(0) InvalidHandle = ^Handle(0)
@@ -125,8 +127,7 @@ func UTF16PtrToString(p *uint16) string {
for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ { for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p)) ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p))
} }
return UTF16ToString(unsafe.Slice(p, n))
return string(utf16.Decode(unsafe.Slice(p, n)))
} }
func Getpagesize() int { return 4096 } func Getpagesize() int { return 4096 }
@@ -155,6 +156,8 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys GetModuleFileName(module Handle, filename *uint16, size uint32) (n uint32, err error) = kernel32.GetModuleFileNameW //sys GetModuleFileName(module Handle, filename *uint16, size uint32) (n uint32, err error) = kernel32.GetModuleFileNameW
//sys GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err error) = kernel32.GetModuleHandleExW //sys GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err error) = kernel32.GetModuleHandleExW
//sys SetDefaultDllDirectories(directoryFlags uint32) (err error) //sys SetDefaultDllDirectories(directoryFlags uint32) (err error)
//sys AddDllDirectory(path *uint16) (cookie uintptr, err error) = kernel32.AddDllDirectory
//sys RemoveDllDirectory(cookie uintptr) (err error) = kernel32.RemoveDllDirectory
//sys SetDllDirectory(path string) (err error) = kernel32.SetDllDirectoryW //sys SetDllDirectory(path string) (err error) = kernel32.SetDllDirectoryW
//sys GetVersion() (ver uint32, err error) //sys GetVersion() (ver uint32, err error)
//sys FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW //sys FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW
@@ -164,6 +167,9 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW //sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
//sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW
//sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) //sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error)
//sys DisconnectNamedPipe(pipe Handle) (err error)
//sys GetNamedPipeClientProcessId(pipe Handle, clientProcessID *uint32) (err error)
//sys GetNamedPipeServerProcessId(pipe Handle, serverProcessID *uint32) (err error)
//sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) //sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error)
//sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState //sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState
@@ -192,6 +198,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW //sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW
//sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW //sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW
//sys SetEndOfFile(handle Handle) (err error) //sys SetEndOfFile(handle Handle) (err error)
//sys SetFileValidData(handle Handle, validDataLength int64) (err error)
//sys GetSystemTimeAsFileTime(time *Filetime) //sys GetSystemTimeAsFileTime(time *Filetime)
//sys GetSystemTimePreciseAsFileTime(time *Filetime) //sys GetSystemTimePreciseAsFileTime(time *Filetime)
//sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff] //sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff]
@@ -208,6 +215,10 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (handle Handle, err error) //sys OpenProcess(desiredAccess uint32, inheritHandle bool, processId uint32) (handle Handle, err error)
//sys ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *uint16, showCmd int32) (err error) [failretval<=32] = shell32.ShellExecuteW //sys ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *uint16, showCmd int32) (err error) [failretval<=32] = shell32.ShellExecuteW
//sys GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) = user32.GetWindowThreadProcessId //sys GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) = user32.GetWindowThreadProcessId
//sys LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) [failretval==0] = user32.LoadKeyboardLayoutW
//sys UnloadKeyboardLayout(hkl Handle) (err error) = user32.UnloadKeyboardLayout
//sys GetKeyboardLayout(tid uint32) (hkl Handle) = user32.GetKeyboardLayout
//sys ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) = user32.ToUnicodeEx
//sys GetShellWindow() (shellWindow HWND) = user32.GetShellWindow //sys GetShellWindow() (shellWindow HWND) = user32.GetShellWindow
//sys MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) [failretval==0] = user32.MessageBoxW //sys MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) [failretval==0] = user32.MessageBoxW
//sys ExitWindowsEx(flags uint32, reason uint32) (err error) = user32.ExitWindowsEx //sys ExitWindowsEx(flags uint32, reason uint32) (err error) = user32.ExitWindowsEx
@@ -233,6 +244,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock //sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock //sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys getTickCount64() (ms uint64) = kernel32.GetTickCount64 //sys getTickCount64() (ms uint64) = kernel32.GetTickCount64
//sys GetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error)
//sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) //sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error)
//sys GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW //sys GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW
//sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW //sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW
@@ -303,6 +315,10 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode //sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode
//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo //sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo
//sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition //sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition
//sys GetConsoleCP() (cp uint32, err error) = kernel32.GetConsoleCP
//sys GetConsoleOutputCP() (cp uint32, err error) = kernel32.GetConsoleOutputCP
//sys SetConsoleCP(cp uint32) (err error) = kernel32.SetConsoleCP
//sys SetConsoleOutputCP(cp uint32) (err error) = kernel32.SetConsoleOutputCP
//sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW //sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW
//sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW //sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW
//sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole //sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole
@@ -345,8 +361,19 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost //sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost
//sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32) //sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
//sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error) //sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
//sys ClearCommBreak(handle Handle) (err error)
//sys ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error)
//sys EscapeCommFunction(handle Handle, dwFunc uint32) (err error)
//sys GetCommState(handle Handle, lpDCB *DCB) (err error)
//sys GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error)
//sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) //sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys PurgeComm(handle Handle, dwFlags uint32) (err error)
//sys SetCommBreak(handle Handle) (err error)
//sys SetCommMask(handle Handle, dwEvtMask uint32) (err error)
//sys SetCommState(handle Handle, lpDCB *DCB) (err error)
//sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) //sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error)
//sys WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error)
//sys GetActiveProcessorCount(groupNumber uint16) (ret uint32) //sys GetActiveProcessorCount(groupNumber uint16) (ret uint32)
//sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32) //sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
//sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows //sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows
@@ -700,20 +727,12 @@ func DurationSinceBoot() time.Duration {
} }
func Ftruncate(fd Handle, length int64) (err error) { func Ftruncate(fd Handle, length int64) (err error) {
curoffset, e := Seek(fd, 0, 1) type _FILE_END_OF_FILE_INFO struct {
if e != nil { EndOfFile int64
return e
} }
defer Seek(fd, curoffset, 0) var info _FILE_END_OF_FILE_INFO
_, e = Seek(fd, length, 0) info.EndOfFile = length
if e != nil { return SetFileInformationByHandle(fd, FileEndOfFileInfo, (*byte)(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info)))
return e
}
e = SetEndOfFile(fd)
if e != nil {
return e
}
return nil
} }
func Gettimeofday(tv *Timeval) (err error) { func Gettimeofday(tv *Timeval) (err error) {
@@ -869,6 +888,11 @@ const socket_error = uintptr(^uint32(0))
//sys GetACP() (acp uint32) = kernel32.GetACP //sys GetACP() (acp uint32) = kernel32.GetACP
//sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar //sys MultiByteToWideChar(codePage uint32, dwFlags uint32, str *byte, nstr int32, wchar *uint16, nwchar int32) (nwrite int32, err error) = kernel32.MultiByteToWideChar
//sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx //sys getBestInterfaceEx(sockaddr unsafe.Pointer, pdwBestIfIndex *uint32) (errcode error) = iphlpapi.GetBestInterfaceEx
//sys GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) = iphlpapi.GetIfEntry2Ex
//sys GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) = iphlpapi.GetUnicastIpAddressEntry
//sys NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyIpInterfaceChange
//sys NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) = iphlpapi.NotifyUnicastIpAddressChange
//sys CancelMibChangeNotify2(notificationHandle Handle) (errcode error) = iphlpapi.CancelMibChangeNotify2
// For testing: clients can set this flag to force // For testing: clients can set this flag to force
// creation of IPv6 sockets to return EAFNOSUPPORT. // creation of IPv6 sockets to return EAFNOSUPPORT.
@@ -969,7 +993,8 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {
if n > 0 { if n > 0 {
sl += int32(n) + 1 sl += int32(n) + 1
} }
if sa.raw.Path[0] == '@' { if sa.raw.Path[0] == '@' || (sa.raw.Path[0] == 0 && sl > 3) {
// Check sl > 3 so we don't change unnamed socket behavior.
sa.raw.Path[0] = 0 sa.raw.Path[0] = 0
// Don't count trailing NUL for abstract address. // Don't count trailing NUL for abstract address.
sl-- sl--
@@ -1352,9 +1377,11 @@ func SetsockoptLinger(fd Handle, level, opt int, l *Linger) (err error) {
func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) { func SetsockoptInet4Addr(fd Handle, level, opt int, value [4]byte) (err error) {
return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4) return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(&value[0])), 4)
} }
func SetsockoptIPMreq(fd Handle, level, opt int, mreq *IPMreq) (err error) { func SetsockoptIPMreq(fd Handle, level, opt int, mreq *IPMreq) (err error) {
return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq))) return Setsockopt(fd, int32(level), int32(opt), (*byte)(unsafe.Pointer(mreq)), int32(unsafe.Sizeof(*mreq)))
} }
func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) { func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) {
return syscall.EWINDOWS return syscall.EWINDOWS
} }
@@ -1657,13 +1684,16 @@ func (s NTStatus) Error() string {
// do not use NTUnicodeString, and instead UTF16PtrFromString should be used for // do not use NTUnicodeString, and instead UTF16PtrFromString should be used for
// the more common *uint16 string type. // the more common *uint16 string type.
func NewNTUnicodeString(s string) (*NTUnicodeString, error) { func NewNTUnicodeString(s string) (*NTUnicodeString, error) {
var u NTUnicodeString s16, err := UTF16FromString(s)
s16, err := UTF16PtrFromString(s)
if err != nil { if err != nil {
return nil, err return nil, err
} }
RtlInitUnicodeString(&u, s16) n := uint16(len(s16) * 2)
return &u, nil return &NTUnicodeString{
Length: n - 2, // subtract 2 bytes for the NULL terminator
MaximumLength: n,
Buffer: &s16[0],
}, nil
} }
// Slice returns a uint16 slice that aliases the data in the NTUnicodeString. // Slice returns a uint16 slice that aliases the data in the NTUnicodeString.
@@ -1830,3 +1860,73 @@ func ResizePseudoConsole(pconsole Handle, size Coord) error {
// accept arguments that can be casted to uintptr, and Coord can't. // accept arguments that can be casted to uintptr, and Coord can't.
return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size)))) return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size))))
} }
// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
const (
CBR_110 = 110
CBR_300 = 300
CBR_600 = 600
CBR_1200 = 1200
CBR_2400 = 2400
CBR_4800 = 4800
CBR_9600 = 9600
CBR_14400 = 14400
CBR_19200 = 19200
CBR_38400 = 38400
CBR_57600 = 57600
CBR_115200 = 115200
CBR_128000 = 128000
CBR_256000 = 256000
DTR_CONTROL_DISABLE = 0x00000000
DTR_CONTROL_ENABLE = 0x00000010
DTR_CONTROL_HANDSHAKE = 0x00000020
RTS_CONTROL_DISABLE = 0x00000000
RTS_CONTROL_ENABLE = 0x00001000
RTS_CONTROL_HANDSHAKE = 0x00002000
RTS_CONTROL_TOGGLE = 0x00003000
NOPARITY = 0
ODDPARITY = 1
EVENPARITY = 2
MARKPARITY = 3
SPACEPARITY = 4
ONESTOPBIT = 0
ONE5STOPBITS = 1
TWOSTOPBITS = 2
)
// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction.
const (
SETXOFF = 1
SETXON = 2
SETRTS = 3
CLRRTS = 4
SETDTR = 5
CLRDTR = 6
SETBREAK = 8
CLRBREAK = 9
)
// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm.
const (
PURGE_TXABORT = 0x0001
PURGE_RXABORT = 0x0002
PURGE_TXCLEAR = 0x0004
PURGE_RXCLEAR = 0x0008
)
// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask.
const (
EV_RXCHAR = 0x0001
EV_RXFLAG = 0x0002
EV_TXEMPTY = 0x0004
EV_CTS = 0x0008
EV_DSR = 0x0010
EV_RLSD = 0x0020
EV_BREAK = 0x0040
EV_ERR = 0x0080
EV_RING = 0x0100
)

View File

@@ -176,6 +176,7 @@ const (
WAIT_FAILED = 0xFFFFFFFF WAIT_FAILED = 0xFFFFFFFF
// Access rights for process. // Access rights for process.
PROCESS_ALL_ACCESS = 0xFFFF
PROCESS_CREATE_PROCESS = 0x0080 PROCESS_CREATE_PROCESS = 0x0080
PROCESS_CREATE_THREAD = 0x0002 PROCESS_CREATE_THREAD = 0x0002
PROCESS_DUP_HANDLE = 0x0040 PROCESS_DUP_HANDLE = 0x0040
@@ -1060,6 +1061,7 @@ const (
SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6 SIO_GET_EXTENSION_FUNCTION_POINTER = IOC_INOUT | IOC_WS2 | 6
SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4 SIO_KEEPALIVE_VALS = IOC_IN | IOC_VENDOR | 4
SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12 SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12
SIO_UDP_NETRESET = IOC_IN | IOC_VENDOR | 15
// cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460 // cf. http://support.microsoft.com/default.aspx?scid=kb;en-us;257460
@@ -1095,6 +1097,32 @@ const (
SOMAXCONN = 0x7fffffff SOMAXCONN = 0x7fffffff
TCP_NODELAY = 1 TCP_NODELAY = 1
TCP_EXPEDITED_1122 = 2
TCP_KEEPALIVE = 3
TCP_MAXSEG = 4
TCP_MAXRT = 5
TCP_STDURG = 6
TCP_NOURG = 7
TCP_ATMARK = 8
TCP_NOSYNRETRIES = 9
TCP_TIMESTAMPS = 10
TCP_OFFLOAD_PREFERENCE = 11
TCP_CONGESTION_ALGORITHM = 12
TCP_DELAY_FIN_ACK = 13
TCP_MAXRTMS = 14
TCP_FASTOPEN = 15
TCP_KEEPCNT = 16
TCP_KEEPIDLE = TCP_KEEPALIVE
TCP_KEEPINTVL = 17
TCP_FAIL_CONNECT_ON_ICMP_ERROR = 18
TCP_ICMP_ERROR_INFO = 19
UDP_NOCHECKSUM = 1
UDP_SEND_MSG_SIZE = 2
UDP_RECV_MAX_COALESCED_SIZE = 3
UDP_CHECKSUM_COVERAGE = 20
UDP_COALESCED_INFO = 3
SHUT_RD = 0 SHUT_RD = 0
SHUT_WR = 1 SHUT_WR = 1
@@ -1977,7 +2005,21 @@ const (
MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20 MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20
) )
const GAA_FLAG_INCLUDE_PREFIX = 0x00000010 // Flags for GetAdaptersAddresses, see
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses.
const (
GAA_FLAG_SKIP_UNICAST = 0x1
GAA_FLAG_SKIP_ANYCAST = 0x2
GAA_FLAG_SKIP_MULTICAST = 0x4
GAA_FLAG_SKIP_DNS_SERVER = 0x8
GAA_FLAG_INCLUDE_PREFIX = 0x10
GAA_FLAG_SKIP_FRIENDLY_NAME = 0x20
GAA_FLAG_INCLUDE_WINS_INFO = 0x40
GAA_FLAG_INCLUDE_GATEWAYS = 0x80
GAA_FLAG_INCLUDE_ALL_INTERFACES = 0x100
GAA_FLAG_INCLUDE_ALL_COMPARTMENTS = 0x200
GAA_FLAG_INCLUDE_TUNNEL_BINDINGORDER = 0x400
)
const ( const (
IF_TYPE_OTHER = 1 IF_TYPE_OTHER = 1
@@ -1991,6 +2033,50 @@ const (
IF_TYPE_IEEE1394 = 144 IF_TYPE_IEEE1394 = 144
) )
// Enum NL_PREFIX_ORIGIN for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_prefix_origin
const (
IpPrefixOriginOther = 0
IpPrefixOriginManual = 1
IpPrefixOriginWellKnown = 2
IpPrefixOriginDhcp = 3
IpPrefixOriginRouterAdvertisement = 4
IpPrefixOriginUnchanged = 1 << 4
)
// Enum NL_SUFFIX_ORIGIN for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_suffix_origin
const (
NlsoOther = 0
NlsoManual = 1
NlsoWellKnown = 2
NlsoDhcp = 3
NlsoLinkLayerAddress = 4
NlsoRandom = 5
IpSuffixOriginOther = 0
IpSuffixOriginManual = 1
IpSuffixOriginWellKnown = 2
IpSuffixOriginDhcp = 3
IpSuffixOriginLinkLayerAddress = 4
IpSuffixOriginRandom = 5
IpSuffixOriginUnchanged = 1 << 4
)
// Enum NL_DAD_STATE for [IpAdapterUnicastAddress], see
// https://learn.microsoft.com/en-us/windows/win32/api/nldef/ne-nldef-nl_dad_state
const (
NldsInvalid = 0
NldsTentative = 1
NldsDuplicate = 2
NldsDeprecated = 3
NldsPreferred = 4
IpDadStateInvalid = 0
IpDadStateTentative = 1
IpDadStateDuplicate = 2
IpDadStateDeprecated = 3
IpDadStatePreferred = 4
)
type SocketAddress struct { type SocketAddress struct {
Sockaddr *syscall.RawSockaddrAny Sockaddr *syscall.RawSockaddrAny
SockaddrLength int32 SockaddrLength int32
@@ -2118,6 +2204,132 @@ const (
IfOperStatusLowerLayerDown = 7 IfOperStatusLowerLayerDown = 7
) )
const (
IF_MAX_PHYS_ADDRESS_LENGTH = 32
IF_MAX_STRING_SIZE = 256
)
// MIB_IF_ENTRY_LEVEL enumeration from netioapi.h or
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex.
const (
MibIfEntryNormal = 0
MibIfEntryNormalWithoutStatistics = 2
)
// MIB_NOTIFICATION_TYPE enumeration from netioapi.h or
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ne-netioapi-mib_notification_type.
const (
MibParameterNotification = 0
MibAddInstance = 1
MibDeleteInstance = 2
MibInitialNotification = 3
)
// MibIfRow2 stores information about a particular interface. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_if_row2.
type MibIfRow2 struct {
InterfaceLuid uint64
InterfaceIndex uint32
InterfaceGuid GUID
Alias [IF_MAX_STRING_SIZE + 1]uint16
Description [IF_MAX_STRING_SIZE + 1]uint16
PhysicalAddressLength uint32
PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8
PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]uint8
Mtu uint32
Type uint32
TunnelType uint32
MediaType uint32
PhysicalMediumType uint32
AccessType uint32
DirectionType uint32
InterfaceAndOperStatusFlags uint8
OperStatus uint32
AdminStatus uint32
MediaConnectState uint32
NetworkGuid GUID
ConnectionType uint32
TransmitLinkSpeed uint64
ReceiveLinkSpeed uint64
InOctets uint64
InUcastPkts uint64
InNUcastPkts uint64
InDiscards uint64
InErrors uint64
InUnknownProtos uint64
InUcastOctets uint64
InMulticastOctets uint64
InBroadcastOctets uint64
OutOctets uint64
OutUcastPkts uint64
OutNUcastPkts uint64
OutDiscards uint64
OutErrors uint64
OutUcastOctets uint64
OutMulticastOctets uint64
OutBroadcastOctets uint64
OutQLen uint64
}
// MIB_UNICASTIPADDRESS_ROW stores information about a unicast IP address. See
// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_unicastipaddress_row.
type MibUnicastIpAddressRow struct {
Address RawSockaddrInet6 // SOCKADDR_INET union
InterfaceLuid uint64
InterfaceIndex uint32
PrefixOrigin uint32
SuffixOrigin uint32
ValidLifetime uint32
PreferredLifetime uint32
OnLinkPrefixLength uint8
SkipAsSource uint8
DadState uint32
ScopeId uint32
CreationTimeStamp Filetime
}
const ScopeLevelCount = 16
// MIB_IPINTERFACE_ROW stores interface management information for a particular IP address family on a network interface.
// See https://learn.microsoft.com/en-us/windows/win32/api/netioapi/ns-netioapi-mib_ipinterface_row.
type MibIpInterfaceRow struct {
Family uint16
InterfaceLuid uint64
InterfaceIndex uint32
MaxReassemblySize uint32
InterfaceIdentifier uint64
MinRouterAdvertisementInterval uint32
MaxRouterAdvertisementInterval uint32
AdvertisingEnabled uint8
ForwardingEnabled uint8
WeakHostSend uint8
WeakHostReceive uint8
UseAutomaticMetric uint8
UseNeighborUnreachabilityDetection uint8
ManagedAddressConfigurationSupported uint8
OtherStatefulConfigurationSupported uint8
AdvertiseDefaultRoute uint8
RouterDiscoveryBehavior uint32
DadTransmits uint32
BaseReachableTime uint32
RetransmitTime uint32
PathMtuDiscoveryTimeout uint32
LinkLocalAddressBehavior uint32
LinkLocalAddressTimeout uint32
ZoneIndices [ScopeLevelCount]uint32
SitePrefixLength uint32
Metric uint32
NlMtu uint32
Connected uint8
SupportsWakeUpPatterns uint8
SupportsNeighborDiscovery uint8
SupportsRouterDiscovery uint8
ReachableTime uint32
TransmitOffload uint32
ReceiveOffload uint32
DisableDefaultRoutes uint8
}
// Console related constants used for the mode parameter to SetConsoleMode. See // Console related constants used for the mode parameter to SetConsoleMode. See
// https://docs.microsoft.com/en-us/windows/console/setconsolemode for details. // https://docs.microsoft.com/en-us/windows/console/setconsolemode for details.
@@ -3354,3 +3566,38 @@ type BLOB struct {
Size uint32 Size uint32
BlobData *byte BlobData *byte
} }
type ComStat struct {
Flags uint32
CBInQue uint32
CBOutQue uint32
}
type DCB struct {
DCBlength uint32
BaudRate uint32
Flags uint32
wReserved uint16
XonLim uint16
XoffLim uint16
ByteSize uint8
Parity uint8
StopBits uint8
XonChar byte
XoffChar byte
ErrorChar byte
EofChar byte
EvtChar byte
wReserved1 uint16
}
// Keyboard Layout Flags.
// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-loadkeyboardlayoutw
const (
KLF_ACTIVATE = 0x00000001
KLF_SUBSTITUTE_OK = 0x00000002
KLF_REORDER = 0x00000008
KLF_REPLACELANG = 0x00000010
KLF_NOTELLSHELL = 0x00000080
KLF_SETFORPROCESS = 0x00000100
)

View File

@@ -91,6 +91,7 @@ var (
procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW")
procEqualSid = modadvapi32.NewProc("EqualSid") procEqualSid = modadvapi32.NewProc("EqualSid")
procFreeSid = modadvapi32.NewProc("FreeSid") procFreeSid = modadvapi32.NewProc("FreeSid")
procGetAce = modadvapi32.NewProc("GetAce")
procGetLengthSid = modadvapi32.NewProc("GetLengthSid") procGetLengthSid = modadvapi32.NewProc("GetLengthSid")
procGetNamedSecurityInfoW = modadvapi32.NewProc("GetNamedSecurityInfoW") procGetNamedSecurityInfoW = modadvapi32.NewProc("GetNamedSecurityInfoW")
procGetSecurityDescriptorControl = modadvapi32.NewProc("GetSecurityDescriptorControl") procGetSecurityDescriptorControl = modadvapi32.NewProc("GetSecurityDescriptorControl")
@@ -180,13 +181,21 @@ var (
procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree") procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree")
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute") procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute") procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procCancelMibChangeNotify2 = modiphlpapi.NewProc("CancelMibChangeNotify2")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo") procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo")
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx") procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex")
procGetUnicastIpAddressEntry = modiphlpapi.NewProc("GetUnicastIpAddressEntry")
procNotifyIpInterfaceChange = modiphlpapi.NewProc("NotifyIpInterfaceChange")
procNotifyUnicastIpAddressChange = modiphlpapi.NewProc("NotifyUnicastIpAddressChange")
procAddDllDirectory = modkernel32.NewProc("AddDllDirectory")
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
procCancelIo = modkernel32.NewProc("CancelIo") procCancelIo = modkernel32.NewProc("CancelIo")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
procClearCommError = modkernel32.NewProc("ClearCommError")
procCloseHandle = modkernel32.NewProc("CloseHandle") procCloseHandle = modkernel32.NewProc("CloseHandle")
procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole") procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
@@ -211,7 +220,9 @@ var (
procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList") procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList")
procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW") procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW")
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
procDuplicateHandle = modkernel32.NewProc("DuplicateHandle") procDuplicateHandle = modkernel32.NewProc("DuplicateHandle")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procExitProcess = modkernel32.NewProc("ExitProcess") procExitProcess = modkernel32.NewProc("ExitProcess")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
procFindClose = modkernel32.NewProc("FindClose") procFindClose = modkernel32.NewProc("FindClose")
@@ -235,11 +246,15 @@ var (
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent") procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP") procGetACP = modkernel32.NewProc("GetACP")
procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount") procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
procGetCommState = modkernel32.NewProc("GetCommState")
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts") procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW") procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
procGetComputerNameW = modkernel32.NewProc("GetComputerNameW") procGetComputerNameW = modkernel32.NewProc("GetComputerNameW")
procGetConsoleCP = modkernel32.NewProc("GetConsoleCP")
procGetConsoleMode = modkernel32.NewProc("GetConsoleMode") procGetConsoleMode = modkernel32.NewProc("GetConsoleMode")
procGetConsoleOutputCP = modkernel32.NewProc("GetConsoleOutputCP")
procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo") procGetConsoleScreenBufferInfo = modkernel32.NewProc("GetConsoleScreenBufferInfo")
procGetCurrentDirectoryW = modkernel32.NewProc("GetCurrentDirectoryW") procGetCurrentDirectoryW = modkernel32.NewProc("GetCurrentDirectoryW")
procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId") procGetCurrentProcessId = modkernel32.NewProc("GetCurrentProcessId")
@@ -253,6 +268,7 @@ var (
procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW") procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW")
procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle") procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx") procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procGetFileTime = modkernel32.NewProc("GetFileTime")
procGetFileType = modkernel32.NewProc("GetFileType") procGetFileType = modkernel32.NewProc("GetFileType")
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW") procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW")
@@ -264,8 +280,10 @@ var (
procGetMaximumProcessorCount = modkernel32.NewProc("GetMaximumProcessorCount") procGetMaximumProcessorCount = modkernel32.NewProc("GetMaximumProcessorCount")
procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW") procGetModuleFileNameW = modkernel32.NewProc("GetModuleFileNameW")
procGetModuleHandleExW = modkernel32.NewProc("GetModuleHandleExW") procGetModuleHandleExW = modkernel32.NewProc("GetModuleHandleExW")
procGetNamedPipeClientProcessId = modkernel32.NewProc("GetNamedPipeClientProcessId")
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW") procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo") procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
procGetNamedPipeServerProcessId = modkernel32.NewProc("GetNamedPipeServerProcessId")
procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult") procGetOverlappedResult = modkernel32.NewProc("GetOverlappedResult")
procGetPriorityClass = modkernel32.NewProc("GetPriorityClass") procGetPriorityClass = modkernel32.NewProc("GetPriorityClass")
procGetProcAddress = modkernel32.NewProc("GetProcAddress") procGetProcAddress = modkernel32.NewProc("GetProcAddress")
@@ -320,6 +338,7 @@ var (
procProcess32NextW = modkernel32.NewProc("Process32NextW") procProcess32NextW = modkernel32.NewProc("Process32NextW")
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId") procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
procPulseEvent = modkernel32.NewProc("PulseEvent") procPulseEvent = modkernel32.NewProc("PulseEvent")
procPurgeComm = modkernel32.NewProc("PurgeComm")
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW") procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW")
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject") procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject")
@@ -329,12 +348,18 @@ var (
procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory")
procReleaseMutex = modkernel32.NewProc("ReleaseMutex") procReleaseMutex = modkernel32.NewProc("ReleaseMutex")
procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW") procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW")
procRemoveDllDirectory = modkernel32.NewProc("RemoveDllDirectory")
procResetEvent = modkernel32.NewProc("ResetEvent") procResetEvent = modkernel32.NewProc("ResetEvent")
procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole") procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole")
procResumeThread = modkernel32.NewProc("ResumeThread") procResumeThread = modkernel32.NewProc("ResumeThread")
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
procSetCommMask = modkernel32.NewProc("SetCommMask")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procSetConsoleCP = modkernel32.NewProc("SetConsoleCP")
procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition") procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode") procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
procSetConsoleOutputCP = modkernel32.NewProc("SetConsoleOutputCP")
procSetCurrentDirectoryW = modkernel32.NewProc("SetCurrentDirectoryW") procSetCurrentDirectoryW = modkernel32.NewProc("SetCurrentDirectoryW")
procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories") procSetDefaultDllDirectories = modkernel32.NewProc("SetDefaultDllDirectories")
procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW") procSetDllDirectoryW = modkernel32.NewProc("SetDllDirectoryW")
@@ -347,6 +372,7 @@ var (
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle") procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procSetFilePointer = modkernel32.NewProc("SetFilePointer") procSetFilePointer = modkernel32.NewProc("SetFilePointer")
procSetFileTime = modkernel32.NewProc("SetFileTime") procSetFileTime = modkernel32.NewProc("SetFileTime")
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
procSetHandleInformation = modkernel32.NewProc("SetHandleInformation") procSetHandleInformation = modkernel32.NewProc("SetHandleInformation")
procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject") procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject")
procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState") procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState")
@@ -357,6 +383,7 @@ var (
procSetStdHandle = modkernel32.NewProc("SetStdHandle") procSetStdHandle = modkernel32.NewProc("SetStdHandle")
procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW") procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW")
procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW") procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW")
procSetupComm = modkernel32.NewProc("SetupComm")
procSizeofResource = modkernel32.NewProc("SizeofResource") procSizeofResource = modkernel32.NewProc("SizeofResource")
procSleepEx = modkernel32.NewProc("SleepEx") procSleepEx = modkernel32.NewProc("SleepEx")
procTerminateJobObject = modkernel32.NewProc("TerminateJobObject") procTerminateJobObject = modkernel32.NewProc("TerminateJobObject")
@@ -375,6 +402,7 @@ var (
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx") procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock") procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId") procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
procWaitCommEvent = modkernel32.NewProc("WaitCommEvent")
procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects") procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects")
procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject") procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject")
procWriteConsoleW = modkernel32.NewProc("WriteConsoleW") procWriteConsoleW = modkernel32.NewProc("WriteConsoleW")
@@ -385,6 +413,7 @@ var (
procTransmitFile = modmswsock.NewProc("TransmitFile") procTransmitFile = modmswsock.NewProc("TransmitFile")
procNetApiBufferFree = modnetapi32.NewProc("NetApiBufferFree") procNetApiBufferFree = modnetapi32.NewProc("NetApiBufferFree")
procNetGetJoinInformation = modnetapi32.NewProc("NetGetJoinInformation") procNetGetJoinInformation = modnetapi32.NewProc("NetGetJoinInformation")
procNetUserEnum = modnetapi32.NewProc("NetUserEnum")
procNetUserGetInfo = modnetapi32.NewProc("NetUserGetInfo") procNetUserGetInfo = modnetapi32.NewProc("NetUserGetInfo")
procNtCreateFile = modntdll.NewProc("NtCreateFile") procNtCreateFile = modntdll.NewProc("NtCreateFile")
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile") procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
@@ -460,12 +489,16 @@ var (
procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow") procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow")
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow") procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo") procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo")
procGetKeyboardLayout = moduser32.NewProc("GetKeyboardLayout")
procGetShellWindow = moduser32.NewProc("GetShellWindow") procGetShellWindow = moduser32.NewProc("GetShellWindow")
procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId")
procIsWindow = moduser32.NewProc("IsWindow") procIsWindow = moduser32.NewProc("IsWindow")
procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode") procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode")
procIsWindowVisible = moduser32.NewProc("IsWindowVisible") procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
procLoadKeyboardLayoutW = moduser32.NewProc("LoadKeyboardLayoutW")
procMessageBoxW = moduser32.NewProc("MessageBoxW") procMessageBoxW = moduser32.NewProc("MessageBoxW")
procToUnicodeEx = moduser32.NewProc("ToUnicodeEx")
procUnloadKeyboardLayout = moduser32.NewProc("UnloadKeyboardLayout")
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock") procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock") procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW") procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW")
@@ -771,6 +804,14 @@ func FreeSid(sid *SID) (err error) {
return return
} }
func GetAce(acl *ACL, aceIndex uint32, pAce **ACCESS_ALLOWED_ACE) (err error) {
r1, _, e1 := syscall.Syscall(procGetAce.Addr(), 3, uintptr(unsafe.Pointer(acl)), uintptr(aceIndex), uintptr(unsafe.Pointer(pAce)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetLengthSid(sid *SID) (len uint32) { func GetLengthSid(sid *SID) (len uint32) {
r0, _, _ := syscall.Syscall(procGetLengthSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0) r0, _, _ := syscall.Syscall(procGetLengthSid.Addr(), 1, uintptr(unsafe.Pointer(sid)), 0, 0)
len = uint32(r0) len = uint32(r0)
@@ -1572,6 +1613,14 @@ func DwmSetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, si
return return
} }
func CancelMibChangeNotify2(notificationHandle Handle) (errcode error) {
r0, _, _ := syscall.Syscall(procCancelMibChangeNotify2.Addr(), 1, uintptr(notificationHandle), 0, 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0) r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0)
if r0 != 0 { if r0 != 0 {
@@ -1604,6 +1653,55 @@ func GetIfEntry(pIfRow *MibIfRow) (errcode error) {
return return
} }
func GetIfEntry2Ex(level uint32, row *MibIfRow2) (errcode error) {
r0, _, _ := syscall.Syscall(procGetIfEntry2Ex.Addr(), 2, uintptr(level), uintptr(unsafe.Pointer(row)), 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func GetUnicastIpAddressEntry(row *MibUnicastIpAddressRow) (errcode error) {
r0, _, _ := syscall.Syscall(procGetUnicastIpAddressEntry.Addr(), 1, uintptr(unsafe.Pointer(row)), 0, 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func NotifyIpInterfaceChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {
_p0 = 1
}
r0, _, _ := syscall.Syscall6(procNotifyIpInterfaceChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func NotifyUnicastIpAddressChange(family uint16, callback uintptr, callerContext unsafe.Pointer, initialNotification bool, notificationHandle *Handle) (errcode error) {
var _p0 uint32
if initialNotification {
_p0 = 1
}
r0, _, _ := syscall.Syscall6(procNotifyUnicastIpAddressChange.Addr(), 5, uintptr(family), uintptr(callback), uintptr(callerContext), uintptr(_p0), uintptr(unsafe.Pointer(notificationHandle)), 0)
if r0 != 0 {
errcode = syscall.Errno(r0)
}
return
}
func AddDllDirectory(path *uint16) (cookie uintptr, err error) {
r0, _, e1 := syscall.Syscall(procAddDllDirectory.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0)
cookie = uintptr(r0)
if cookie == 0 {
err = errnoErr(e1)
}
return
}
func AssignProcessToJobObject(job Handle, process Handle) (err error) { func AssignProcessToJobObject(job Handle, process Handle) (err error) {
r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0) r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0)
if r1 == 0 { if r1 == 0 {
@@ -1628,6 +1726,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
return return
} }
func ClearCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func CloseHandle(handle Handle) (err error) { func CloseHandle(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0) r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -1832,6 +1946,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
return return
} }
func DisconnectNamedPipe(pipe Handle) (err error) {
r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) { func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) {
var _p0 uint32 var _p0 uint32
if bInheritHandle { if bInheritHandle {
@@ -1844,6 +1966,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
return return
} }
func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) {
r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ExitProcess(exitcode uint32) { func ExitProcess(exitcode uint32) {
syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0) syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
return return
@@ -2045,6 +2175,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
return return
} }
func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2075,6 +2221,15 @@ func GetComputerName(buf *uint16, n *uint32) (err error) {
return return
} }
func GetConsoleCP() (cp uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetConsoleCP.Addr(), 0, 0, 0, 0)
cp = uint32(r0)
if cp == 0 {
err = errnoErr(e1)
}
return
}
func GetConsoleMode(console Handle, mode *uint32) (err error) { func GetConsoleMode(console Handle, mode *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(mode)), 0) r1, _, e1 := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(mode)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2083,6 +2238,15 @@ func GetConsoleMode(console Handle, mode *uint32) (err error) {
return return
} }
func GetConsoleOutputCP() (cp uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetConsoleOutputCP.Addr(), 0, 0, 0, 0)
cp = uint32(r0)
if cp == 0 {
err = errnoErr(e1)
}
return
}
func GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) { func GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) {
r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(info)), 0) r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(console), uintptr(unsafe.Pointer(info)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2185,6 +2349,14 @@ func GetFileInformationByHandleEx(handle Handle, class uint32, outBuffer *byte,
return return
} }
func GetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileTime.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(ctime)), uintptr(unsafe.Pointer(atime)), uintptr(unsafe.Pointer(wtime)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetFileType(filehandle Handle) (n uint32, err error) { func GetFileType(filehandle Handle) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0) r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0)
n = uint32(r0) n = uint32(r0)
@@ -2276,6 +2448,14 @@ func GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err er
return return
} }
func GetNamedPipeClientProcessId(pipe Handle, clientProcessID *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetNamedPipeClientProcessId.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(clientProcessID)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) { func GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0) r1, _, e1 := syscall.Syscall9(procGetNamedPipeHandleStateW.Addr(), 7, uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -2292,6 +2472,14 @@ func GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint3
return return
} }
func GetNamedPipeServerProcessId(pipe Handle, serverProcessID *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetNamedPipeServerProcessId.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(serverProcessID)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetOverlappedResult(handle Handle, overlapped *Overlapped, done *uint32, wait bool) (err error) { func GetOverlappedResult(handle Handle, overlapped *Overlapped, done *uint32, wait bool) (err error) {
var _p0 uint32 var _p0 uint32
if wait { if wait {
@@ -2789,6 +2977,14 @@ func PulseEvent(event Handle) (err error) {
return return
} }
func PurgeComm(handle Handle, dwFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) { func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max)) r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
n = uint32(r0) n = uint32(r0)
@@ -2870,6 +3066,14 @@ func RemoveDirectory(path *uint16) (err error) {
return return
} }
func RemoveDllDirectory(cookie uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procRemoveDllDirectory.Addr(), 1, uintptr(cookie), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ResetEvent(event Handle) (err error) { func ResetEvent(event Handle) (err error) {
r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0) r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -2895,6 +3099,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
return return
} }
func SetCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommMask(handle Handle, dwEvtMask uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2903,6 +3131,14 @@ func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
return return
} }
func SetConsoleCP(cp uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetConsoleCP.Addr(), 1, uintptr(cp), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func setConsoleCursorPosition(console Handle, position uint32) (err error) { func setConsoleCursorPosition(console Handle, position uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetConsoleCursorPosition.Addr(), 2, uintptr(console), uintptr(position), 0) r1, _, e1 := syscall.Syscall(procSetConsoleCursorPosition.Addr(), 2, uintptr(console), uintptr(position), 0)
if r1 == 0 { if r1 == 0 {
@@ -2919,6 +3155,14 @@ func SetConsoleMode(console Handle, mode uint32) (err error) {
return return
} }
func SetConsoleOutputCP(cp uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetConsoleOutputCP.Addr(), 1, uintptr(cp), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCurrentDirectory(path *uint16) (err error) { func SetCurrentDirectory(path *uint16) (err error) {
r1, _, e1 := syscall.Syscall(procSetCurrentDirectoryW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0) r1, _, e1 := syscall.Syscall(procSetCurrentDirectoryW.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -3023,6 +3267,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
return return
} }
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) { func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags)) r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
if r1 == 0 { if r1 == 0 {
@@ -3108,6 +3360,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
return return
} }
func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) { func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) {
r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0) r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
size = uint32(r0) size = uint32(r0)
@@ -3254,6 +3514,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
return return
} }
func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) { func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) {
var _p0 uint32 var _p0 uint32
if waitAll { if waitAll {
@@ -3341,6 +3609,14 @@ func NetGetJoinInformation(server *uint16, name **uint16, bufType *uint32) (nete
return return
} }
func NetUserEnum(serverName *uint16, level uint32, filter uint32, buf **byte, prefMaxLen uint32, entriesRead *uint32, totalEntries *uint32, resumeHandle *uint32) (neterr error) {
r0, _, _ := syscall.Syscall9(procNetUserEnum.Addr(), 8, uintptr(unsafe.Pointer(serverName)), uintptr(level), uintptr(filter), uintptr(unsafe.Pointer(buf)), uintptr(prefMaxLen), uintptr(unsafe.Pointer(entriesRead)), uintptr(unsafe.Pointer(totalEntries)), uintptr(unsafe.Pointer(resumeHandle)), 0)
if r0 != 0 {
neterr = syscall.Errno(r0)
}
return
}
func NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) { func NetUserGetInfo(serverName *uint16, userName *uint16, level uint32, buf **byte) (neterr error) {
r0, _, _ := syscall.Syscall6(procNetUserGetInfo.Addr(), 4, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(unsafe.Pointer(buf)), 0, 0) r0, _, _ := syscall.Syscall6(procNetUserGetInfo.Addr(), 4, uintptr(unsafe.Pointer(serverName)), uintptr(unsafe.Pointer(userName)), uintptr(level), uintptr(unsafe.Pointer(buf)), 0, 0)
if r0 != 0 { if r0 != 0 {
@@ -3919,6 +4195,12 @@ func GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) {
return return
} }
func GetKeyboardLayout(tid uint32) (hkl Handle) {
r0, _, _ := syscall.Syscall(procGetKeyboardLayout.Addr(), 1, uintptr(tid), 0, 0)
hkl = Handle(r0)
return
}
func GetShellWindow() (shellWindow HWND) { func GetShellWindow() (shellWindow HWND) {
r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0) r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0)
shellWindow = HWND(r0) shellWindow = HWND(r0)
@@ -3952,6 +4234,15 @@ func IsWindowVisible(hwnd HWND) (isVisible bool) {
return return
} }
func LoadKeyboardLayout(name *uint16, flags uint32) (hkl Handle, err error) {
r0, _, e1 := syscall.Syscall(procLoadKeyboardLayoutW.Addr(), 2, uintptr(unsafe.Pointer(name)), uintptr(flags), 0)
hkl = Handle(r0)
if hkl == 0 {
err = errnoErr(e1)
}
return
}
func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) { func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) {
r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0) r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0)
ret = int32(r0) ret = int32(r0)
@@ -3961,6 +4252,20 @@ func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret i
return return
} }
func ToUnicodeEx(vkey uint32, scancode uint32, keystate *byte, pwszBuff *uint16, cchBuff int32, flags uint32, hkl Handle) (ret int32) {
r0, _, _ := syscall.Syscall9(procToUnicodeEx.Addr(), 7, uintptr(vkey), uintptr(scancode), uintptr(unsafe.Pointer(keystate)), uintptr(unsafe.Pointer(pwszBuff)), uintptr(cchBuff), uintptr(flags), uintptr(hkl), 0, 0)
ret = int32(r0)
return
}
func UnloadKeyboardLayout(hkl Handle) (err error) {
r1, _, e1 := syscall.Syscall(procUnloadKeyboardLayout.Addr(), 1, uintptr(hkl), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) { func CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) {
var _p0 uint32 var _p0 uint32
if inheritExisting { if inheritExisting {

4
vendor/golang.org/x/text/LICENSE generated vendored
View File

@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved. Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the in the documentation and/or other materials provided with the
distribution. distribution.
* Neither the name of Google Inc. nor the names of its * Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from contributors may be used to endorse or promote products derived from
this software without specific prior written permission. this software without specific prior written permission.

12
vendor/modules.txt vendored
View File

@@ -1,16 +1,16 @@
# github.com/mattn/go-sqlite3 v1.14.7 # github.com/mattn/go-sqlite3 v1.14.7
## explicit; go 1.12 ## explicit; go 1.12
github.com/mattn/go-sqlite3 github.com/mattn/go-sqlite3
# golang.org/x/net v0.17.0 # golang.org/x/net v0.33.0
## explicit; go 1.17 ## explicit; go 1.18
golang.org/x/net/html golang.org/x/net/html
golang.org/x/net/html/atom golang.org/x/net/html/atom
golang.org/x/net/html/charset golang.org/x/net/html/charset
# golang.org/x/sys v0.13.0 # golang.org/x/sys v0.28.0
## explicit; go 1.17 ## explicit; go 1.18
golang.org/x/sys/windows golang.org/x/sys/windows
# golang.org/x/text v0.13.0 # golang.org/x/text v0.21.0
## explicit; go 1.17 ## explicit; go 1.18
golang.org/x/text/encoding golang.org/x/text/encoding
golang.org/x/text/encoding/charmap golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/htmlindex golang.org/x/text/encoding/htmlindex