ForageFacebook 2.0.0__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/dependabot.yml +10 -0
- foragefacebook-2.2.0/.github/workflows/ci.yml +60 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/workflows/publish.yml +28 -10
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.gitignore +5 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/AGENTS.md +10 -8
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CHANGELOG.md +36 -1
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CONTRIBUTING.md +6 -4
- foragefacebook-2.2.0/Makefile +14 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/PKG-INFO +69 -27
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/README.md +68 -25
- foragefacebook-2.2.0/docs/capabilities.md +37 -0
- foragefacebook-2.2.0/docs/research/2026-09-20-cli-portfolio-audit.md +869 -0
- foragefacebook-2.2.0/docs/research/2026-09-20-cli-portfolio-repairs.md +106 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/pyproject.toml +1 -2
- foragefacebook-2.2.0/scripts/live-e2e.py +57 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/scripts/release.sh +6 -2
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/__init__.py +1 -1
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/auth.py +13 -2
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/cli.py +144 -6
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/exporter.py +3 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/models.py +15 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/parser.py +118 -124
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/src/forage/scraper.py +145 -29
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/uv.lock +2 -110
- foragefacebook-2.0.0/.github/workflows/ci.yml +0 -91
- foragefacebook-2.0.0/tests/__init__.py +0 -1
- foragefacebook-2.0.0/tests/conftest.py +0 -176
- foragefacebook-2.0.0/tests/fixtures/comment_simple.html +0 -7
- foragefacebook-2.0.0/tests/fixtures/comment_with_replies.html +0 -23
- foragefacebook-2.0.0/tests/fixtures/post_long_content.html +0 -13
- foragefacebook-2.0.0/tests/fixtures/post_realistic.html +0 -54
- foragefacebook-2.0.0/tests/fixtures/post_simple.html +0 -13
- foragefacebook-2.0.0/tests/fixtures/post_sponsored.html +0 -7
- foragefacebook-2.0.0/tests/fixtures/post_with_emoji.html +0 -12
- foragefacebook-2.0.0/tests/fixtures/post_with_media.html +0 -15
- foragefacebook-2.0.0/tests/fixtures/post_with_see_more.html +0 -12
- foragefacebook-2.0.0/tests/test_auth.py +0 -48
- foragefacebook-2.0.0/tests/test_cli.py +0 -161
- foragefacebook-2.0.0/tests/test_exporter.py +0 -669
- foragefacebook-2.0.0/tests/test_marketplace.py +0 -52
- foragefacebook-2.0.0/tests/test_models.py +0 -158
- foragefacebook-2.0.0/tests/test_parser.py +0 -362
- foragefacebook-2.0.0/tests/test_scraper.py +0 -460
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/.github/FUNDING.yml +0 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/CLAUDE.md +0 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/LICENSE +0 -0
- {foragefacebook-2.0.0 → foragefacebook-2.2.0}/SECURITY.md +0 -0
|
@@ -4,6 +4,11 @@ updates:
|
|
|
4
4
|
directory: "/"
|
|
5
5
|
schedule:
|
|
6
6
|
interval: "weekly"
|
|
7
|
+
cooldown:
|
|
8
|
+
default-days: 7
|
|
9
|
+
groups:
|
|
10
|
+
dependencies:
|
|
11
|
+
patterns: ["*"]
|
|
7
12
|
open-pull-requests-limit: 5
|
|
8
13
|
labels:
|
|
9
14
|
- "dependencies"
|
|
@@ -12,6 +17,11 @@ updates:
|
|
|
12
17
|
directory: "/"
|
|
13
18
|
schedule:
|
|
14
19
|
interval: "weekly"
|
|
20
|
+
cooldown:
|
|
21
|
+
default-days: 7
|
|
22
|
+
groups:
|
|
23
|
+
dependencies:
|
|
24
|
+
patterns: ["*"]
|
|
15
25
|
open-pull-requests-limit: 5
|
|
16
26
|
labels:
|
|
17
27
|
- "dependencies"
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: CI
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
push:
|
|
6
|
+
branches: [master, main]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: [master, main]
|
|
9
|
+
workflow_call:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: ${{ github.ref_type != 'tag' }}
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
quality:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 15
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
|
+
with:
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
27
|
+
with:
|
|
28
|
+
version: "0.12.17"
|
|
29
|
+
python-version: "3.14"
|
|
30
|
+
- run: uv sync --locked --all-extras --no-config
|
|
31
|
+
- name: Check source and lockfile
|
|
32
|
+
run: |
|
|
33
|
+
uv lock --check --no-config
|
|
34
|
+
uv run --locked --no-config ruff check src/ scripts/
|
|
35
|
+
uv run --locked --no-config ruff format --check src/ scripts/
|
|
36
|
+
uv run --locked --no-config ty check src/forage/
|
|
37
|
+
- name: Audit workflows and release helper
|
|
38
|
+
run: |
|
|
39
|
+
go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
|
|
40
|
+
uvx --no-config zizmor==1.30.1 --no-progress .github/workflows
|
|
41
|
+
shellcheck scripts/release.sh
|
|
42
|
+
go run mvdan.cc/sh/v3/cmd/shfmt@v3.14.1 -i 2 -d scripts/release.sh
|
|
43
|
+
- name: Build and install wheel
|
|
44
|
+
run: |
|
|
45
|
+
uv build --no-config
|
|
46
|
+
uv venv /tmp/forage-install-smoke
|
|
47
|
+
uv pip install --no-config --python /tmp/forage-install-smoke/bin/python dist/*.whl
|
|
48
|
+
/tmp/forage-install-smoke/bin/forage --version
|
|
49
|
+
/tmp/forage-install-smoke/bin/forage --help
|
|
50
|
+
|
|
51
|
+
check:
|
|
52
|
+
if: always()
|
|
53
|
+
needs: [quality]
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
timeout-minutes: 2
|
|
56
|
+
steps:
|
|
57
|
+
- name: Require all checks
|
|
58
|
+
env:
|
|
59
|
+
QUALITY_RESULT: ${{ needs.quality.result }}
|
|
60
|
+
run: test "$QUALITY_RESULT" = success
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
---
|
|
1
2
|
name: Publish to PyPI
|
|
2
3
|
|
|
3
4
|
on:
|
|
@@ -13,34 +14,50 @@ on:
|
|
|
13
14
|
permissions:
|
|
14
15
|
contents: read
|
|
15
16
|
|
|
17
|
+
concurrency:
|
|
18
|
+
group: publish-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: false
|
|
20
|
+
|
|
16
21
|
jobs:
|
|
22
|
+
checks:
|
|
23
|
+
# actionlint 1.7.12 does not yet parse the new $/ syntax. This resolves the same commit.
|
|
24
|
+
uses: ./.github/workflows/ci.yml # zizmor: ignore[self-repository]
|
|
17
25
|
build:
|
|
26
|
+
needs: checks
|
|
27
|
+
timeout-minutes: 10
|
|
18
28
|
runs-on: ubuntu-latest
|
|
19
29
|
|
|
20
30
|
steps:
|
|
21
|
-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
|
|
31
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
22
32
|
with:
|
|
23
33
|
persist-credentials: false
|
|
24
34
|
|
|
25
35
|
- name: Install uv
|
|
26
|
-
uses: astral-sh/setup-uv@
|
|
36
|
+
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
|
|
27
37
|
with:
|
|
28
38
|
enable-cache: false
|
|
39
|
+
version: "0.12.17"
|
|
40
|
+
python-version: "3.14"
|
|
29
41
|
|
|
30
|
-
- name:
|
|
31
|
-
|
|
42
|
+
- name: Verify release version
|
|
43
|
+
if: github.event_name == 'release'
|
|
44
|
+
env:
|
|
45
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
46
|
+
run: |
|
|
47
|
+
uv run --no-project --no-config python -c 'import os, tomllib; from pathlib import Path; version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]; assert os.environ["RELEASE_TAG"] == "v" + version, "tag/package version mismatch"'
|
|
32
48
|
|
|
33
49
|
- name: Build package
|
|
34
|
-
run: uv build
|
|
50
|
+
run: uv build --no-config
|
|
35
51
|
|
|
36
52
|
- name: Upload artifacts
|
|
37
|
-
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
53
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
38
54
|
with:
|
|
39
55
|
name: dist
|
|
40
56
|
path: dist/
|
|
41
57
|
|
|
42
58
|
publish-test-pypi:
|
|
43
59
|
needs: build
|
|
60
|
+
timeout-minutes: 10
|
|
44
61
|
runs-on: ubuntu-latest
|
|
45
62
|
if: github.event_name == 'workflow_dispatch' && github.event.inputs.test_pypi == 'true'
|
|
46
63
|
|
|
@@ -53,18 +70,19 @@ jobs:
|
|
|
53
70
|
|
|
54
71
|
steps:
|
|
55
72
|
- name: Download artifacts
|
|
56
|
-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
|
73
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
57
74
|
with:
|
|
58
75
|
name: dist
|
|
59
76
|
path: dist/
|
|
60
77
|
|
|
61
78
|
- name: Publish to Test PyPI
|
|
62
|
-
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
|
79
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
63
80
|
with:
|
|
64
81
|
repository-url: https://test.pypi.org/legacy/
|
|
65
82
|
|
|
66
83
|
publish-pypi:
|
|
67
84
|
needs: build
|
|
85
|
+
timeout-minutes: 10
|
|
68
86
|
runs-on: ubuntu-latest
|
|
69
87
|
if: github.event_name == 'release'
|
|
70
88
|
|
|
@@ -77,10 +95,10 @@ jobs:
|
|
|
77
95
|
|
|
78
96
|
steps:
|
|
79
97
|
- name: Download artifacts
|
|
80
|
-
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
|
98
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
81
99
|
with:
|
|
82
100
|
name: dist
|
|
83
101
|
path: dist/
|
|
84
102
|
|
|
85
103
|
- name: Publish to PyPI
|
|
86
|
-
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33
|
|
104
|
+
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
@@ -12,11 +12,10 @@ Forage is a CLI tool for scraping private Facebook groups to analyze community d
|
|
|
12
12
|
uv sync # Install deps (always use uv, not pip/python)
|
|
13
13
|
uv run forage --help # Run CLI
|
|
14
14
|
uv run ty check src/ # Type check (Astral's ty)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
uv run
|
|
18
|
-
uv run ruff
|
|
19
|
-
uv run ruff format src/ tests/ # Auto-format (CI enforces this)
|
|
15
|
+
make check # Lint, type check, and build
|
|
16
|
+
make test # Read-only live Facebook checks
|
|
17
|
+
uv run ruff check src/ scripts/ # Lint
|
|
18
|
+
uv run ruff format src/ scripts/ # Format
|
|
20
19
|
```
|
|
21
20
|
|
|
22
21
|
## Manual Testing
|
|
@@ -59,7 +58,10 @@ Random viewport sizes, `human_delay()` with variance, real Chromium browser, ses
|
|
|
59
58
|
|
|
60
59
|
### Test Structure
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
Use `make test` for real CLI reads through the saved Facebook session. Install Chromium first.
|
|
62
|
+
Set `FORAGE_E2E_GROUP` to test a group; otherwise the test searches Marketplace for computers.
|
|
63
|
+
Keep unit tests, mock tests, and fake-server suites out of this repo. CI runs static checks and package builds.
|
|
64
|
+
Live tests must not log in, change credentials, or write to Facebook.
|
|
63
65
|
|
|
64
66
|
## Fragile Code — Check Here First When Things Break
|
|
65
67
|
|
|
@@ -99,9 +101,9 @@ Steps:
|
|
|
99
101
|
1. Update `version` in both `pyproject.toml` and `src/forage/__init__.py`
|
|
100
102
|
2. Run `uv lock --no-config` — refresh locked editable package metadata without leaking user-level uv settings
|
|
101
103
|
3. Update `CHANGELOG.md` — add version section under `## [Unreleased]`, update comparison links at bottom
|
|
102
|
-
4. Run `uv lock --check --no-config` and `uv run ruff format src/
|
|
104
|
+
4. Run `uv lock --check --no-config` and `uv run ruff format src/ scripts/` — CI enforces formatting
|
|
103
105
|
5. Commit: `git commit -m "chore: release vX.Y.Z"`
|
|
104
|
-
6. Push
|
|
106
|
+
6. Push a feature branch and open a PR. Merge only after all required checks pass. Never push directly to `master`.
|
|
105
107
|
7. Tag: `git tag -a vX.Y.Z -m "Release vX.Y.Z"` then `git push origin vX.Y.Z`
|
|
106
108
|
8. Create GitHub release: `gh release create vX.Y.Z --title "vX.Y.Z" --notes "..."`
|
|
107
109
|
— This triggers `.github/workflows/publish.yml` which publishes to PyPI via OIDC (no tokens)
|
|
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.0] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Replace unit and synthetic browser tests with read-only live CLI checks.
|
|
15
|
+
- Keep lint, type checks, and package build/install checks in CI.
|
|
16
|
+
- Remove pytest and the Node test harness.
|
|
17
|
+
- Add common `make check`, `make test`, and `make build` entry points.
|
|
18
|
+
- Add `forage version` beside the existing `--version` flag.
|
|
19
|
+
|
|
20
|
+
## [2.1.0] - 2026-09-20
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Preserve native comment IDs and scope fallback IDs to their post to prevent SQLite collisions.
|
|
25
|
+
- Preserve full visible post text, expand supported “See more” controls, and exclude nested comments.
|
|
26
|
+
- Parse compact comment counts and retain query-based post permalinks.
|
|
27
|
+
- Include the full `--until` day, reject invalid date ranges before login, and preserve “Yesterday” clock times.
|
|
28
|
+
- Report missing feeds and Marketplace authentication failures instead of misleading empty results or timeouts.
|
|
29
|
+
- Bind Marketplace coordinates to the requested listing ID; exclude unbound locations.
|
|
30
|
+
- Skip full parsing for group posts whose reliable permalink ID is already present.
|
|
31
|
+
- Count accepted Marketplace listings toward `--limit`; keep a separate `--max-candidates` cost bound.
|
|
32
|
+
- Store session directories and authentication state with owner-only POSIX permissions.
|
|
33
|
+
- Reject missing CSV/SQLite output arguments before browser work.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- JSON collection diagnostics and explicit content/comment completeness indicators.
|
|
38
|
+
- `forage doctor` for local setup checks without Facebook access.
|
|
39
|
+
- `forage export` to reuse existing exporters with saved group JSON.
|
|
40
|
+
- Network-blocked Chromium regression tests and a documented capability contract.
|
|
41
|
+
|
|
42
|
+
|
|
10
43
|
## [2.0.0] - 2026-08-29
|
|
11
44
|
|
|
12
45
|
### Added
|
|
@@ -214,7 +247,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
214
247
|
- Sensitive files excluded via `.gitignore`
|
|
215
248
|
- Security guidelines in SECURITY.md
|
|
216
249
|
|
|
217
|
-
[Unreleased]: https://github.com/jwmoss/forage/compare/v2.
|
|
250
|
+
[Unreleased]: https://github.com/jwmoss/forage/compare/v2.2.0...HEAD
|
|
251
|
+
[2.2.0]: https://github.com/jwmoss/forage/compare/v2.1.0...v2.2.0
|
|
252
|
+
[2.1.0]: https://github.com/jwmoss/forage/compare/v2.0.0...v2.1.0
|
|
218
253
|
[2.0.0]: https://github.com/jwmoss/forage/compare/v1.2.1...v2.0.0
|
|
219
254
|
[1.2.1]: https://github.com/jwmoss/forage/compare/v1.2.0...v1.2.1
|
|
220
255
|
[1.2.0]: https://github.com/jwmoss/forage/compare/v1.1.0...v1.2.0
|
|
@@ -22,7 +22,8 @@ Thank you for your interest in contributing to Forage!
|
|
|
22
22
|
|
|
23
23
|
4. Run tests:
|
|
24
24
|
```bash
|
|
25
|
-
|
|
25
|
+
make check
|
|
26
|
+
make test
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
5. Run type checker:
|
|
@@ -55,15 +56,16 @@ Example: `feat: add SQLite export option`
|
|
|
55
56
|
1. Fork the repository
|
|
56
57
|
2. Create a feature branch (`git checkout -b feat/my-feature`)
|
|
57
58
|
3. Make your changes
|
|
58
|
-
4. Ensure tests pass (`
|
|
59
|
+
4. Ensure tests pass (`make check`; run `make test` with a saved session)
|
|
59
60
|
5. Ensure type checking passes (`uv run ty check src/forage/`)
|
|
60
61
|
6. Commit with a descriptive message
|
|
61
62
|
7. Push and open a pull request
|
|
62
63
|
|
|
63
64
|
## Testing
|
|
64
65
|
|
|
65
|
-
-
|
|
66
|
-
-
|
|
66
|
+
- Verify behavior through the real CLI against Facebook.
|
|
67
|
+
- Keep live checks read-only and use the existing session.
|
|
68
|
+
- Do not add unit tests, mock tests, or fake-server suites.
|
|
67
69
|
- Mock external dependencies (Playwright, network)
|
|
68
70
|
- Aim for good coverage of edge cases
|
|
69
71
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.PHONY: check test build
|
|
2
|
+
|
|
3
|
+
check:
|
|
4
|
+
uv lock --check --no-config
|
|
5
|
+
uv run --locked --no-config ruff check src/ scripts/
|
|
6
|
+
uv run --locked --no-config ruff format --check src/ scripts/
|
|
7
|
+
uv run --locked --no-config ty check src/forage/
|
|
8
|
+
uv build --no-config
|
|
9
|
+
|
|
10
|
+
test:
|
|
11
|
+
uv run --locked --no-config python scripts/live-e2e.py
|
|
12
|
+
|
|
13
|
+
build:
|
|
14
|
+
uv build --no-config
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ForageFacebook
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: CLI tool to scrape posts, comments, and reactions from private Facebook groups
|
|
5
5
|
Project-URL: Homepage, https://github.com/jwmoss/forage
|
|
6
6
|
Project-URL: Repository, https://github.com/jwmoss/forage
|
|
@@ -28,7 +28,6 @@ Requires-Dist: playwright>=1.40
|
|
|
28
28
|
Requires-Dist: pydantic>=2.0
|
|
29
29
|
Requires-Dist: rich>=13.0
|
|
30
30
|
Provides-Extra: dev
|
|
31
|
-
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
32
31
|
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
33
32
|
Requires-Dist: ty>=0.0.1a6; extra == 'dev'
|
|
34
33
|
Description-Content-Type: text/markdown
|
|
@@ -38,7 +37,9 @@ Description-Content-Type: text/markdown
|
|
|
38
37
|
[](https://github.com/jwmoss/forage/actions/workflows/ci.yml)
|
|
39
38
|
[](LICENSE)
|
|
40
39
|
|
|
41
|
-
CLI tool to
|
|
40
|
+
CLI tool to collect Facebook group posts and search Marketplace electronics through browser automation.
|
|
41
|
+
|
|
42
|
+
Forage uses browser pages, not a public REST API. Results are partial observations. See the [capability contract](docs/capabilities.md).
|
|
42
43
|
|
|
43
44
|
## Table of Contents
|
|
44
45
|
|
|
@@ -124,7 +125,7 @@ uv run forage scrape your-group-slug --min-reactions 5
|
|
|
124
125
|
uv run forage scrape your-group-slug --top-comments 10
|
|
125
126
|
|
|
126
127
|
# Watch the browser (debugging)
|
|
127
|
-
uv run forage scrape your-group-slug --no-headless
|
|
128
|
+
uv run forage -v scrape your-group-slug --no-headless
|
|
128
129
|
|
|
129
130
|
# Slower scraping to avoid rate limits
|
|
130
131
|
uv run forage scrape your-group-slug --delay 5.0
|
|
@@ -148,6 +149,9 @@ Global Flags:
|
|
|
148
149
|
Commands:
|
|
149
150
|
login Open browser for interactive Facebook login
|
|
150
151
|
scrape Scrape posts from a Facebook group
|
|
152
|
+
marketplace Search Marketplace electronics
|
|
153
|
+
doctor Check local browser and session setup
|
|
154
|
+
export Convert saved group JSON without Facebook access
|
|
151
155
|
```
|
|
152
156
|
|
|
153
157
|
#### scrape flags
|
|
@@ -156,7 +160,7 @@ Commands:
|
|
|
156
160
|
|------|---------|-------------|
|
|
157
161
|
| `--days` | `7` | Posts from last N days |
|
|
158
162
|
| `--since` | - | Start date (ISO 8601: YYYY-MM-DD) |
|
|
159
|
-
| `--until` | - |
|
|
163
|
+
| `--until` | - | Inclusive end date (YYYY-MM-DD), in the local timezone |
|
|
160
164
|
| `--limit` | `0` | Max posts (0 = unlimited) |
|
|
161
165
|
| `--delay` | `2.0` | Seconds between page loads |
|
|
162
166
|
| `--min-reactions` | `0` | Min reactions for comments |
|
|
@@ -169,6 +173,46 @@ Commands:
|
|
|
169
173
|
| `--no-headless` | `false` | Show browser window |
|
|
170
174
|
| `--browser` | `chromium` | Browser: chromium, firefox, webkit |
|
|
171
175
|
|
|
176
|
+
### Marketplace search
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
uv run forage marketplace "rtx 4070" --city wilmington --radius 40 --limit 20 -o listings.json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`--limit` counts accepted listings. `--max-candidates` bounds the search cost, including rejected candidates; its default is 200.
|
|
183
|
+
Forage excludes distant listings and listings whose coordinates it cannot verify. City is the search center, not a city-only filter.
|
|
184
|
+
The current parser supports electronics with dollar prices or “Free” labels. Search relevance follows Facebook's results.
|
|
185
|
+
|
|
186
|
+
### Local diagnosis and saved exports
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# Check browser installation and session permissions without contacting Facebook
|
|
190
|
+
uv run forage doctor
|
|
191
|
+
|
|
192
|
+
# Convert an existing group result without another scrape or login
|
|
193
|
+
uv run forage export posts.json --format sqlite --output posts.db
|
|
194
|
+
uv run forage export posts.json --format csv --output posts.csv
|
|
195
|
+
uv run forage export posts.json --format llm --output compact.json
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`doctor` checks local files. It does not prove that a session remains valid. Install the selected browser before login:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
uv run playwright install chromium
|
|
202
|
+
# For --browser firefox or webkit, install that browser instead.
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
JSON and LLM JSON include collection diagnostics: stop reason, candidate count, rejected count, parse failures, and unknown timestamps.
|
|
206
|
+
`partial: true` means the scraper cannot prove exhaustive coverage. A verified empty-page message produces `stop_reason: "empty"`.
|
|
207
|
+
`content_truncated` identifies an unexpanded post. `comments_complete: false` means comment coverage is unverified.
|
|
208
|
+
Keep the original JSON if you need these diagnostics beside CSV or SQLite output.
|
|
209
|
+
|
|
210
|
+
Dates use the local system and browser timezone. `--until` includes the entire selected day. Reversed dates fail before login.
|
|
211
|
+
The scraper retains posts with unknown timestamps and reports their count.
|
|
212
|
+
|
|
213
|
+
Exit codes: `0` for a completed command, `1` for collection or output errors, `2` for invalid arguments,
|
|
214
|
+
`3` for required authentication, and `4` for a recognized unavailable group. Empty results alone do not prove an empty group.
|
|
215
|
+
|
|
172
216
|
### SQLite Export
|
|
173
217
|
|
|
174
218
|
Export directly to SQLite for easier analysis:
|
|
@@ -274,10 +318,23 @@ uv sync --extra dev
|
|
|
274
318
|
# Run type checker
|
|
275
319
|
uv run ty check src/
|
|
276
320
|
|
|
277
|
-
# Run
|
|
278
|
-
|
|
321
|
+
# Run static checks and build the package
|
|
322
|
+
make check
|
|
323
|
+
|
|
324
|
+
# Install Chromium for live reads
|
|
325
|
+
uv run playwright install chromium
|
|
326
|
+
|
|
327
|
+
# Read Marketplace through the saved Facebook session
|
|
328
|
+
make test
|
|
329
|
+
|
|
330
|
+
# Read one post from a group you can access
|
|
331
|
+
FORAGE_E2E_GROUP=your-group-slug make test
|
|
279
332
|
```
|
|
280
333
|
|
|
334
|
+
Live tests run the real CLI and Facebook pages. They do not use fixtures or mock servers.
|
|
335
|
+
They need an existing login session. CI runs static checks and package build/install checks.
|
|
336
|
+
|
|
337
|
+
|
|
281
338
|
## Architecture
|
|
282
339
|
|
|
283
340
|
```
|
|
@@ -295,29 +352,14 @@ src/forage/
|
|
|
295
352
|
- Facebook's HTML structure changes frequently
|
|
296
353
|
- Rate limiting may require slower scraping
|
|
297
354
|
- Individual reaction types not broken out (only total)
|
|
298
|
-
- Session
|
|
355
|
+
- Session lifetime depends on Facebook; use `forage login` when authentication fails
|
|
356
|
+
- English page labels define the supported parser scope
|
|
357
|
+
- Previously overwritten SQLite comments require a new scrape; upgrades cannot recover them
|
|
299
358
|
|
|
300
359
|
## Roadmap
|
|
301
360
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
### High Priority
|
|
305
|
-
- [ ] **Cookie import** - Import cookies from browser extensions (EditThisCookie, Netscape format)
|
|
306
|
-
- [ ] **Incremental scraping** - Only fetch posts newer than last scrape
|
|
307
|
-
- [ ] **Progress persistence** - Resume interrupted scrapes
|
|
308
|
-
|
|
309
|
-
### Medium Priority
|
|
310
|
-
- [ ] **Multiple groups** - Scrape multiple groups in one command
|
|
311
|
-
- [ ] **Media extraction** - Download images/videos from posts
|
|
312
|
-
- [ ] **Reaction breakdown** - Extract individual reaction types (like, love, etc.)
|
|
313
|
-
- [ ] **Author statistics** - Aggregate stats per author
|
|
314
|
-
- [ ] **Scheduled scraping** - Cron-friendly mode with locking
|
|
315
|
-
|
|
316
|
-
### Nice to Have
|
|
317
|
-
- [ ] **Web UI** - Local web interface for browsing scraped data
|
|
318
|
-
- [ ] **Webhook notifications** - Notify on new posts matching criteria
|
|
319
|
-
- [ ] **Public group support** - Scrape without login for public groups
|
|
320
|
-
- [ ] **Parallel scraping** - Speed up multi-group scrapes
|
|
361
|
+
Prioritize sanitized DOM fixtures and verified selector updates. Add incremental collection only after identity and completeness checks support it.
|
|
362
|
+
Use shell loops for batches, system scheduling for recurring commands, and SQLite or jq for analysis.
|
|
321
363
|
|
|
322
364
|
### Contributing
|
|
323
365
|
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
[](https://github.com/jwmoss/forage/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
CLI tool to
|
|
6
|
+
CLI tool to collect Facebook group posts and search Marketplace electronics through browser automation.
|
|
7
|
+
|
|
8
|
+
Forage uses browser pages, not a public REST API. Results are partial observations. See the [capability contract](docs/capabilities.md).
|
|
7
9
|
|
|
8
10
|
## Table of Contents
|
|
9
11
|
|
|
@@ -89,7 +91,7 @@ uv run forage scrape your-group-slug --min-reactions 5
|
|
|
89
91
|
uv run forage scrape your-group-slug --top-comments 10
|
|
90
92
|
|
|
91
93
|
# Watch the browser (debugging)
|
|
92
|
-
uv run forage scrape your-group-slug --no-headless
|
|
94
|
+
uv run forage -v scrape your-group-slug --no-headless
|
|
93
95
|
|
|
94
96
|
# Slower scraping to avoid rate limits
|
|
95
97
|
uv run forage scrape your-group-slug --delay 5.0
|
|
@@ -113,6 +115,9 @@ Global Flags:
|
|
|
113
115
|
Commands:
|
|
114
116
|
login Open browser for interactive Facebook login
|
|
115
117
|
scrape Scrape posts from a Facebook group
|
|
118
|
+
marketplace Search Marketplace electronics
|
|
119
|
+
doctor Check local browser and session setup
|
|
120
|
+
export Convert saved group JSON without Facebook access
|
|
116
121
|
```
|
|
117
122
|
|
|
118
123
|
#### scrape flags
|
|
@@ -121,7 +126,7 @@ Commands:
|
|
|
121
126
|
|------|---------|-------------|
|
|
122
127
|
| `--days` | `7` | Posts from last N days |
|
|
123
128
|
| `--since` | - | Start date (ISO 8601: YYYY-MM-DD) |
|
|
124
|
-
| `--until` | - |
|
|
129
|
+
| `--until` | - | Inclusive end date (YYYY-MM-DD), in the local timezone |
|
|
125
130
|
| `--limit` | `0` | Max posts (0 = unlimited) |
|
|
126
131
|
| `--delay` | `2.0` | Seconds between page loads |
|
|
127
132
|
| `--min-reactions` | `0` | Min reactions for comments |
|
|
@@ -134,6 +139,46 @@ Commands:
|
|
|
134
139
|
| `--no-headless` | `false` | Show browser window |
|
|
135
140
|
| `--browser` | `chromium` | Browser: chromium, firefox, webkit |
|
|
136
141
|
|
|
142
|
+
### Marketplace search
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run forage marketplace "rtx 4070" --city wilmington --radius 40 --limit 20 -o listings.json
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`--limit` counts accepted listings. `--max-candidates` bounds the search cost, including rejected candidates; its default is 200.
|
|
149
|
+
Forage excludes distant listings and listings whose coordinates it cannot verify. City is the search center, not a city-only filter.
|
|
150
|
+
The current parser supports electronics with dollar prices or “Free” labels. Search relevance follows Facebook's results.
|
|
151
|
+
|
|
152
|
+
### Local diagnosis and saved exports
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Check browser installation and session permissions without contacting Facebook
|
|
156
|
+
uv run forage doctor
|
|
157
|
+
|
|
158
|
+
# Convert an existing group result without another scrape or login
|
|
159
|
+
uv run forage export posts.json --format sqlite --output posts.db
|
|
160
|
+
uv run forage export posts.json --format csv --output posts.csv
|
|
161
|
+
uv run forage export posts.json --format llm --output compact.json
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
`doctor` checks local files. It does not prove that a session remains valid. Install the selected browser before login:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
uv run playwright install chromium
|
|
168
|
+
# For --browser firefox or webkit, install that browser instead.
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
JSON and LLM JSON include collection diagnostics: stop reason, candidate count, rejected count, parse failures, and unknown timestamps.
|
|
172
|
+
`partial: true` means the scraper cannot prove exhaustive coverage. A verified empty-page message produces `stop_reason: "empty"`.
|
|
173
|
+
`content_truncated` identifies an unexpanded post. `comments_complete: false` means comment coverage is unverified.
|
|
174
|
+
Keep the original JSON if you need these diagnostics beside CSV or SQLite output.
|
|
175
|
+
|
|
176
|
+
Dates use the local system and browser timezone. `--until` includes the entire selected day. Reversed dates fail before login.
|
|
177
|
+
The scraper retains posts with unknown timestamps and reports their count.
|
|
178
|
+
|
|
179
|
+
Exit codes: `0` for a completed command, `1` for collection or output errors, `2` for invalid arguments,
|
|
180
|
+
`3` for required authentication, and `4` for a recognized unavailable group. Empty results alone do not prove an empty group.
|
|
181
|
+
|
|
137
182
|
### SQLite Export
|
|
138
183
|
|
|
139
184
|
Export directly to SQLite for easier analysis:
|
|
@@ -239,10 +284,23 @@ uv sync --extra dev
|
|
|
239
284
|
# Run type checker
|
|
240
285
|
uv run ty check src/
|
|
241
286
|
|
|
242
|
-
# Run
|
|
243
|
-
|
|
287
|
+
# Run static checks and build the package
|
|
288
|
+
make check
|
|
289
|
+
|
|
290
|
+
# Install Chromium for live reads
|
|
291
|
+
uv run playwright install chromium
|
|
292
|
+
|
|
293
|
+
# Read Marketplace through the saved Facebook session
|
|
294
|
+
make test
|
|
295
|
+
|
|
296
|
+
# Read one post from a group you can access
|
|
297
|
+
FORAGE_E2E_GROUP=your-group-slug make test
|
|
244
298
|
```
|
|
245
299
|
|
|
300
|
+
Live tests run the real CLI and Facebook pages. They do not use fixtures or mock servers.
|
|
301
|
+
They need an existing login session. CI runs static checks and package build/install checks.
|
|
302
|
+
|
|
303
|
+
|
|
246
304
|
## Architecture
|
|
247
305
|
|
|
248
306
|
```
|
|
@@ -260,29 +318,14 @@ src/forage/
|
|
|
260
318
|
- Facebook's HTML structure changes frequently
|
|
261
319
|
- Rate limiting may require slower scraping
|
|
262
320
|
- Individual reaction types not broken out (only total)
|
|
263
|
-
- Session
|
|
321
|
+
- Session lifetime depends on Facebook; use `forage login` when authentication fails
|
|
322
|
+
- English page labels define the supported parser scope
|
|
323
|
+
- Previously overwritten SQLite comments require a new scrape; upgrades cannot recover them
|
|
264
324
|
|
|
265
325
|
## Roadmap
|
|
266
326
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
### High Priority
|
|
270
|
-
- [ ] **Cookie import** - Import cookies from browser extensions (EditThisCookie, Netscape format)
|
|
271
|
-
- [ ] **Incremental scraping** - Only fetch posts newer than last scrape
|
|
272
|
-
- [ ] **Progress persistence** - Resume interrupted scrapes
|
|
273
|
-
|
|
274
|
-
### Medium Priority
|
|
275
|
-
- [ ] **Multiple groups** - Scrape multiple groups in one command
|
|
276
|
-
- [ ] **Media extraction** - Download images/videos from posts
|
|
277
|
-
- [ ] **Reaction breakdown** - Extract individual reaction types (like, love, etc.)
|
|
278
|
-
- [ ] **Author statistics** - Aggregate stats per author
|
|
279
|
-
- [ ] **Scheduled scraping** - Cron-friendly mode with locking
|
|
280
|
-
|
|
281
|
-
### Nice to Have
|
|
282
|
-
- [ ] **Web UI** - Local web interface for browsing scraped data
|
|
283
|
-
- [ ] **Webhook notifications** - Notify on new posts matching criteria
|
|
284
|
-
- [ ] **Public group support** - Scrape without login for public groups
|
|
285
|
-
- [ ] **Parallel scraping** - Speed up multi-group scrapes
|
|
327
|
+
Prioritize sanitized DOM fixtures and verified selector updates. Add incremental collection only after identity and completeness checks support it.
|
|
328
|
+
Use shell loops for batches, system scheduling for recurring commands, and SQLite or jq for analysis.
|
|
286
329
|
|
|
287
330
|
### Contributing
|
|
288
331
|
|