diff options
| author | Paul Buetow <paul@buetow.org> | 2026-02-17 22:03:10 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-02-17 22:03:10 +0200 |
| commit | 5f53f241189af3fceb26395af383809cd4fa3bf0 (patch) | |
| tree | 4ffabc358ec85ed1da425f72e1357e80f340b2c1 | |
| parent | a8cb28d2257cff652fc4f8a9768b66b9d69fd68a (diff) | |
fix: improve LinkedIn API posting and authenticationv1.2.4
- Add token validation before using cached credentials to prevent stale token issues
- Add proper error handling for image upload initialization with helpful 426 messages
- Skip data URI images instead of attempting to download them
- Update default LinkedIn API version to 202601 (January 2026) - latest active version
- Fix re-authentication flow for expired tokens
Amp-Thread-ID: https://ampcode.com/threads/T-019c6d28-4526-7738-b593-9bd584baa478
Co-authored-by: Amp <amp@ampcode.com>
| -rw-r--r-- | AGENTS.md | 94 | ||||
| -rw-r--r-- | PLAN.md | 26 | ||||
| -rwxr-xr-x | gos | bin | 0 -> 10792292 bytes | |||
| -rwxr-xr-x | gosc | bin | 0 -> 10792292 bytes | |||
| -rw-r--r-- | gosdir/db/platforms/noop/testing.share:noop.extracted.txt.20250517-174027.posted | 1 | ||||
| -rw-r--r-- | gosdir/db/trashbin/testing.share:noop.extracted.txt.20250517-174017.trash | 1 | ||||
| -rw-r--r-- | hexai-lsp.log | 100 | ||||
| -rw-r--r-- | internal/platforms/linkedin/linkedin.go | 27 | ||||
| -rw-r--r-- | internal/platforms/linkedin/oauth2/oauth2.go | 8 | ||||
| -rw-r--r-- | internal/platforms/linkedin/preview.go | 6 | ||||
| -rw-r--r-- | internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377 | 2 | ||||
| -rw-r--r-- | internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983 | 2 | ||||
| -rw-r--r-- | internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d | 2 | ||||
| -rw-r--r-- | internal/version.go | 2 | ||||
| -rw-r--r-- | run.sh | 10 |
15 files changed, 275 insertions, 6 deletions
diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1d01ea4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# AGENTS.md + +## Project Overview + +Gos (Go Social Media) is a Go-based command-line tool for scheduling and managing social media posts to Mastodon and LinkedIn. It serves as a replacement for Buffer.com, allowing users to queue and schedule posts from the terminal. + +## Project Structure + +``` +gos/ +├── cmd/ +│ ├── gos/ # Main gos binary +│ └── gosc/ # Gos Composer binary (quick post composition) +├── internal/ +│ ├── colour/ # Terminal color utilities +│ ├── config/ # Configuration management +│ ├── entry/ # Post entry handling +│ ├── oi/ # Output/input utilities +│ ├── platforms/ # Social media platform implementations +│ │ ├── linkedin/ +│ │ ├── mastodon/ +│ │ ├── noop/ +│ │ └── platform.go +│ ├── prompt/ # User prompts +│ ├── queue/ # Message queue management +│ ├── schedule/ # Posting schedule logic +│ ├── summary/ # Gemini gemtext summary generation +│ ├── table/ # Table output formatting +│ ├── tags/ # Share tag parsing +│ ├── timestamp/ # Timestamp utilities +│ ├── main.go +│ ├── run.go +│ └── version.go +├── docs/ # Documentation and images +├── examples/ # Example files +└── gosdir/ # Example gos directory structure +``` + +## Build System + +This project uses [Mage](https://magefile.org/) for build automation. + +### Commands + +| Command | Description | +|---------|-------------| +| `mage` or `mage build` | Build `gos` and `gosc` binaries | +| `mage install` | Build and install binaries to `~/go/bin` | +| `mage clean` | Remove built binaries | +| `mage test` | Run all tests | +| `mage fuzz` | Run fuzz tests (10s) | +| `mage lint` | Run golangci-lint | +| `mage vet` | Run `go vet` | +| `mage dev` | Run tests, vet, lint, then build with race detector | +| `mage devInstall` | Install `gopls` and `golangci-lint` | + +### Before Committing + +Run `mage dev` to ensure tests pass, vet and lint checks succeed, and the build completes with race detection. + +## Testing + +- Tests use the standard Go testing framework +- Test files follow the `*_test.go` naming convention +- Run tests: `mage test` or `go test -v ./...` +- Run fuzz tests: `mage fuzz` or `go test ./internal/entry/ -fuzz=FuzzExtractURLs -fuzztime=10s` + +## Code Conventions + +- **Go version**: 1.23+ +- **Module path**: `codeberg.org/snonux/gos` +- **Package layout**: Internal packages under `internal/`, commands under `cmd/` +- **Error handling**: Standard Go error handling patterns +- **Dependencies**: Minimal external dependencies (fatih/color, golang.org/x packages) + +### Go Coding Practices + +Follow the practices defined in `/home/paul/git/conf/snippets/go/go-projects/go-projects.md`: + +## Linting + +Uses `golangci-lint`. Install with: +```bash +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest +``` + +Or run `mage devInstall`. + +## Key Dependencies + +- `github.com/fatih/color` - Terminal colors +- `golang.org/x/oauth2` - OAuth2 for LinkedIn +- `golang.org/x/net` - HTML parsing for LinkedIn previews +- `github.com/magefile/mage` - Build automation @@ -0,0 +1,26 @@ +# Plan + +- Add a new flag `--stats` which would only print out the stats for all social networks, but do nothing else. + +## Implementation Outline for `--stats` flag: (ALL DONE) + +1. **Modify `internal/main.go`**: (DONE) + * **Define the `--stats` flag**: Add `statsOnly := flag.Bool("stats", false, "Print statistics for all social networks and exit")` near other flag definitions. + * **Populate `config.Args`**: Assign `*statsOnly` to `args.StatsOnly` after `flag.Parse()`. + * **Conditional logic**: After `flag.Parse()` and before calling `run(ctx, args)`, add a check: + ```go + if args.StatsOnly { + // Call the new function to print all stats + schedule.PrintAllStats(args) + return // Exit after printing stats + } + ``` + +2. **Modify `internal/config/args.go`**: (DONE) + * Add `StatsOnly bool` to the `Args` struct. + +3. **Modify `internal/schedule/stats.go`**: (DONE) + * **Create a new public function `PrintAllStats(args config.Args)`**: + * This function will iterate through `args.Platforms`. + * For each platform, it will call `newStats` to gather the statistics. + * Then, it will call `stats.RenderTable` to display the statistics for that platform.
\ No newline at end of file Binary files differBinary files differdiff --git a/gosdir/db/platforms/noop/testing.share:noop.extracted.txt.20250517-174027.posted b/gosdir/db/platforms/noop/testing.share:noop.extracted.txt.20250517-174027.posted new file mode 100644 index 0000000..cb9d77f --- /dev/null +++ b/gosdir/db/platforms/noop/testing.share:noop.extracted.txt.20250517-174027.posted @@ -0,0 +1 @@ +fxoobarbaz #testing
\ No newline at end of file diff --git a/gosdir/db/trashbin/testing.share:noop.extracted.txt.20250517-174017.trash b/gosdir/db/trashbin/testing.share:noop.extracted.txt.20250517-174017.trash new file mode 100644 index 0000000..cb9d77f --- /dev/null +++ b/gosdir/db/trashbin/testing.share:noop.extracted.txt.20250517-174017.trash @@ -0,0 +1 @@ +fxoobarbaz #testing
\ No newline at end of file diff --git a/hexai-lsp.log b/hexai-lsp.log new file mode 100644 index 0000000..21cb1bb --- /dev/null +++ b/hexai-lsp.log @@ -0,0 +1,100 @@ +2025/09/04 15:40:17 hexai-lsp [40m[90mllm/openai msg[1] role=user size=339 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/git/hexai/PROJECTSTATU…[40m[90m[0m +2025/09/04 15:40:17 hexai-lsp [40m[90mllm/openai msg[2] role=user size=3342 preview=[36mAdditional context: +# Ideas + +## Code quality + +* [/] TODO's in the code to be addressed +* [/] No more…[40m[90m[0m +2025/09/04 15:40:17 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp completion trigger kind=1 char="" uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=75[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp completion ctx uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=75 above="" current="* [ ] Modify the LLM triggers to be more consistenc. E.g. use >>text here>" below="* [X] Include unit test coverage reports" function=""[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=22 preview=[32mnt with other triggers[40m[90m duration=631.487672ms[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp llm stats reqs=22 avg_sent=3915 avg_recv=71 sent_total=86132 recv_total=1431 rpm=3.06 sent_per_min=11993 recv_per_min=199[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp completion llm=requesting model=gpt-4.1[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai chat start model=gpt-4.1 temp=0.20 max_tokens=4000 stop=0 messages=3[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai msg[0] role=system size=306 preview=[36mYou are a terse code completion engine. Return only the code to insert, no surrounding prose or back…[40m[90m[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai msg[1] role=user size=340 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/git/hexai/PROJECTSTATU…[40m[90m[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai msg[2] role=user size=3343 preview=[36mAdditional context: +# Ideas + +## Code quality + +* [/] TODO's in the code to be addressed +* [/] No more…[40m[90m[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp completion trigger kind=1 char="" uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=78[0m +2025/09/04 15:40:18 hexai-lsp [40m[90mlsp completion ctx uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=78 above="" current="* [ ] Modify the LLM triggers to be more consistenc. E.g. use >>text here> or" below="* [X] Include unit test coverage reports" function=""[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp completion llm=requesting model=gpt-4.1[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai chat start model=gpt-4.1 temp=0.20 max_tokens=4000 stop=0 messages=3[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[0] role=system size=306 preview=[36mYou are a terse code completion engine. Return only the code to insert, no surrounding prose or back…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[1] role=user size=343 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/git/hexai/PROJECTSTATU…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[2] role=user size=3346 preview=[36mAdditional context: +# Ideas + +## Code quality + +* [/] TODO's in the code to be addressed +* [/] No more…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp completion trigger kind=1 char="" uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=79[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp completion ctx uri=file:///home/paul/git/hexai/PROJECTSTATUS.md line=15 char=79 above="" current="* [ ] Modify the LLM triggers to be more consistenc. E.g. use >>text here> or >" below="* [X] Include unit test coverage reports" function=""[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=91 preview=[32m* [ ] Audit all LLM trigger points for consistency and document the standard trigger format[40m[90m duration=1.796370662s[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp llm stats reqs=24 avg_sent=3921 avg_recv=72 sent_total=94124 recv_total=1522 rpm=3.33 sent_per_min=13077 recv_per_min=211[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp completion llm=requesting model=gpt-4.1[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai chat start model=gpt-4.1 temp=0.20 max_tokens=4000 stop=0 messages=3[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[0] role=system size=306 preview=[36mYou are a terse code completion engine. Return only the code to insert, no surrounding prose or back…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[1] role=user size=344 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/git/hexai/PROJECTSTATU…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai msg[2] role=user size=3347 preview=[36mAdditional context: +# Ideas + +## Code quality + +* [/] TODO's in the code to be addressed +* [/] No more…[40m[90m[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=45 preview=[32m>text here> throughout for inline completions[40m[90m duration=807.205456ms[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=93 preview=[32m* [ ] Standardize all LLM triggers to use a consistent delimiter format, e.g. `>>text here>>`[40m[90m duration=1.36528963s[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp llm stats reqs=24 avg_sent=3921 avg_recv=71 sent_total=94124 recv_total=1567 rpm=3.33 sent_per_min=13062 recv_per_min=217[0m +2025/09/04 15:40:19 hexai-lsp [40m[90mlsp llm stats reqs=24 avg_sent=3921 avg_recv=72 sent_total=94124 recv_total=1660 rpm=3.33 sent_per_min=13062 recv_per_min=230[0m +2025/09/04 15:40:20 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=81 preview=[32m* [ ] Audit all trigger patterns for consistency and document the standard format[40m[90m duration=666.442236ms[0m +2025/09/04 15:40:20 hexai-lsp [40m[90mlsp llm stats reqs=24 avg_sent=3921 avg_recv=72 sent_total=94124 recv_total=1741 rpm=3.33 sent_per_min=13056 recv_per_min=242[0m +2025/09/04 15:46:43 hexai-lsp [40m[90mlsp llm enabled provider=openai model=gpt-4.1[0m +2025/09/04 15:46:43 hexai-lsp [40m[90mlsp client initialized[0m +2025/09/04 15:46:44 hexai-lsp [40m[90mlsp completion trigger kind=1 char="" uri=file:///home/paul/Notes/Listen/Urlaub.md line=0 char=1[0m +2025/09/04 15:46:44 hexai-lsp [40m[90mlsp completion ctx uri=file:///home/paul/Notes/Listen/Urlaub.md line=0 char=1 above="" current="#" below="" function=""[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mlsp completion llm=requesting model=gpt-4.1[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mllm/openai chat start model=gpt-4.1 temp=0.20 max_tokens=4000 stop=0 messages=3[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mllm/openai msg[0] role=system size=306 preview=[36mYou are a terse code completion engine. Return only the code to insert, no surrounding prose or back…[40m[90m[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mllm/openai msg[1] role=user size=221 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/Notes/Listen/Urlaub.md…[40m[90m[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mllm/openai msg[2] role=user size=22 preview=[36mAdditional context: +# +[40m[90m[0m +2025/09/04 15:46:45 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:46:46 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=6 preview=[32mUrlaub[40m[90m duration=970.760029ms[0m +2025/09/04 15:46:46 hexai-lsp [40m[90mlsp llm stats reqs=1 avg_sent=549 avg_recv=6 sent_total=549 recv_total=6 rpm=27.74 sent_per_min=15228 recv_per_min=166[0m +2025/09/04 15:46:59 hexai-lsp [40m[90mlsp llm enabled provider=openai model=gpt-4.1[0m +2025/09/04 15:46:59 hexai-lsp [40m[90mlsp client initialized[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mlsp completion trigger kind=1 char="" uri=file:///home/paul/Notes/Listen/Vacation.md line=0 char=0[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mlsp completion ctx uri=file:///home/paul/Notes/Listen/Vacation.md line=0 char=0 above="" current="" below="" function=""[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mlsp completion llm=requesting model=gpt-4.1[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mllm/openai chat start model=gpt-4.1 temp=0.20 max_tokens=4000 stop=0 messages=3[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mllm/openai msg[0] role=system size=306 preview=[36mYou are a terse code completion engine. Return only the code to insert, no surrounding prose or back…[40m[90m[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mllm/openai msg[1] role=user size=222 preview=[36mProvide the next likely code to insert at the cursor. +File: file:///home/paul/Notes/Listen/Vacation.…[40m[90m[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mllm/openai msg[2] role=user size=21 preview=[36mAdditional context: + +[40m[90m[0m +2025/09/04 15:47:02 hexai-lsp [40m[90mllm/openai POST https://api.openai.com/v1/chat/completions[0m +2025/09/04 15:47:04 hexai-lsp [40m[90mllm/openai success choice=0 finish=stop size=126 preview=[32m- Book flights +- Reserve hotel +- Create itinerary +- Pack essentials +- Arrange transportation +- Set v…[40m[90m duration=1.350494698s[0m +2025/09/04 15:47:04 hexai-lsp [40m[90mlsp llm stats reqs=1 avg_sent=549 avg_recv=126 sent_total=549 recv_total=126 rpm=14.55 sent_per_min=7987 recv_per_min=1833[0m diff --git a/internal/platforms/linkedin/linkedin.go b/internal/platforms/linkedin/linkedin.go index 11291fb..16688d5 100644 --- a/internal/platforms/linkedin/linkedin.go +++ b/internal/platforms/linkedin/linkedin.go @@ -27,9 +27,10 @@ func addCommonHeaders(req *http.Request, accessToken, liVersion string) { req.Header.Set("Authorization", "Bearer "+accessToken) req.Header.Set("Content-Type", "application/json") req.Header.Set("X-RestLi-Protocol-Version", "2.0.0") - if liVersion != "" { - req.Header.Set("LinkedIn-Version", liVersion) + if liVersion == "" { + liVersion = "202601" // Default to latest stable version } + req.Header.Set("LinkedIn-Version", liVersion) } func Post(ctx context.Context, args config.Args, sizeLimit int, en entry.Entry) error { @@ -147,7 +148,7 @@ func postMessageToLinkedInAPI(ctx context.Context, personID, accessToken, conten } else if resp.StatusCode == http.StatusUpgradeRequired { // 426 often indicates a non-active LinkedIn-Version header. // Provide a clear hint to configure a valid version. - err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202502) or remove to use default. Response: %s", err, string(body)) + err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202601) or remove to use default. Response: %s", err, string(body)) } } return err @@ -191,6 +192,24 @@ func initializeImageUpload(ctx context.Context, personURN, accessToken string, l } defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", fmt.Errorf("error reading response: %w", err) + } + + if resp.StatusCode != http.StatusOK { + err := fmt.Errorf("image upload initialization failed. Status: %s\n%s\n", + resp.Status, string(body)) + if resp.StatusCode == http.StatusUnauthorized { + err = errors.Join(err, errUnauthorized) + } else if resp.StatusCode == http.StatusUpgradeRequired { + // 426 often indicates a non-active LinkedIn-Version header. + // Provide a clear hint to configure a valid version. + err = fmt.Errorf("%w; LinkedIn API version likely inactive. Set an active 'LinkedInVersion' in config (e.g. 202601) or remove to use default. Response: %s", err, string(body)) + } + return "", "", err + } + type InitializeUploadResponse struct { Value struct { UploadURL string `json:"uploadUrl"` @@ -198,7 +217,7 @@ func initializeImageUpload(ctx context.Context, personURN, accessToken string, l } `json:"value"` } var response InitializeUploadResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + if err := json.Unmarshal(body, &response); err != nil { return "", "", fmt.Errorf("error decoding response: %w", err) } diff --git a/internal/platforms/linkedin/oauth2/oauth2.go b/internal/platforms/linkedin/oauth2/oauth2.go index 8fe25ab..45cb9b7 100644 --- a/internal/platforms/linkedin/oauth2/oauth2.go +++ b/internal/platforms/linkedin/oauth2/oauth2.go @@ -93,7 +93,13 @@ func oauthCallbackHandler(w http.ResponseWriter, r *http.Request) { func LinkedInCreds(ctx context.Context, args config.Args) (string, string, error) { conf := args.Config if conf.LinkedInAccessToken != "" && conf.LinkedInPersonID != "" { - return conf.LinkedInPersonID, conf.LinkedInAccessToken, nil + // Validate cached token before using it + token := &oauth2.Token{AccessToken: conf.LinkedInAccessToken} + if _, err := getOauthPersonID(token); err == nil { + return conf.LinkedInPersonID, conf.LinkedInAccessToken, nil + } + // Cached token is invalid, clear it to trigger re-auth + conf.LinkedInAccessToken = "" } oauthConfig = &oauth2.Config{ diff --git a/internal/platforms/linkedin/preview.go b/internal/platforms/linkedin/preview.go index a583c7a..125a4bf 100644 --- a/internal/platforms/linkedin/preview.go +++ b/internal/platforms/linkedin/preview.go @@ -73,6 +73,12 @@ func (p preview) Thumbnail() (string, bool) { } func (p preview) DownloadImage(destPath string) (string, error) { + // Skip data URIs - they can't be downloaded and don't provide meaningful images + if u, err := url.Parse(p.thumbnailURL); err == nil && u.Scheme == "data" { + colour.Infoln("Skipping data URI image, using article metadata instead") + return "", nil + } + if err := oi.EnsureDir(destPath); err != nil { return "", err } diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377 b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377 new file mode 100644 index 0000000..2eecaf6 --- /dev/null +++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/5ffd3a9d58497377 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("\n\n\n") diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983 b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983 new file mode 100644 index 0000000..ee3f339 --- /dev/null +++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/771e938e4458e983 @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0") diff --git a/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d new file mode 100644 index 0000000..190e0f4 --- /dev/null +++ b/internal/platforms/linkedin/testdata/fuzz/FuzzLinkedInURLExtract/b0118fa98fb2891d @@ -0,0 +1,2 @@ +go test fuzz v1 +string("0 0") diff --git a/internal/version.go b/internal/version.go index f54de26..3980280 100644 --- a/internal/version.go +++ b/internal/version.go @@ -6,7 +6,7 @@ import ( "codeberg.org/snonux/gos/internal/table" ) -const versionStr = "v1.2.3" +const versionStr = "v1.2.4" func printVersion() { table.New(). @@ -0,0 +1,10 @@ +#!/bin/bash + +# Pull the pre-built image +docker pull ghcr.io/zeeno-atl/claude-code:latest + +# Run with current directory +docker run -it --rm -v "$(pwd):/app" ghcr.io/zeeno-atl/claude-code:latest + +# Run with API key +docker run -it --rm -v "$(pwd):/app" -e ANTHROPIC_API_KEY="your_api_key" ghcr.io/zeeno-atl/claude-code:latest |
