atpcli 0.1.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.
- atpcli-0.1.0/.gitignore +14 -0
- atpcli-0.1.0/.python-version +1 -0
- atpcli-0.1.0/CONTRIBUTING.md +95 -0
- atpcli-0.1.0/LICENSE +21 -0
- atpcli-0.1.0/Makefile +37 -0
- atpcli-0.1.0/PKG-INFO +132 -0
- atpcli-0.1.0/README.md +117 -0
- atpcli-0.1.0/atpcli/__init__.py +3 -0
- atpcli-0.1.0/atpcli/cli.py +135 -0
- atpcli-0.1.0/atpcli/config.py +42 -0
- atpcli-0.1.0/atpcli/display.py +111 -0
- atpcli-0.1.0/docs/getting-started.md +154 -0
- atpcli-0.1.0/docs/index.md +42 -0
- atpcli-0.1.0/docs/install.md +105 -0
- atpcli-0.1.0/docs/usage-login.md +173 -0
- atpcli-0.1.0/docs/usage-timeline.md +238 -0
- atpcli-0.1.0/mkdocs.yml +47 -0
- atpcli-0.1.0/pyproject.toml +66 -0
- atpcli-0.1.0/tests/__init__.py +0 -0
- atpcli-0.1.0/tests/test_cli.py +184 -0
- atpcli-0.1.0/tests/test_config.py +59 -0
- atpcli-0.1.0/tests/test_display.py +145 -0
- atpcli-0.1.0/uv.lock +1421 -0
atpcli-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.10
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
First things first: thank you for contributing! This project will be successful thanks to everyone who contributes, and we're happy to have you.
|
|
4
|
+
|
|
5
|
+
## Bug or issue?
|
|
6
|
+
|
|
7
|
+
To raise a bug or issue please use [our GitHub](https://github.com/phalt/atpcli/issues).
|
|
8
|
+
|
|
9
|
+
Please check the issue has not been raised before by using the search feature.
|
|
10
|
+
|
|
11
|
+
When submitting an issue or bug, please make sure you provide thorough detail on:
|
|
12
|
+
|
|
13
|
+
1. The version of atpcli you are using
|
|
14
|
+
2. Any errors or outputs you see in your terminal
|
|
15
|
+
3. Steps to reproduce the issue
|
|
16
|
+
|
|
17
|
+
## Contribution
|
|
18
|
+
|
|
19
|
+
If you want to directly contribute you can do so in two ways:
|
|
20
|
+
|
|
21
|
+
1. Documentation
|
|
22
|
+
2. Code
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
|
|
26
|
+
Fixing grammar, spelling mistakes, or expanding the documentation to cover features that are not yet documented, are all valuable contributions.
|
|
27
|
+
|
|
28
|
+
### Code
|
|
29
|
+
|
|
30
|
+
Contribution by writing code for new features, or fixing bugs, is a great way to contribute to the project.
|
|
31
|
+
|
|
32
|
+
#### Set up
|
|
33
|
+
|
|
34
|
+
Clone the repo:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
git clone git@github.com:phalt/atpcli.git
|
|
38
|
+
cd atpcli
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Move to a feature branch:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
git branch -B my-branch-name
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Install UV (if not already installed):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
# On macOS and Linux:
|
|
51
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
52
|
+
|
|
53
|
+
# Or using pip:
|
|
54
|
+
pip install uv
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Install all the dependencies:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
make install
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This will use UV to create a virtual environment and install all dependencies. UV handles the virtual environment automatically, so you don't need to manually activate it.
|
|
64
|
+
|
|
65
|
+
To make sure you have things set up correctly, please run the tests:
|
|
66
|
+
|
|
67
|
+
```sh
|
|
68
|
+
make test
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Preparing changes for review
|
|
72
|
+
|
|
73
|
+
Once you've made changes, here's a good checklist to run through before publishing for review:
|
|
74
|
+
|
|
75
|
+
Run tests:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
make test
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Format and lint the code:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
make format
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Making a pull request
|
|
88
|
+
|
|
89
|
+
Please push your changes up to a feature branch and make a new [pull request](https://github.com/phalt/atpcli/compare) on GitHub.
|
|
90
|
+
|
|
91
|
+
Please add a description to the PR and some information about why the change is being made.
|
|
92
|
+
|
|
93
|
+
After a review, you might need to make more changes.
|
|
94
|
+
|
|
95
|
+
Once accepted, a core contributor will merge your changes!
|
atpcli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Paul Hallett
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
atpcli-0.1.0/Makefile
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
help:
|
|
2
|
+
@echo Developer commands for atpcli
|
|
3
|
+
@echo
|
|
4
|
+
@grep -E '^[ .a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
|
|
5
|
+
@echo
|
|
6
|
+
|
|
7
|
+
install: ## Install requirements ready for development
|
|
8
|
+
uv sync
|
|
9
|
+
|
|
10
|
+
format: ## Format the code correctly
|
|
11
|
+
uv run ruff format .
|
|
12
|
+
uv run ruff check --fix .
|
|
13
|
+
|
|
14
|
+
clean: ## Clear any cache files and test files
|
|
15
|
+
rm -rf .pytest_cache
|
|
16
|
+
rm -rf .ruff_cache
|
|
17
|
+
rm -rf dist/
|
|
18
|
+
rm -rf **/__pycache__
|
|
19
|
+
rm -rf **/*.pyc
|
|
20
|
+
rm -rf htmlcov/
|
|
21
|
+
rm -rf .coverage
|
|
22
|
+
rm -rf site/
|
|
23
|
+
|
|
24
|
+
test: ## Run tests
|
|
25
|
+
uv run pytest -vvv -x --cov=atpcli --cov-report=term-missing --cov-report=html --cov-config=pyproject.toml
|
|
26
|
+
|
|
27
|
+
docs-serve: ## Run a local documentation server
|
|
28
|
+
uv run mkdocs serve
|
|
29
|
+
|
|
30
|
+
docs-build: ## Build the documentation
|
|
31
|
+
uv run mkdocs build
|
|
32
|
+
|
|
33
|
+
shell: ## Run a Python shell
|
|
34
|
+
uv run python
|
|
35
|
+
|
|
36
|
+
release: ## Build a new version and release it
|
|
37
|
+
uv build && uv publish
|
atpcli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: atpcli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python CLI wrapper around the atproto package
|
|
5
|
+
Project-URL: Homepage, https://github.com/phalt/atpcli
|
|
6
|
+
Project-URL: Issues, https://github.com/phalt/atpcli/issues
|
|
7
|
+
Author-email: Paul Hallett <paulandrewhallett@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: atproto>=0.0.55
|
|
12
|
+
Requires-Dist: click>=8.1.7
|
|
13
|
+
Requires-Dist: rich>=13.7.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# atpcli
|
|
17
|
+
|
|
18
|
+
A Python CLI wrapper around the [atproto](https://github.com/MarshalX/atproto) package for interacting with Bluesky.
|
|
19
|
+
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
Full documentation is available at [docs/](docs/):
|
|
23
|
+
|
|
24
|
+
- [Installation Guide](docs/install.md)
|
|
25
|
+
- [Quick Start Guide](docs/getting-started.md) - Learn how to get app passwords and use atpcli
|
|
26
|
+
- [Login Command](docs/usage-login.md)
|
|
27
|
+
- [Timeline Command](docs/usage-timeline.md)
|
|
28
|
+
|
|
29
|
+
Or serve the docs locally:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
make docs-serve
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Installation
|
|
38
|
+
|
|
39
|
+
Install globally using [uv](https://docs.astral.sh/uv/):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv tool install atpcli
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
This installs `atpcli` as a global tool, making it available from anywhere in your terminal.
|
|
46
|
+
|
|
47
|
+
Or for development:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/phalt/atpcli.git
|
|
51
|
+
cd atpcli
|
|
52
|
+
make install
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Login
|
|
58
|
+
|
|
59
|
+
⚠️ **Security Note**: Use Bluesky app passwords, not your main password! See the [Quick Start Guide](docs/getting-started.md) for instructions on creating an app password.
|
|
60
|
+
|
|
61
|
+
Login to your Bluesky account and save the session:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
atpcli bsky login
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You'll be prompted for your handle and password. The session will be saved to `~/.config/atpcli/config.json`.
|
|
68
|
+
|
|
69
|
+
### View Timeline
|
|
70
|
+
|
|
71
|
+
View your timeline:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
atpcli bsky timeline
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
- `--limit N` - Show N posts (default: 10)
|
|
79
|
+
- `--p N` - Show page N (default: 1)
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
```bash
|
|
83
|
+
atpcli bsky timeline --limit 20
|
|
84
|
+
atpcli bsky timeline --p 2
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Development
|
|
88
|
+
|
|
89
|
+
### Setup
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
make install
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Run tests
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
make test
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Build documentation
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
make docs-build
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Serve documentation locally
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
make docs-serve
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Format code
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
make format
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Clean build artifacts
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
make clean
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Requirements
|
|
126
|
+
|
|
127
|
+
- Python 3.10+
|
|
128
|
+
- uv package manager
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
atpcli-0.1.0/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# atpcli
|
|
2
|
+
|
|
3
|
+
A Python CLI wrapper around the [atproto](https://github.com/MarshalX/atproto) package for interacting with Bluesky.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Full documentation is available at [docs/](docs/):
|
|
8
|
+
|
|
9
|
+
- [Installation Guide](docs/install.md)
|
|
10
|
+
- [Quick Start Guide](docs/getting-started.md) - Learn how to get app passwords and use atpcli
|
|
11
|
+
- [Login Command](docs/usage-login.md)
|
|
12
|
+
- [Timeline Command](docs/usage-timeline.md)
|
|
13
|
+
|
|
14
|
+
Or serve the docs locally:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
make docs-serve
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### Installation
|
|
23
|
+
|
|
24
|
+
Install globally using [uv](https://docs.astral.sh/uv/):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv tool install atpcli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This installs `atpcli` as a global tool, making it available from anywhere in your terminal.
|
|
31
|
+
|
|
32
|
+
Or for development:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/phalt/atpcli.git
|
|
36
|
+
cd atpcli
|
|
37
|
+
make install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Login
|
|
43
|
+
|
|
44
|
+
⚠️ **Security Note**: Use Bluesky app passwords, not your main password! See the [Quick Start Guide](docs/getting-started.md) for instructions on creating an app password.
|
|
45
|
+
|
|
46
|
+
Login to your Bluesky account and save the session:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
atpcli bsky login
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You'll be prompted for your handle and password. The session will be saved to `~/.config/atpcli/config.json`.
|
|
53
|
+
|
|
54
|
+
### View Timeline
|
|
55
|
+
|
|
56
|
+
View your timeline:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
atpcli bsky timeline
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Options:
|
|
63
|
+
- `--limit N` - Show N posts (default: 10)
|
|
64
|
+
- `--p N` - Show page N (default: 1)
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
```bash
|
|
68
|
+
atpcli bsky timeline --limit 20
|
|
69
|
+
atpcli bsky timeline --p 2
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
### Setup
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
make install
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Run tests
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
make test
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Build documentation
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
make docs-build
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Serve documentation locally
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
make docs-serve
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Format code
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
make format
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Clean build artifacts
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
make clean
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Requirements
|
|
111
|
+
|
|
112
|
+
- Python 3.10+
|
|
113
|
+
- uv package manager
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""CLI commands for atpcli."""
|
|
2
|
+
|
|
3
|
+
import textwrap
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from atproto import Client
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from atpcli.config import Config
|
|
10
|
+
from atpcli.display import display_post
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
atpcli_HEADER = r"""
|
|
15
|
+
_ _
|
|
16
|
+
__ _ _ __ ____| (_)
|
|
17
|
+
/ _` | '_ \/ ___| | |
|
|
18
|
+
| (_| | |_) | (__| | |
|
|
19
|
+
\__,_| .__/ \___|_|_|
|
|
20
|
+
|_|
|
|
21
|
+
""".strip("\n")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
def cli():
|
|
26
|
+
"""atpcli - A Python CLI wrapper around the atproto package."""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
cli.help = textwrap.dedent(f"""\
|
|
31
|
+
\b
|
|
32
|
+
{atpcli_HEADER}
|
|
33
|
+
|
|
34
|
+
🦋 atpcli - A Python CLI wrapper around the atproto package
|
|
35
|
+
|
|
36
|
+
📚 GitHub: https://github.com/phalt/atpcli
|
|
37
|
+
|
|
38
|
+
""").strip("\n")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@click.group()
|
|
42
|
+
def bsky():
|
|
43
|
+
"""Commands for interacting with Bluesky."""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
cli.add_command(bsky)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@bsky.command()
|
|
51
|
+
@click.option("--handle", prompt="Handle", help="Your Bluesky handle")
|
|
52
|
+
@click.option("--password", prompt="Password", hide_input=True, help="Your Bluesky password")
|
|
53
|
+
def login(handle: str, password: str):
|
|
54
|
+
"""Login to Bluesky and save session."""
|
|
55
|
+
try:
|
|
56
|
+
client = Client()
|
|
57
|
+
console.print(f"[blue]Logging in as {handle}...[/blue]")
|
|
58
|
+
profile = client.login(handle, password)
|
|
59
|
+
|
|
60
|
+
# Get the session string from the client
|
|
61
|
+
session_string = client.export_session_string()
|
|
62
|
+
|
|
63
|
+
# Save the session
|
|
64
|
+
config = Config()
|
|
65
|
+
config.save_session(handle, session_string)
|
|
66
|
+
|
|
67
|
+
console.print(f"[green]✓ Successfully logged in as {profile.display_name or handle}[/green]")
|
|
68
|
+
console.print(f"[dim]Session saved to {config.config_file}[/dim]")
|
|
69
|
+
except Exception as e:
|
|
70
|
+
console.print(f"[red]✗ Login failed: {e}[/red]")
|
|
71
|
+
raise SystemExit(1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@bsky.command()
|
|
75
|
+
@click.option("--limit", default=10, help="Number of posts to show")
|
|
76
|
+
@click.option("--p", "page", default=1, help="Page number to load")
|
|
77
|
+
def timeline(limit: int, page: int):
|
|
78
|
+
"""View your timeline."""
|
|
79
|
+
config = Config()
|
|
80
|
+
handle, session_string = config.load_session()
|
|
81
|
+
|
|
82
|
+
if not session_string:
|
|
83
|
+
console.print("[red]✗ Not logged in. Please run 'atpcli bsky login' first.[/red]")
|
|
84
|
+
raise SystemExit(1)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
client = Client()
|
|
88
|
+
console.print(f"[blue]Loading timeline for {handle}...[/blue]")
|
|
89
|
+
|
|
90
|
+
# Restore session from saved string
|
|
91
|
+
client.login(session_string=session_string)
|
|
92
|
+
|
|
93
|
+
# Calculate cursor position for pagination
|
|
94
|
+
# Note: We need to fetch pages sequentially to get the cursor for each page.
|
|
95
|
+
# This means accessing page N requires N API calls, which can be slow for high page numbers.
|
|
96
|
+
cursor = None
|
|
97
|
+
if page > 5:
|
|
98
|
+
warning_msg = f"[yellow]⚠ Loading page {page} requires {page} API calls. This may take a moment...[/yellow]"
|
|
99
|
+
console.print(warning_msg)
|
|
100
|
+
|
|
101
|
+
for i in range(1, page):
|
|
102
|
+
response = client.get_timeline(limit=limit, cursor=cursor)
|
|
103
|
+
cursor = response.cursor
|
|
104
|
+
if not cursor:
|
|
105
|
+
console.print(f"[yellow]⚠ Page {page} does not exist. Showing last available page (page {i}).[/yellow]")
|
|
106
|
+
page = i
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
# Get the requested page
|
|
110
|
+
timeline_response = client.get_timeline(limit=limit, cursor=cursor)
|
|
111
|
+
|
|
112
|
+
# Reverse the feed so latest posts appear at the bottom
|
|
113
|
+
# This allows users to scroll up to read
|
|
114
|
+
reversed_feed = list(reversed(timeline_response.feed))
|
|
115
|
+
|
|
116
|
+
for feed_view in reversed_feed:
|
|
117
|
+
post = feed_view.post
|
|
118
|
+
table = display_post(post)
|
|
119
|
+
console.print(table)
|
|
120
|
+
|
|
121
|
+
# Show pagination info
|
|
122
|
+
page_info = f"[dim]Showing {len(timeline_response.feed)} posts (page {page})"
|
|
123
|
+
if timeline_response.cursor:
|
|
124
|
+
page_info += f" - Use --p {page + 1} for next page"
|
|
125
|
+
page_info += "[/dim]"
|
|
126
|
+
console.print(f"\n{page_info}")
|
|
127
|
+
|
|
128
|
+
except Exception as e:
|
|
129
|
+
console.print(f"[red]✗ Failed to load timeline: {e}[/red]")
|
|
130
|
+
console.print("[yellow]Your session may have expired. Try logging in again.[/yellow]")
|
|
131
|
+
raise SystemExit(1)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
if __name__ == "__main__":
|
|
135
|
+
cli()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Configuration management for atpcli."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Config:
|
|
9
|
+
"""Manage atpcli configuration and session state."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, config_dir: Optional[Path] = None):
|
|
12
|
+
"""Initialize config with optional custom directory."""
|
|
13
|
+
if config_dir is None:
|
|
14
|
+
config_dir = Path.home() / ".config" / "atpcli"
|
|
15
|
+
self.config_dir = config_dir
|
|
16
|
+
self.config_file = self.config_dir / "config.json"
|
|
17
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
def save_session(self, handle: str, session_string: str) -> None:
|
|
20
|
+
"""Save session information to config file."""
|
|
21
|
+
config_data = self.load_config()
|
|
22
|
+
config_data["handle"] = handle
|
|
23
|
+
config_data["session"] = session_string
|
|
24
|
+
with open(self.config_file, "w", encoding="utf-8") as f:
|
|
25
|
+
json.dump(config_data, f, indent=2)
|
|
26
|
+
|
|
27
|
+
def load_session(self) -> tuple[Optional[str], Optional[str]]:
|
|
28
|
+
"""Load session information from config file."""
|
|
29
|
+
config_data = self.load_config()
|
|
30
|
+
return config_data.get("handle"), config_data.get("session")
|
|
31
|
+
|
|
32
|
+
def load_config(self) -> dict:
|
|
33
|
+
"""Load the entire config file."""
|
|
34
|
+
if not self.config_file.exists():
|
|
35
|
+
return {}
|
|
36
|
+
with open(self.config_file, "r", encoding="utf-8") as f:
|
|
37
|
+
return json.load(f)
|
|
38
|
+
|
|
39
|
+
def clear_session(self) -> None:
|
|
40
|
+
"""Clear the saved session."""
|
|
41
|
+
if self.config_file.exists():
|
|
42
|
+
self.config_file.unlink()
|