tumblrbot 1.9.1__tar.gz → 1.9.3__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.9.1 → tumblrbot-1.9.3}/.gitignore +2 -3
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/PKG-INFO +2 -6
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/README.md +1 -4
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/pyproject.toml +1 -2
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/flow/generate.py +2 -1
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/utils/models.py +40 -67
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/.github/FUNDING.yml +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/.github/dependabot.yml +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/UNLICENSE +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/sample_custom_prompts.jsonl +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/__init__.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/__main__.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/flow/__init__.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/flow/download.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/flow/examples.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/flow/fine_tune.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/utils/__init__.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/utils/common.py +0 -0
- {tumblrbot-1.9.1 → tumblrbot-1.9.3}/src/tumblrbot/utils/tumblr.py +0 -0
|
@@ -199,7 +199,7 @@ cython_debug/
|
|
|
199
199
|
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
200
200
|
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
201
201
|
# you could uncomment the following to ignore the entire vscode folder
|
|
202
|
-
|
|
202
|
+
.vscode/
|
|
203
203
|
|
|
204
204
|
# Ruff stuff:
|
|
205
205
|
.ruff_cache/
|
|
@@ -215,8 +215,7 @@ __marimo__/
|
|
|
215
215
|
# Streamlit
|
|
216
216
|
.streamlit/secrets.toml
|
|
217
217
|
|
|
218
|
-
.vscode
|
|
219
218
|
data
|
|
220
219
|
*.jsonl
|
|
221
|
-
|
|
220
|
+
*.toml
|
|
222
221
|
tumblrbot.exe.lnk
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tumblrbot
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.3
|
|
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
|
|
7
|
-
Requires-Dist: keyring
|
|
8
7
|
Requires-Dist: openai
|
|
9
8
|
Requires-Dist: pwinput
|
|
10
9
|
Requires-Dist: pydantic
|
|
@@ -25,7 +24,6 @@ Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
|
|
|
25
24
|
[JSON Lines Validator]: https://jsonlines.org/validator
|
|
26
25
|
|
|
27
26
|
[pip]: https://pypi.org
|
|
28
|
-
[keyring]: https://pypi.org/project/keyring
|
|
29
27
|
[Rich]: https://pypi.org/project/rich
|
|
30
28
|
|
|
31
29
|
[OpenAI]: https://pypi.org/project/openai
|
|
@@ -60,7 +58,6 @@ Features:
|
|
|
60
58
|
|
|
61
59
|
- An [interactive console][Main] for all steps of generating posts for the blog:
|
|
62
60
|
1. Asks for [OpenAI] and [Tumblr] tokens.
|
|
63
|
-
- Stores API tokens using [keyring].
|
|
64
61
|
1. Retrieves [Tumblr] [OAuth] tokens.
|
|
65
62
|
1. [Downloads posts][Download] from specified blogs ([configurable]).
|
|
66
63
|
- Skips redownloading already downloaded posts.
|
|
@@ -102,7 +99,6 @@ Features:
|
|
|
102
99
|
1. Install the [pip] package: `pip install tumblrbot`
|
|
103
100
|
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
104
101
|
- On Linux, you will have to make a virtual environment or use the flag to install packages system-wide.
|
|
105
|
-
- See [keyring] for additional requirements if you are not on Windows.
|
|
106
102
|
|
|
107
103
|
## Usage
|
|
108
104
|
|
|
@@ -174,7 +170,7 @@ Specific Options:
|
|
|
174
170
|
- **`tags_chance`** - This should be between 0 and 1. Setting it to 0 corresponds to a 0% chance (never) to add tags to a post. 1 corresponds to a 100% chance (always) to add tags to a post. Adding tags incurs a very small token cost.
|
|
175
171
|
- **`reblog_blog_identifiers`** - Whenever a reblog is attempted, a random blog from this list will be chosen to be reblogged from.
|
|
176
172
|
- **`reblog_chance`** - This setting works the same way as `tags_chance`.
|
|
177
|
-
- **`reblog_user_message`** - This setting is a [format string]. The only argument it is formatted with is the content of the post being reblogged. In simple terms, the `{}` will be replaced with said content.
|
|
173
|
+
- **`reblog_user_message`** - This setting is a [format string]. The only argument it is formatted with is the content of the post being reblogged. In simple terms, the `{}` will be replaced with said content. Alternatively, you can leave out the `{}` so that the reblogged post is appended to the end.
|
|
178
174
|
- *Note: The bot is only given the latest message in a reblog chain due to the required complexity and added costs of including the entire chain.*
|
|
179
175
|
|
|
180
176
|
## Manual Fine-Tuning
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
[JSON Lines Validator]: https://jsonlines.org/validator
|
|
8
8
|
|
|
9
9
|
[pip]: https://pypi.org
|
|
10
|
-
[keyring]: https://pypi.org/project/keyring
|
|
11
10
|
[Rich]: https://pypi.org/project/rich
|
|
12
11
|
|
|
13
12
|
[OpenAI]: https://pypi.org/project/openai
|
|
@@ -42,7 +41,6 @@ Features:
|
|
|
42
41
|
|
|
43
42
|
- An [interactive console][Main] for all steps of generating posts for the blog:
|
|
44
43
|
1. Asks for [OpenAI] and [Tumblr] tokens.
|
|
45
|
-
- Stores API tokens using [keyring].
|
|
46
44
|
1. Retrieves [Tumblr] [OAuth] tokens.
|
|
47
45
|
1. [Downloads posts][Download] from specified blogs ([configurable]).
|
|
48
46
|
- Skips redownloading already downloaded posts.
|
|
@@ -84,7 +82,6 @@ Features:
|
|
|
84
82
|
1. Install the [pip] package: `pip install tumblrbot`
|
|
85
83
|
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
86
84
|
- On Linux, you will have to make a virtual environment or use the flag to install packages system-wide.
|
|
87
|
-
- See [keyring] for additional requirements if you are not on Windows.
|
|
88
85
|
|
|
89
86
|
## Usage
|
|
90
87
|
|
|
@@ -156,7 +153,7 @@ Specific Options:
|
|
|
156
153
|
- **`tags_chance`** - This should be between 0 and 1. Setting it to 0 corresponds to a 0% chance (never) to add tags to a post. 1 corresponds to a 100% chance (always) to add tags to a post. Adding tags incurs a very small token cost.
|
|
157
154
|
- **`reblog_blog_identifiers`** - Whenever a reblog is attempted, a random blog from this list will be chosen to be reblogged from.
|
|
158
155
|
- **`reblog_chance`** - This setting works the same way as `tags_chance`.
|
|
159
|
-
- **`reblog_user_message`** - This setting is a [format string]. The only argument it is formatted with is the content of the post being reblogged. In simple terms, the `{}` will be replaced with said content.
|
|
156
|
+
- **`reblog_user_message`** - This setting is a [format string]. The only argument it is formatted with is the content of the post being reblogged. In simple terms, the `{}` will be replaced with said content. Alternatively, you can leave out the `{}` so that the reblogged post is appended to the end.
|
|
160
157
|
- *Note: The bot is only given the latest message in a reblog chain due to the required complexity and added costs of including the entire chain.*
|
|
161
158
|
|
|
162
159
|
## Manual Fine-Tuning
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tumblrbot"
|
|
3
|
-
version = "1.9.
|
|
3
|
+
version = "1.9.3"
|
|
4
4
|
description = "An updated bot that posts to Tumblr, based on your very own blog!"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">= 3.13"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"keyring",
|
|
9
8
|
"openai",
|
|
10
9
|
"pwinput",
|
|
11
10
|
"pydantic",
|
|
@@ -35,6 +35,8 @@ class DraftGenerator(FlowClass):
|
|
|
35
35
|
def generate_post(self) -> Post:
|
|
36
36
|
if original := self.get_random_post():
|
|
37
37
|
user_message = self.config.reblog_user_message.format(original)
|
|
38
|
+
if "{}" not in self.config.reblog_user_message:
|
|
39
|
+
user_message += str(original)
|
|
38
40
|
else:
|
|
39
41
|
original = Post()
|
|
40
42
|
user_message = self.config.user_message
|
|
@@ -46,7 +48,6 @@ class DraftGenerator(FlowClass):
|
|
|
46
48
|
return Post(
|
|
47
49
|
content=[Post.Block(type="text", text=text)],
|
|
48
50
|
tags=tags or [],
|
|
49
|
-
state="draft",
|
|
50
51
|
parent_tumblelog_uuid=original.blog.uuid,
|
|
51
52
|
parent_post_id=original.id,
|
|
52
53
|
reblog_key=original.reblog_key,
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
import tomllib
|
|
2
|
-
from abc import abstractmethod
|
|
3
1
|
from collections.abc import Generator
|
|
4
2
|
from pathlib import Path
|
|
5
|
-
from
|
|
3
|
+
from tomllib import loads
|
|
4
|
+
from typing import Annotated, Any, Literal, Self, override
|
|
6
5
|
|
|
7
6
|
import rich
|
|
8
|
-
import tomlkit
|
|
9
|
-
from keyring import get_password, set_password
|
|
10
7
|
from openai.types import ChatModel
|
|
11
8
|
from pwinput import pwinput
|
|
12
9
|
from pydantic import BaseModel, ConfigDict, Field, NonNegativeFloat, NonNegativeInt, PlainSerializer, PositiveFloat, PositiveInt, model_validator
|
|
13
10
|
from pydantic.json_schema import SkipJsonSchema
|
|
14
11
|
from requests_oauthlib import OAuth1Session
|
|
15
12
|
from rich.panel import Panel
|
|
16
|
-
from rich.prompt import
|
|
17
|
-
from tomlkit import comment, document
|
|
13
|
+
from rich.prompt import Prompt
|
|
14
|
+
from tomlkit import comment, document, dumps # pyright: ignore[reportUnknownVariableType]
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
class FullyValidatedModel(BaseModel):
|
|
@@ -29,22 +26,32 @@ class FullyValidatedModel(BaseModel):
|
|
|
29
26
|
|
|
30
27
|
class FileSyncSettings(FullyValidatedModel):
|
|
31
28
|
@classmethod
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
def get_toml_file(cls) -> Path:
|
|
30
|
+
return Path(f"{cls.__name__.lower()}.toml")
|
|
34
31
|
|
|
35
32
|
@classmethod
|
|
36
33
|
def load(cls) -> Self:
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
toml_file = cls.get_toml_file()
|
|
35
|
+
data = loads(toml_file.read_text("utf_8")) if toml_file.exists() else {}
|
|
36
|
+
return cls.model_validate(data)
|
|
39
37
|
|
|
40
38
|
@model_validator(mode="after")
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
def dump(self) -> Self:
|
|
40
|
+
toml_table = document()
|
|
41
|
+
|
|
42
|
+
for (name, field), value in zip(self.__class__.model_fields.items(), self.model_dump(mode="json").values(), strict=True):
|
|
43
|
+
if field.description is not None:
|
|
44
|
+
for line in field.description.split(". "):
|
|
45
|
+
toml_table.add(comment(f"{line.removesuffix('.')}."))
|
|
43
46
|
|
|
47
|
+
toml_table[name] = value
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
toml_file: ClassVar = Path("config.toml")
|
|
49
|
+
self.get_toml_file().write_text(dumps(toml_table), encoding="utf_8")
|
|
47
50
|
|
|
51
|
+
return self
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Config(FileSyncSettings):
|
|
48
55
|
# Downloading Posts & Writing Examples
|
|
49
56
|
download_blog_identifiers: list[str] = Field([], description="The identifiers of the blogs which post data will be downloaded from.")
|
|
50
57
|
data_directory: Path = Field(Path("data"), description="Where to store downloaded post data.")
|
|
@@ -78,16 +85,10 @@ class Config(FileSyncSettings):
|
|
|
78
85
|
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.")
|
|
79
86
|
reblog_blog_identifiers: list[str] = Field([], description="The identifiers of blogs that can be reblogged from when generating drafts.")
|
|
80
87
|
reblog_chance: NonNegativeFloat = Field(0.1, description="The chance to generate a reblog of a random post. This will use more OpenAI tokens.")
|
|
81
|
-
reblog_user_message: str = Field("Please write a comical Tumblr post in response to the following post:\n{}", description="The format string for the user message used to reblog posts.")
|
|
82
|
-
|
|
83
|
-
@classmethod
|
|
84
|
-
@override
|
|
85
|
-
def read(cls) -> dict[str, object] | None:
|
|
86
|
-
return tomllib.loads(cls.toml_file.read_text("utf_8")) if cls.toml_file.exists() else None
|
|
88
|
+
reblog_user_message: str = Field("Please write a comical Tumblr post in response to the following post:\n\n{}", description="The format string for the user message used to reblog posts.")
|
|
87
89
|
|
|
88
|
-
@model_validator(mode="after")
|
|
89
90
|
@override
|
|
90
|
-
def
|
|
91
|
+
def model_post_init(self, _: object) -> None:
|
|
91
92
|
if not self.download_blog_identifiers:
|
|
92
93
|
rich.print("Enter the [cyan]identifiers of your blogs[/] that data should be [bold purple]downloaded[/] from, separated by commas.")
|
|
93
94
|
self.download_blog_identifiers = list(map(str.strip, Prompt.ask("[bold][Example] [dim]staff.tumblr.com,changes").split(",")))
|
|
@@ -96,19 +97,6 @@ class Config(FileSyncSettings):
|
|
|
96
97
|
rich.print("Enter the [cyan]identifier of your blog[/] that drafts should be [bold purple]uploaded[/] to.")
|
|
97
98
|
self.upload_blog_identifier = Prompt.ask("[bold][Example] [dim]staff.tumblr.com or changes").strip()
|
|
98
99
|
|
|
99
|
-
toml_table = document()
|
|
100
|
-
|
|
101
|
-
for (name, field), value in zip(self.__class__.model_fields.items(), self.model_dump(mode="json").values(), strict=True):
|
|
102
|
-
if field.description is not None:
|
|
103
|
-
for line in field.description.split(". "):
|
|
104
|
-
toml_table.add(comment(f"{line.removesuffix('.')}."))
|
|
105
|
-
|
|
106
|
-
toml_table[name] = value
|
|
107
|
-
|
|
108
|
-
self.toml_file.write_text(tomlkit.dumps(toml_table), encoding="utf_8")
|
|
109
|
-
|
|
110
|
-
return self
|
|
111
|
-
|
|
112
100
|
|
|
113
101
|
class Tokens(FileSyncSettings):
|
|
114
102
|
class Tumblr(FullyValidatedModel):
|
|
@@ -117,39 +105,16 @@ class Tokens(FileSyncSettings):
|
|
|
117
105
|
resource_owner_key: str = ""
|
|
118
106
|
resource_owner_secret: str = ""
|
|
119
107
|
|
|
120
|
-
service_name: ClassVar = "tumblrbot"
|
|
121
|
-
username: ClassVar = "tokens"
|
|
122
|
-
|
|
123
108
|
openai_api_key: str = ""
|
|
124
109
|
tumblr: Tumblr = Tumblr()
|
|
125
110
|
|
|
126
|
-
@staticmethod
|
|
127
|
-
def get_oauth_tokens(token: dict[str, str]) -> tuple[str, str]:
|
|
128
|
-
return token["oauth_token"], token["oauth_token_secret"]
|
|
129
|
-
|
|
130
|
-
@staticmethod
|
|
131
|
-
def online_token_prompt(url: str, *tokens: str) -> Generator[str]:
|
|
132
|
-
formatted_token_string = " and ".join(f"[cyan]{token}[/]" for token in tokens)
|
|
133
|
-
|
|
134
|
-
rich.print(f"Retrieve your {formatted_token_string} from: {url}")
|
|
135
|
-
for token in tokens:
|
|
136
|
-
yield pwinput(f"Enter your {token} (masked): ").strip()
|
|
137
|
-
|
|
138
|
-
rich.print()
|
|
139
|
-
|
|
140
|
-
@classmethod
|
|
141
|
-
@override
|
|
142
|
-
def read(cls) -> str | None:
|
|
143
|
-
return get_password(cls.service_name, cls.username)
|
|
144
|
-
|
|
145
|
-
@model_validator(mode="after")
|
|
146
111
|
@override
|
|
147
|
-
def
|
|
112
|
+
def model_post_init(self, _: object) -> None:
|
|
148
113
|
# Check if any tokens are missing or if the user wants to reset them, then set tokens if necessary.
|
|
149
|
-
if not self.openai_api_key
|
|
114
|
+
if not self.openai_api_key:
|
|
150
115
|
(self.openai_api_key,) = self.online_token_prompt("https://platform.openai.com/api-keys", "API key")
|
|
151
116
|
|
|
152
|
-
if not all(self.tumblr.model_dump().values())
|
|
117
|
+
if not all(self.tumblr.model_dump().values()):
|
|
153
118
|
self.tumblr.client_key, self.tumblr.client_secret = self.online_token_prompt("https://tumblr.com/oauth/apps", "consumer key", "consumer secret")
|
|
154
119
|
|
|
155
120
|
# This is the whole OAuth 1.0 process.
|
|
@@ -174,11 +139,19 @@ class Tokens(FileSyncSettings):
|
|
|
174
139
|
|
|
175
140
|
self.tumblr.resource_owner_key, self.tumblr.resource_owner_secret = self.get_oauth_tokens(oauth_tokens)
|
|
176
141
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
142
|
+
@staticmethod
|
|
143
|
+
def online_token_prompt(url: str, *tokens: str) -> Generator[str]:
|
|
144
|
+
formatted_token_string = " and ".join(f"[cyan]{token}[/]" for token in tokens)
|
|
180
145
|
|
|
181
|
-
|
|
146
|
+
rich.print(f"Retrieve your {formatted_token_string} from: {url}")
|
|
147
|
+
for token in tokens:
|
|
148
|
+
yield pwinput(f"Enter your {token} (masked): ").strip()
|
|
149
|
+
|
|
150
|
+
rich.print()
|
|
151
|
+
|
|
152
|
+
@staticmethod
|
|
153
|
+
def get_oauth_tokens(token: dict[str, str]) -> tuple[str, str]:
|
|
154
|
+
return token["oauth_token"], token["oauth_token_secret"]
|
|
182
155
|
|
|
183
156
|
|
|
184
157
|
class Blog(FullyValidatedModel):
|
|
@@ -209,7 +182,7 @@ class Post(FullyValidatedModel):
|
|
|
209
182
|
|
|
210
183
|
timestamp: SkipJsonSchema[int] = 0
|
|
211
184
|
tags: Annotated[list[str], PlainSerializer(",".join)] = []
|
|
212
|
-
state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "
|
|
185
|
+
state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "draft"
|
|
213
186
|
|
|
214
187
|
content: SkipJsonSchema[list[Block]] = []
|
|
215
188
|
layout: SkipJsonSchema[list[Block]] = []
|
|
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
|