tumblrbot 1.9.2__tar.gz → 1.9.4__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.2 → tumblrbot-1.9.4}/.gitignore +1 -1
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/PKG-INFO +2 -5
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/README.md +0 -3
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/pyproject.toml +2 -2
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/flow/generate.py +0 -1
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/utils/models.py +40 -67
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/.github/FUNDING.yml +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/.github/dependabot.yml +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/UNLICENSE +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/sample_custom_prompts.jsonl +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/__init__.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/__main__.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/flow/__init__.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/flow/download.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/flow/examples.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/flow/fine_tune.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/utils/__init__.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/utils/common.py +0 -0
- {tumblrbot-1.9.2 → tumblrbot-1.9.4}/src/tumblrbot/utils/tumblr.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tumblrbot
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.4
|
|
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:
|
|
7
|
+
Requires-Dist: click
|
|
8
8
|
Requires-Dist: openai
|
|
9
9
|
Requires-Dist: pwinput
|
|
10
10
|
Requires-Dist: pydantic
|
|
@@ -25,7 +25,6 @@ Project-URL: Source, https://github.com/MaidScientistIzutsumiMarin/tumblrbot
|
|
|
25
25
|
[JSON Lines Validator]: https://jsonlines.org/validator
|
|
26
26
|
|
|
27
27
|
[pip]: https://pypi.org
|
|
28
|
-
[keyring]: https://pypi.org/project/keyring
|
|
29
28
|
[Rich]: https://pypi.org/project/rich
|
|
30
29
|
|
|
31
30
|
[OpenAI]: https://pypi.org/project/openai
|
|
@@ -60,7 +59,6 @@ Features:
|
|
|
60
59
|
|
|
61
60
|
- An [interactive console][Main] for all steps of generating posts for the blog:
|
|
62
61
|
1. Asks for [OpenAI] and [Tumblr] tokens.
|
|
63
|
-
- Stores API tokens using [keyring].
|
|
64
62
|
1. Retrieves [Tumblr] [OAuth] tokens.
|
|
65
63
|
1. [Downloads posts][Download] from specified blogs ([configurable]).
|
|
66
64
|
- Skips redownloading already downloaded posts.
|
|
@@ -102,7 +100,6 @@ Features:
|
|
|
102
100
|
1. Install the [pip] package: `pip install tumblrbot`
|
|
103
101
|
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
104
102
|
- 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
103
|
|
|
107
104
|
## Usage
|
|
108
105
|
|
|
@@ -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
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tumblrbot"
|
|
3
|
-
version = "1.9.
|
|
3
|
+
version = "1.9.4"
|
|
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
|
-
"
|
|
8
|
+
"click",
|
|
9
9
|
"openai",
|
|
10
10
|
"pwinput",
|
|
11
11
|
"pydantic",
|
|
@@ -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
|