tumblrbot 1.5.0__tar.gz → 1.6.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.
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/PKG-INFO +3 -3
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/README.md +2 -2
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/pyproject.toml +1 -1
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/flow/generate.py +22 -4
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/utils/models.py +17 -7
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/utils/tumblr.py +8 -3
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/.github/FUNDING.yml +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/.github/dependabot.yml +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/.gitignore +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/UNLICENSE +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/sample_custom_prompts.jsonl +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/__init__.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/__main__.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/flow/__init__.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/flow/download.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/flow/examples.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/flow/fine_tune.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/utils/__init__.py +0 -0
- {tumblrbot-1.5.0 → tumblrbot-1.6.0}/src/tumblrbot/utils/common.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tumblrbot
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: An updated bot that posts to Tumblr, based on your very own blog!
|
|
5
5
|
Requires-Python: >= 3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -73,14 +73,14 @@ Features:
|
|
|
73
73
|
1. [Generates and uploads posts][Generate] to the [configured][config] [Tumblr] blog using the [configured][config] fine-tuned model.
|
|
74
74
|
- Creates tags by extracting keywords at the [configured][config] frequency using the [configured][config] model.
|
|
75
75
|
- Uploads posts as drafts to the [configured][config] [Tumblr] blog.
|
|
76
|
+
- Reblog posts at the [configured][config] frequency.
|
|
76
77
|
- Shows progress and previews the current post.
|
|
77
78
|
- Colorful output, progress bars, and post previews using [rich].
|
|
78
79
|
- Automatically keeps the [config] file up-to-date and recreates it if missing.
|
|
79
80
|
|
|
80
81
|
**To-Do:**
|
|
81
82
|
|
|
82
|
-
-
|
|
83
|
-
- Add reblog generation logic.
|
|
83
|
+
- ...
|
|
84
84
|
|
|
85
85
|
**Known Issues:**
|
|
86
86
|
|
|
@@ -55,14 +55,14 @@ Features:
|
|
|
55
55
|
1. [Generates and uploads posts][Generate] to the [configured][config] [Tumblr] blog using the [configured][config] fine-tuned model.
|
|
56
56
|
- Creates tags by extracting keywords at the [configured][config] frequency using the [configured][config] model.
|
|
57
57
|
- Uploads posts as drafts to the [configured][config] [Tumblr] blog.
|
|
58
|
+
- Reblog posts at the [configured][config] frequency.
|
|
58
59
|
- Shows progress and previews the current post.
|
|
59
60
|
- Colorful output, progress bars, and post previews using [rich].
|
|
60
61
|
- Automatically keeps the [config] file up-to-date and recreates it if missing.
|
|
61
62
|
|
|
62
63
|
**To-Do:**
|
|
63
64
|
|
|
64
|
-
-
|
|
65
|
-
- Add reblog generation logic.
|
|
65
|
+
- ...
|
|
66
66
|
|
|
67
67
|
**Known Issues:**
|
|
68
68
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from random import random
|
|
1
|
+
from random import random, randrange
|
|
2
2
|
from typing import override
|
|
3
3
|
|
|
4
4
|
import rich
|
|
@@ -28,18 +28,28 @@ class DraftGenerator(FlowClass):
|
|
|
28
28
|
rich.print(f":chart_increasing: [bold green]Generated {self.config.draft_count} draft(s).[/] {message}")
|
|
29
29
|
|
|
30
30
|
def generate_post(self) -> Post:
|
|
31
|
-
|
|
31
|
+
if random() < self.config.reblog_chance: # noqa: S311
|
|
32
|
+
original = self.get_random_post()
|
|
33
|
+
user_message = f"{self.config.reblog_user_message}\n\n{original.get_content_text()}"
|
|
34
|
+
else:
|
|
35
|
+
original = Post()
|
|
36
|
+
user_message = self.config.user_message
|
|
37
|
+
|
|
38
|
+
text = self.generate_text(user_message)
|
|
32
39
|
if tags := self.generate_tags(text):
|
|
33
40
|
tags = tags.tags
|
|
34
41
|
return Post(
|
|
35
42
|
content=[Post.Block(type="text", text=text)],
|
|
36
43
|
tags=tags or [],
|
|
37
44
|
state="draft",
|
|
45
|
+
parent_tumblelog_uuid=original.blog.uuid,
|
|
46
|
+
parent_post_id=original.id,
|
|
47
|
+
reblog_key=original.reblog_key,
|
|
38
48
|
)
|
|
39
49
|
|
|
40
|
-
def generate_text(self) -> str:
|
|
50
|
+
def generate_text(self, user_message: str) -> str:
|
|
41
51
|
return self.openai.responses.create(
|
|
42
|
-
input=
|
|
52
|
+
input=user_message,
|
|
43
53
|
instructions=self.config.developer_message,
|
|
44
54
|
model=self.config.fine_tuned_model,
|
|
45
55
|
).output_text
|
|
@@ -54,3 +64,11 @@ class DraftGenerator(FlowClass):
|
|
|
54
64
|
).output_parsed
|
|
55
65
|
|
|
56
66
|
return None
|
|
67
|
+
|
|
68
|
+
def get_random_post(self) -> Post:
|
|
69
|
+
total = self.tumblr.retrieve_blog_info(self.config.upload_blog_identifier).response.blog.posts
|
|
70
|
+
post = self.tumblr.retrieve_published_posts(
|
|
71
|
+
self.config.upload_blog_identifier,
|
|
72
|
+
offset=randrange(total), # noqa: S311
|
|
73
|
+
).response.posts[0]
|
|
74
|
+
return Post.model_validate(post)
|
|
@@ -74,6 +74,8 @@ class Config(FileSyncSettings):
|
|
|
74
74
|
draft_count: PositiveInt = Field(150, description="The number of drafts to process. This will affect the number of tokens used with OpenAI")
|
|
75
75
|
tags_chance: NonNegativeFloat = Field(0.1, description="The chance to generate tags for any given post. This will incur extra calls to OpenAI.")
|
|
76
76
|
tags_developer_message: str = Field("You will be provided with a block of text, and your task is to extract a very short list of the most important subjects from it.", description="The developer message used to generate tags.")
|
|
77
|
+
reblog_chance: NonNegativeFloat = Field(0.05, description="The chance to generate a reblog of a random post.")
|
|
78
|
+
reblog_user_message: str = Field("Please write a comical Tumblr post in response to the following Tumblr post:", description="The prefix for the user message used to reblog posts.")
|
|
77
79
|
|
|
78
80
|
@classmethod
|
|
79
81
|
@override
|
|
@@ -100,7 +102,7 @@ class Config(FileSyncSettings):
|
|
|
100
102
|
|
|
101
103
|
toml_table[name] = value
|
|
102
104
|
|
|
103
|
-
|
|
105
|
+
self.toml_file.write_text(tomlkit.dumps(toml_table), encoding="utf_8")
|
|
104
106
|
|
|
105
107
|
return self
|
|
106
108
|
|
|
@@ -170,12 +172,14 @@ class Tokens(FileSyncSettings):
|
|
|
170
172
|
return self
|
|
171
173
|
|
|
172
174
|
|
|
175
|
+
class Blog(FullyValidatedModel):
|
|
176
|
+
posts: int = 0
|
|
177
|
+
uuid: str = ""
|
|
178
|
+
|
|
179
|
+
|
|
173
180
|
class ResponseModel(FullyValidatedModel):
|
|
174
181
|
class Response(FullyValidatedModel):
|
|
175
|
-
|
|
176
|
-
posts: int
|
|
177
|
-
|
|
178
|
-
blog: Blog = Blog(posts=0)
|
|
182
|
+
blog: Blog = Blog()
|
|
179
183
|
posts: list[Any] = []
|
|
180
184
|
|
|
181
185
|
response: Response
|
|
@@ -183,12 +187,18 @@ class ResponseModel(FullyValidatedModel):
|
|
|
183
187
|
|
|
184
188
|
class Post(FullyValidatedModel):
|
|
185
189
|
class Block(FullyValidatedModel):
|
|
186
|
-
type: str
|
|
190
|
+
type: str = ""
|
|
187
191
|
text: str = ""
|
|
188
192
|
blocks: list[int] = []
|
|
189
193
|
|
|
194
|
+
blog: SkipJsonSchema[Blog] = Blog()
|
|
195
|
+
id: SkipJsonSchema[int] = 0
|
|
196
|
+
parent_tumblelog_uuid: SkipJsonSchema[str] = ""
|
|
197
|
+
parent_post_id: SkipJsonSchema[int] = 0
|
|
198
|
+
reblog_key: SkipJsonSchema[str] = ""
|
|
199
|
+
|
|
190
200
|
timestamp: SkipJsonSchema[int] = 0
|
|
191
|
-
tags: Annotated[list[str], PlainSerializer(",".join)]
|
|
201
|
+
tags: Annotated[list[str], PlainSerializer(",".join)] = []
|
|
192
202
|
state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "published"
|
|
193
203
|
|
|
194
204
|
content: SkipJsonSchema[list[Block]] = []
|
|
@@ -22,20 +22,25 @@ class TumblrSession(OAuth1Session):
|
|
|
22
22
|
error.add_note(response.text)
|
|
23
23
|
raise
|
|
24
24
|
|
|
25
|
-
def
|
|
25
|
+
def retrieve_blog_info(self, blog_identifier: str) -> ResponseModel:
|
|
26
|
+
response = self.get(f"https://api.tumblr.com/v2/blog/{blog_identifier}/info")
|
|
27
|
+
return ResponseModel.model_validate_json(response.text)
|
|
28
|
+
|
|
29
|
+
def retrieve_published_posts(self, blog_identifier: str, offset: int | None = None, after: int | None = None) -> ResponseModel:
|
|
26
30
|
response = self.get(
|
|
27
31
|
f"https://api.tumblr.com/v2/blog/{blog_identifier}/posts",
|
|
28
32
|
params={
|
|
33
|
+
"offset": offset,
|
|
29
34
|
"after": after,
|
|
30
35
|
"sort": "asc",
|
|
31
36
|
"npf": True,
|
|
32
37
|
},
|
|
33
38
|
)
|
|
34
|
-
return ResponseModel.model_validate_json(response.
|
|
39
|
+
return ResponseModel.model_validate_json(response.text)
|
|
35
40
|
|
|
36
41
|
def create_post(self, blog_identifier: str, post: Post) -> ResponseModel:
|
|
37
42
|
response = self.post(
|
|
38
43
|
f"https://api.tumblr.com/v2/blog/{blog_identifier}/posts",
|
|
39
44
|
json=post.model_dump(),
|
|
40
45
|
)
|
|
41
|
-
return ResponseModel.model_validate_json(response.
|
|
46
|
+
return ResponseModel.model_validate_json(response.text)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|