ssky 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.
ssky-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 simpleskyclient
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.
ssky-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.1
2
+ Name: ssky
3
+ Version: 0.1.0
4
+ Summary: Simple bluesky client
5
+ Home-page: https://github.com/simpleskyclient/ssky
6
+ License: LICENSE
7
+ Keywords: bluesky,client
8
+ Author: SimpleSkyClient Project
9
+ Author-email: simpleskypclient@gmail.com
10
+ Requires-Python: >=3.12,<3.14
11
+ Classifier: License :: Other/Proprietary License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Dist: atproto (>=0.0.55,<0.0.56)
16
+ Requires-Dist: beautifulsoup4 (>=4.12.3,<5.0.0)
17
+ Requires-Dist: pytest (>=8.3.4,<9.0.0)
18
+ Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
19
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
20
+ Project-URL: Repository, https://github.com/simpleskyclient/ssky
21
+ Description-Content-Type: text/markdown
22
+
23
+ # ssky - Simple Bluesky client
24
+
25
+ ## Introduction
26
+
27
+ Ssky is a simple Bluesky client, providing some specific features.
28
+
29
+ * Simple but Linux shell friendly user interface
30
+ * You can give `URI` or `URI::CID` to retrieve (get), quote, reply-to, repost/unrepost and delete a unique post
31
+ * You can give `DID` or `handle` to retrieve timeline/posts/profile, follow/unfollow a unique user
32
+ * You can get simply 'URI:CID' for posts and 'DID' for users when you retrieve (get) them, so you can use them for Linux pipeline or command substitution
33
+ * Various output options:
34
+ * Only unique identifier such as `URI::CID` or `DID`, for Linux pipeline or command substitution ([See Samples](#samples) )
35
+ * Line oriented output with identifier and human friendly text, for listing many records
36
+ * Multiline output with full texts of some attributes, for reading details
37
+ * JSON output, for preserving record structure derived from atproto library
38
+ * Some subcommands such as `get` and `search` allow to output to files per records
39
+
40
+ ### Requirements
41
+
42
+ * Python 3.12 or later
43
+
44
+ ### Install from github repository
45
+
46
+ ```bash
47
+ pip install git+https://github.com/simpleskyclient/ssky.git
48
+ ```
49
+
50
+ ## Quick Start
51
+
52
+ Ssky's behavior is specified by subcommand and options like `ssky subcommand [options]`.
53
+
54
+ ### Common options and names
55
+
56
+ * Common option -D, --delimiter: Delimiter string in output
57
+ * Common option -I, --id: Identifier only such as `URI::CID` or `DID`, depending on context
58
+ * Common option -T, --text: Text only such as main text in post or description in user profile
59
+ * Common option -L, --long: Long output
60
+ * Common option -J, --json: JSON output
61
+ * Common option -N NUM, --limit NUM: MAX result counts
62
+ * Common option -O DIR, --output DIR: Output to files in the specified directory
63
+ * Common name "myself": replace with login user DID or handle automatically
64
+
65
+ ### Login bluesky
66
+
67
+ First or all, you must log in the Bluesky.
68
+
69
+ ```sh
70
+ ssky login handle password
71
+ ```
72
+
73
+ *Handle* and *password* are formatted like `xxxx.bsky.social` and `xxxx-xxxxx-xxxx-xxxx`, respectively.
74
+
75
+ Once you have logged in, ssky remembers the login session in local environment (`~/.ssky`).
76
+ However, you must log in again when ssky reports session timeout error like "400 Token has been revoked".
77
+
78
+ You can also set Bluesky handle and password in environment variable `SSKY_USER`, separated by ':' like `handle:password`.
79
+
80
+ ```sh
81
+ export SSKY_USER=user.bsky.social:xxxx-xxxxx-xxxx-xxxx
82
+ ```
83
+
84
+ The environment variable overrides command line arguments, so you will login by the user alpha in the following example.
85
+
86
+ ```sh
87
+ SSKY_USER=alpha:password1 ssky login bravo password2
88
+ ```
89
+
90
+ ### Get posts
91
+
92
+ Get subcommand retrieves your timeline, another author's feed by handle or `DID`, and a post by `URI` or `URI::CID`.
93
+
94
+ ```sh
95
+ ssky get # Get your timeline
96
+ ssky get handle # Get other author's feed by handle, such as user.bsky.social
97
+ ssky get did:... # Get other author's feed by DID
98
+ ssky get at://... # Get a post specified by URI
99
+ ```
100
+
101
+ ### Get profile
102
+
103
+ Profile subcommand retrieves the user profile from handle or display name.
104
+
105
+ ```sh
106
+ ssky profile user.bsky.social
107
+ ssky profile 'Display Name'
108
+ ```
109
+
110
+ ### Post
111
+
112
+ Post subcommand sends a post. You can give the message by command line argument or the standard input. Tags, link cards, mentions, quotations, reply-to and attached images are available.
113
+
114
+ ```sh
115
+ ssky post Hello # Post from command line text
116
+ echo Hello | ssky post # Post from /dev/stdin
117
+ ssky post 'Hello, #bluesky @atproto.com https://bsky.app/' # Post with tags, mentions, and embed link card
118
+ ssky post 'Hello, bluesky!' --image dir1/hello1.png --image dir2/hello2.png # Post with images
119
+ ssky post 'Hello, bluesky!' --quote at://... # Quote the post
120
+ ssky post 'Hello, bluesky!' --reply-to at://... # Reply to the post
121
+
122
+ ssky post Hello --dry # Dry run
123
+ ```
124
+
125
+ ### Search posts
126
+
127
+ Search subcommand searches posts.
128
+
129
+ ```sh
130
+ ssky search foo # Search posts including 'foo'
131
+ ssky search foo --author handle # Search posts including 'foo' by the specified author
132
+ ssky search foo --since 20240101 --until 20241231 # Search posts including 'foo' in the specified period
133
+ ```
134
+
135
+ ### Delete post
136
+
137
+ Delete subcommand deletes a post.
138
+
139
+ ```sh
140
+ ssky delete at://... # Delete the post
141
+ ```
142
+
143
+ ### Repost to post
144
+
145
+ Repost subcommand reposts the post.
146
+
147
+ ```sh
148
+ ssky repost at://... # Repost the post
149
+ ```
150
+
151
+ ### Unrepost to post
152
+
153
+ Repost subcommand deletes the repost to a post.
154
+
155
+ ```sh
156
+ ssky unrepost at://... # Delete your repost to the specified post
157
+ ```
158
+
159
+ ### Search users
160
+
161
+ User subcommand searches users.
162
+
163
+ ```sh
164
+ ssky user foo # Search users including 'foo' in handle name and description
165
+ ```
166
+
167
+ ### Follow user
168
+
169
+ Follow subcommand follows a user.
170
+
171
+ ```sh
172
+ ssky follow handle # Follow the user
173
+ ssky follow did:.. # Follow the user
174
+ ```
175
+
176
+ ### Unfollow user
177
+
178
+ Unfollow subcommand deletes the follow to the user.
179
+
180
+ ```sh
181
+ ssky unfollow handle # Unfollow the user
182
+ ssky unfollow did:.. # Unfollow the user
183
+ ```
184
+
185
+ ## Samples
186
+
187
+ ### Search keyword 'bluesky' in your posts
188
+
189
+ ```sh
190
+ ssky search bluesky --author myself
191
+ ```
192
+
193
+ ### Save your last post in the directory './log'
194
+
195
+ ```sh
196
+ ssky post 'My very important posted message'
197
+ ssky get myself --limit 1 --text --output ./log
198
+ ```
199
+
200
+ ### Reply to the last post by myself
201
+
202
+ ```sh
203
+ ssky post 'Reply!' --reply-to $(ssky get myself --limit 1 --id)
204
+ ```
205
+
206
+ ## License
207
+
208
+ [MIT License](LICENSE)
209
+
210
+ ## Author
211
+
212
+ [SimpleSkyClient Project](https://github.com/simpleskyclient)
213
+
ssky-0.1.0/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # ssky - Simple Bluesky client
2
+
3
+ ## Introduction
4
+
5
+ Ssky is a simple Bluesky client, providing some specific features.
6
+
7
+ * Simple but Linux shell friendly user interface
8
+ * You can give `URI` or `URI::CID` to retrieve (get), quote, reply-to, repost/unrepost and delete a unique post
9
+ * You can give `DID` or `handle` to retrieve timeline/posts/profile, follow/unfollow a unique user
10
+ * You can get simply 'URI:CID' for posts and 'DID' for users when you retrieve (get) them, so you can use them for Linux pipeline or command substitution
11
+ * Various output options:
12
+ * Only unique identifier such as `URI::CID` or `DID`, for Linux pipeline or command substitution ([See Samples](#samples) )
13
+ * Line oriented output with identifier and human friendly text, for listing many records
14
+ * Multiline output with full texts of some attributes, for reading details
15
+ * JSON output, for preserving record structure derived from atproto library
16
+ * Some subcommands such as `get` and `search` allow to output to files per records
17
+
18
+ ### Requirements
19
+
20
+ * Python 3.12 or later
21
+
22
+ ### Install from github repository
23
+
24
+ ```bash
25
+ pip install git+https://github.com/simpleskyclient/ssky.git
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ Ssky's behavior is specified by subcommand and options like `ssky subcommand [options]`.
31
+
32
+ ### Common options and names
33
+
34
+ * Common option -D, --delimiter: Delimiter string in output
35
+ * Common option -I, --id: Identifier only such as `URI::CID` or `DID`, depending on context
36
+ * Common option -T, --text: Text only such as main text in post or description in user profile
37
+ * Common option -L, --long: Long output
38
+ * Common option -J, --json: JSON output
39
+ * Common option -N NUM, --limit NUM: MAX result counts
40
+ * Common option -O DIR, --output DIR: Output to files in the specified directory
41
+ * Common name "myself": replace with login user DID or handle automatically
42
+
43
+ ### Login bluesky
44
+
45
+ First or all, you must log in the Bluesky.
46
+
47
+ ```sh
48
+ ssky login handle password
49
+ ```
50
+
51
+ *Handle* and *password* are formatted like `xxxx.bsky.social` and `xxxx-xxxxx-xxxx-xxxx`, respectively.
52
+
53
+ Once you have logged in, ssky remembers the login session in local environment (`~/.ssky`).
54
+ However, you must log in again when ssky reports session timeout error like "400 Token has been revoked".
55
+
56
+ You can also set Bluesky handle and password in environment variable `SSKY_USER`, separated by ':' like `handle:password`.
57
+
58
+ ```sh
59
+ export SSKY_USER=user.bsky.social:xxxx-xxxxx-xxxx-xxxx
60
+ ```
61
+
62
+ The environment variable overrides command line arguments, so you will login by the user alpha in the following example.
63
+
64
+ ```sh
65
+ SSKY_USER=alpha:password1 ssky login bravo password2
66
+ ```
67
+
68
+ ### Get posts
69
+
70
+ Get subcommand retrieves your timeline, another author's feed by handle or `DID`, and a post by `URI` or `URI::CID`.
71
+
72
+ ```sh
73
+ ssky get # Get your timeline
74
+ ssky get handle # Get other author's feed by handle, such as user.bsky.social
75
+ ssky get did:... # Get other author's feed by DID
76
+ ssky get at://... # Get a post specified by URI
77
+ ```
78
+
79
+ ### Get profile
80
+
81
+ Profile subcommand retrieves the user profile from handle or display name.
82
+
83
+ ```sh
84
+ ssky profile user.bsky.social
85
+ ssky profile 'Display Name'
86
+ ```
87
+
88
+ ### Post
89
+
90
+ Post subcommand sends a post. You can give the message by command line argument or the standard input. Tags, link cards, mentions, quotations, reply-to and attached images are available.
91
+
92
+ ```sh
93
+ ssky post Hello # Post from command line text
94
+ echo Hello | ssky post # Post from /dev/stdin
95
+ ssky post 'Hello, #bluesky @atproto.com https://bsky.app/' # Post with tags, mentions, and embed link card
96
+ ssky post 'Hello, bluesky!' --image dir1/hello1.png --image dir2/hello2.png # Post with images
97
+ ssky post 'Hello, bluesky!' --quote at://... # Quote the post
98
+ ssky post 'Hello, bluesky!' --reply-to at://... # Reply to the post
99
+
100
+ ssky post Hello --dry # Dry run
101
+ ```
102
+
103
+ ### Search posts
104
+
105
+ Search subcommand searches posts.
106
+
107
+ ```sh
108
+ ssky search foo # Search posts including 'foo'
109
+ ssky search foo --author handle # Search posts including 'foo' by the specified author
110
+ ssky search foo --since 20240101 --until 20241231 # Search posts including 'foo' in the specified period
111
+ ```
112
+
113
+ ### Delete post
114
+
115
+ Delete subcommand deletes a post.
116
+
117
+ ```sh
118
+ ssky delete at://... # Delete the post
119
+ ```
120
+
121
+ ### Repost to post
122
+
123
+ Repost subcommand reposts the post.
124
+
125
+ ```sh
126
+ ssky repost at://... # Repost the post
127
+ ```
128
+
129
+ ### Unrepost to post
130
+
131
+ Repost subcommand deletes the repost to a post.
132
+
133
+ ```sh
134
+ ssky unrepost at://... # Delete your repost to the specified post
135
+ ```
136
+
137
+ ### Search users
138
+
139
+ User subcommand searches users.
140
+
141
+ ```sh
142
+ ssky user foo # Search users including 'foo' in handle name and description
143
+ ```
144
+
145
+ ### Follow user
146
+
147
+ Follow subcommand follows a user.
148
+
149
+ ```sh
150
+ ssky follow handle # Follow the user
151
+ ssky follow did:.. # Follow the user
152
+ ```
153
+
154
+ ### Unfollow user
155
+
156
+ Unfollow subcommand deletes the follow to the user.
157
+
158
+ ```sh
159
+ ssky unfollow handle # Unfollow the user
160
+ ssky unfollow did:.. # Unfollow the user
161
+ ```
162
+
163
+ ## Samples
164
+
165
+ ### Search keyword 'bluesky' in your posts
166
+
167
+ ```sh
168
+ ssky search bluesky --author myself
169
+ ```
170
+
171
+ ### Save your last post in the directory './log'
172
+
173
+ ```sh
174
+ ssky post 'My very important posted message'
175
+ ssky get myself --limit 1 --text --output ./log
176
+ ```
177
+
178
+ ### Reply to the last post by myself
179
+
180
+ ```sh
181
+ ssky post 'Reply!' --reply-to $(ssky get myself --limit 1 --id)
182
+ ```
183
+
184
+ ## License
185
+
186
+ [MIT License](LICENSE)
187
+
188
+ ## Author
189
+
190
+ [SimpleSkyClient Project](https://github.com/simpleskyclient)
@@ -0,0 +1,26 @@
1
+ [tool.poetry]
2
+ name = "ssky"
3
+ version = "0.1.0"
4
+ description = "Simple bluesky client"
5
+ authors = ["SimpleSkyClient Project <simpleskypclient@gmail.com>"]
6
+ readme = "README.md"
7
+ license = "LICENSE"
8
+ homepage = "https://github.com/simpleskyclient/ssky"
9
+ repository = "https://github.com/simpleskyclient/ssky"
10
+ keywords = ["bluesky", "client"]
11
+ packages = [{include = "ssky", from = "src"}]
12
+
13
+ [tool.poetry.dependencies]
14
+ python = ">=3.12,<3.14"
15
+ beautifulsoup4 = "^4.12.3"
16
+ python-dotenv = "^1.0.1"
17
+ requests = "^2.32.3"
18
+ atproto = "^0.0.55"
19
+ pytest = "^8.3.4"
20
+
21
+ [tool.poetry.scripts]
22
+ ssky = "ssky.main:main"
23
+
24
+ [build-system]
25
+ requires = ["poetry-core"]
26
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,25 @@
1
+ import sys
2
+ import atproto_client
3
+ from ssky.ssky_session import ssky_client
4
+ from ssky.util import disjoin_uri_cid, is_joined_uri_cid
5
+
6
+ def delete(post, **kwargs) -> str:
7
+ if is_joined_uri_cid(post):
8
+ uri, _ = disjoin_uri_cid(post)
9
+ else:
10
+ uri = post
11
+
12
+ try:
13
+ status = ssky_client().delete_post(uri)
14
+ if status is False:
15
+ print('Failed to delete', file=sys.stderr)
16
+ return None
17
+ return uri
18
+ except atproto_client.exceptions.AtProtocolError as e:
19
+ if 'response' in dir(e) and e.response is not None:
20
+ print(f'{e.response.status_code} {e.response.content.message}', file=sys.stderr)
21
+ elif str(e) is not None and len(str(e)) > 0:
22
+ print(f'{str(e)}', file=sys.stderr)
23
+ else:
24
+ print(f'{e.__class__.__name__}', file=sys.stderr)
25
+ return None
@@ -0,0 +1,20 @@
1
+ import sys
2
+ import atproto_client
3
+ from ssky.profile_list import ProfileList
4
+ from ssky.ssky_session import expand_actor, ssky_client
5
+
6
+ def follow(name, **kwargs) -> str:
7
+ try:
8
+ client = ssky_client()
9
+ actor = expand_actor(name)
10
+ profile = client.get_profile(actor)
11
+ client.follow(profile.did)
12
+ return ProfileList().append(profile.did)
13
+ except atproto_client.exceptions.AtProtocolError as e:
14
+ if 'response' in dir(e) and e.response is not None:
15
+ print(f'{e.response.status_code} {e.response.content.message}', file=sys.stderr)
16
+ elif str(e) is not None and len(str(e)) > 0:
17
+ print(f'{str(e)}', file=sys.stderr)
18
+ else:
19
+ print(f'{e.__class__.__name__}', file=sys.stderr)
20
+ return None
@@ -0,0 +1,54 @@
1
+ import sys
2
+ import atproto_client
3
+ from ssky.ssky_session import expand_actor, ssky_client
4
+ from ssky.post_data_list import PostDataList
5
+ from ssky.util import disjoin_uri_cid, is_joined_uri_cid
6
+
7
+ def get_posts(client, uri, cid) -> None:
8
+ res = client.get_posts([uri])
9
+ post_data_list = PostDataList()
10
+ for post in res.posts:
11
+ if post.uri == uri and (cid is None or post.cid == cid):
12
+ post_data_list.append(post)
13
+ return post_data_list
14
+
15
+ def get_author_feed(client, user, limit=100) -> None:
16
+ res = client.get_author_feed(user, limit=limit)
17
+ post_data_list = PostDataList()
18
+ for feed_post in res.feed:
19
+ post_data_list.append(feed_post.post)
20
+ return post_data_list
21
+
22
+ def get_timeline(client, limit=100) -> None:
23
+ res = client.get_timeline(limit=limit)
24
+ post_data_list = PostDataList()
25
+ for feed_post in res.feed:
26
+ post_data_list.append(feed_post.post)
27
+ return post_data_list
28
+
29
+ def get(target=None, limit=100, **kwargs) -> PostDataList:
30
+ try:
31
+ client = ssky_client()
32
+ if target is None:
33
+ post_data_list = get_timeline(client, limit=limit)
34
+ elif target.startswith('at://'):
35
+ if is_joined_uri_cid(target):
36
+ uri, cid = disjoin_uri_cid(target)
37
+ else:
38
+ uri = target
39
+ cid = None
40
+ post_data_list = get_posts(client, uri, cid)
41
+ elif target.startswith('did:'):
42
+ post_data_list = get_author_feed(client, target, limit=limit)
43
+ else:
44
+ actor = expand_actor(target)
45
+ post_data_list = get_author_feed(client, actor, limit=limit)
46
+ return post_data_list
47
+ except atproto_client.exceptions.AtProtocolError as e:
48
+ if 'response' in dir(e) and e.response is not None:
49
+ print(f'{e.response.status_code} {e.response.content.message}', file=sys.stderr)
50
+ elif str(e) is not None and len(str(e)) > 0:
51
+ print(f'{str(e)}', file=sys.stderr)
52
+ else:
53
+ print(f'{e.__class__.__name__}', file=sys.stderr)
54
+ return None
@@ -0,0 +1,18 @@
1
+ import sys
2
+ import atproto_client
3
+ from ssky.profile_list import ProfileList
4
+ from ssky.ssky_session import SskySession
5
+
6
+ def login(handle=None, password=None, **kwargs) -> list:
7
+ try:
8
+ session = SskySession(handle=handle, password=password)
9
+ session.persist()
10
+ return ProfileList().append(session.profile().did)
11
+ except atproto_client.exceptions.AtProtocolError as e:
12
+ if 'response' in dir(e) and e.response is not None:
13
+ print(f'{e.response.status_code} {e.response.content.message}', file=sys.stderr)
14
+ elif str(e) is not None and len(str(e)) > 0:
15
+ print(f'{str(e)}', file=sys.stderr)
16
+ else:
17
+ print(f'{e.__class__.__name__}', file=sys.stderr)
18
+ return None