tumblrbot 1.2.0__tar.gz → 1.3.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.3.0/PKG-INFO +114 -0
- tumblrbot-1.3.0/README.md +95 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/pyproject.toml +1 -1
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/__main__.py +4 -3
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/flow/download.py +2 -2
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/flow/examples.py +12 -12
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/flow/fine_tune.py +2 -2
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/flow/generate.py +4 -4
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/utils/common.py +2 -2
- tumblrbot-1.2.0/src/tumblrbot/utils/settings.py → tumblrbot-1.3.0/src/tumblrbot/utils/config.py +3 -73
- tumblrbot-1.3.0/src/tumblrbot/utils/models.py +131 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/utils/tumblr.py +4 -19
- tumblrbot-1.2.0/PKG-INFO +0 -109
- tumblrbot-1.2.0/README.md +0 -90
- tumblrbot-1.2.0/src/tumblrbot/utils/models.py +0 -63
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/.github/dependabot.yml +0 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/.gitignore +0 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/UNLICENSE +0 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/__init__.py +0 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/flow/__init__.py +0 -0
- {tumblrbot-1.2.0 → tumblrbot-1.3.0}/src/tumblrbot/utils/__init__.py +0 -0
tumblrbot-1.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tumblrbot
|
|
3
|
+
Version: 1.3.0
|
|
4
|
+
Summary: An updated bot that posts to Tumblr, based on your very own blog!
|
|
5
|
+
Requires-Python: >= 3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: keyring
|
|
8
|
+
Requires-Dist: more-itertools
|
|
9
|
+
Requires-Dist: openai
|
|
10
|
+
Requires-Dist: pydantic
|
|
11
|
+
Requires-Dist: pydantic-settings
|
|
12
|
+
Requires-Dist: requests
|
|
13
|
+
Requires-Dist: requests-oauthlib
|
|
14
|
+
Requires-Dist: rich
|
|
15
|
+
Requires-Dist: tiktoken
|
|
16
|
+
Requires-Dist: tomlkit
|
|
17
|
+
Project-URL: Source, https://github.com/MaidThatPrograms/tumblrbot
|
|
18
|
+
|
|
19
|
+
[OAuth]: https://oauth.net/1
|
|
20
|
+
[OpenAI]: https://pypi.org/project/openai
|
|
21
|
+
[Python]: https://python.org/download
|
|
22
|
+
[Tumblr]: https://tumblr.com
|
|
23
|
+
|
|
24
|
+
[keyring]: https://pypi.org/project/keyring
|
|
25
|
+
[Rich]: https://pypi.org/project/rich
|
|
26
|
+
|
|
27
|
+
[Moderation API]: https://platform.openai.com/docs/api-reference/moderations
|
|
28
|
+
[pip]: https://pypi.org
|
|
29
|
+
|
|
30
|
+
[Download]: src/tumblrbot/flow/download.py
|
|
31
|
+
[Examples]: src/tumblrbot/flow/examples.py
|
|
32
|
+
[Fine-Tune]: src/tumblrbot/flow/fine_tune.py
|
|
33
|
+
[Generate]: src/tumblrbot/flow/generate.py
|
|
34
|
+
[Main]: src/tumblrbot/__main__.py
|
|
35
|
+
[README.md]: README.md
|
|
36
|
+
|
|
37
|
+
[config]: #configuration
|
|
38
|
+
|
|
39
|
+
# tumblrbot
|
|
40
|
+
[](https://python.org/pypi/tumblrbot)
|
|
41
|
+
|
|
42
|
+
Description of original project:
|
|
43
|
+
> 4tv-tumblrbot was a collaborative project I embarked on with my close friend Dima, who goes by @smoqueen on Tumblr. The aim of this endeavor was straightforward yet silly: to develop a Tumblr bot powered by a machine-learning model. This bot would be specifically trained on the content from a particular Tumblr blog or a selected set of blogs, allowing it to mimic the style, tone, and thematic essence of the original posts.
|
|
44
|
+
|
|
45
|
+
This fork is largely a rewrite of the source code with similarities in its structure and process.
|
|
46
|
+
|
|
47
|
+
Features:
|
|
48
|
+
- An [interactive console][Main] for all steps of generating posts for the blog:
|
|
49
|
+
1. Asks for [OpenAI] and [Tumblr] tokens.
|
|
50
|
+
- Stores API tokens using [keyring].
|
|
51
|
+
- Prevents API tokens from printing to the console.
|
|
52
|
+
1. Retrieves [Tumblr] [OAuth] tokens.
|
|
53
|
+
1. [Downloads posts][Download] from the [configured][config] [Tumblr] blogs.
|
|
54
|
+
- Skips redownloading already downloaded posts.
|
|
55
|
+
- Shows progress and previews the current post.
|
|
56
|
+
1. [Creates examples][Examples] to fine-tune the model from your posts.
|
|
57
|
+
- Filters out posts that contain more than just text data.
|
|
58
|
+
- Filters out any posts flagged by the [OpenAI] [Moderation API] (optional).
|
|
59
|
+
- Shows progress and previews the current post.
|
|
60
|
+
- Formats asks as the user message and the responses as the assistant response.
|
|
61
|
+
- Adds custom user messages and assistant responses to the dataset from the [configured][config] file.
|
|
62
|
+
1. Provides cost estimates if the currently saved examples are used to fine-tune the [configured][config] model.
|
|
63
|
+
1. [Uploads examples][Fine-Tune] to [OpenAI] and begins the fine-tuning process.
|
|
64
|
+
- Resumes monitoring the same fine-tuning process when restarted.
|
|
65
|
+
- Stores the output model automatically when fine-tuning is completed.
|
|
66
|
+
1. [Generates and uploads posts][Generate] to the [configured][config] [Tumblr] blog using the [configured][config] fine-tuned model.
|
|
67
|
+
- Creates tags by extracting keywords at the [configured][config] frequency using the [configured][config] model.
|
|
68
|
+
- Uploads posts as drafts to the [configured][config] [Tumblr] blog.
|
|
69
|
+
- Shows progress and previews the current post.
|
|
70
|
+
- Colorful output, progress bars, and post previews using [rich].
|
|
71
|
+
- Automatically keeps the [config] file up-to-date and recreates it if missing.
|
|
72
|
+
|
|
73
|
+
**To-Do:**
|
|
74
|
+
- Add documentation.
|
|
75
|
+
- Finish updating [README.md].
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
**Please submit an issue or contact us for features you want added/reimplemented.**
|
|
79
|
+
|
|
80
|
+
## Installation
|
|
81
|
+
1. Install the latest version of [Python]:
|
|
82
|
+
- Windows: `winget install python3`
|
|
83
|
+
- Linux (apt): `apt install python-pip`
|
|
84
|
+
- Linux (pacman): `pacman install python-pip`
|
|
85
|
+
1. Install the [pip] package: `pip install tumblrbot`
|
|
86
|
+
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
87
|
+
- On Linux, you will have to make a virtual environment.
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
Run `tumblrbot` from anywhere. Run `tumblrbot --help` for command-line options. Every command-line option corresponds to a value from the [config](#configuration).
|
|
91
|
+
|
|
92
|
+
## Obtaining Tokens
|
|
93
|
+
- The [OpenAI] API token can be created [here](https://platform.openai.com/settings/organization/api-keys).
|
|
94
|
+
1. Leave everything at the defaults and set `Project` to `Default Project`.
|
|
95
|
+
1. Press `Create secret key`.
|
|
96
|
+
1. Press `Copy` to copy the API token to your clipboard.
|
|
97
|
+
- The [Tumblr] API tokens can be created [here](https://tumblr.com/oauth/apps).
|
|
98
|
+
1. Press `+ Register Application`.
|
|
99
|
+
1. Enter anything for `Application Name` and `Application Description`.
|
|
100
|
+
1. Enter any URL for `Application Website` and `Default callback URL`, like `https://example.com`.
|
|
101
|
+
1. Enter any email address for `Administrative contact email`. It probably doesn't need to be one you have access to.
|
|
102
|
+
1. Press the checkbox next to `I'm not a robot` and complete the CAPTCHA.
|
|
103
|
+
1. Press `Register`.
|
|
104
|
+
1. You now have access to your `consumer key` next to `Oauth Consumer Key`.
|
|
105
|
+
1. Press `Show secret key` to see your `Consumer Secret`.
|
|
106
|
+
|
|
107
|
+
When running this program, you will be prompted to enter all of these tokens. **The fields are password-protected, so there will be no output to the console.** If something goes wrong while entering the tokens, you can always reset them by running the program again and answering `y` to the relevant prompt.
|
|
108
|
+
|
|
109
|
+
After inputting the [Tumblr] tokens, you will be given a URL that you need to open in your browser. Press `Allow`, then copy and paste the URL of the page you are redirected to into the console.
|
|
110
|
+
|
|
111
|
+
## Configuration
|
|
112
|
+
All config options can be found in `config.toml` after running the program once. This will be kept up-to-date if there are changes to the config's format in a future update. This also means it may be worthwhile to double-check the config file after an update. Any changes to the config should be in the changelog for a given version.
|
|
113
|
+
> WIP: There will be more information about the config options soon.
|
|
114
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[OAuth]: https://oauth.net/1
|
|
2
|
+
[OpenAI]: https://pypi.org/project/openai
|
|
3
|
+
[Python]: https://python.org/download
|
|
4
|
+
[Tumblr]: https://tumblr.com
|
|
5
|
+
|
|
6
|
+
[keyring]: https://pypi.org/project/keyring
|
|
7
|
+
[Rich]: https://pypi.org/project/rich
|
|
8
|
+
|
|
9
|
+
[Moderation API]: https://platform.openai.com/docs/api-reference/moderations
|
|
10
|
+
[pip]: https://pypi.org
|
|
11
|
+
|
|
12
|
+
[Download]: src/tumblrbot/flow/download.py
|
|
13
|
+
[Examples]: src/tumblrbot/flow/examples.py
|
|
14
|
+
[Fine-Tune]: src/tumblrbot/flow/fine_tune.py
|
|
15
|
+
[Generate]: src/tumblrbot/flow/generate.py
|
|
16
|
+
[Main]: src/tumblrbot/__main__.py
|
|
17
|
+
[README.md]: README.md
|
|
18
|
+
|
|
19
|
+
[config]: #configuration
|
|
20
|
+
|
|
21
|
+
# tumblrbot
|
|
22
|
+
[](https://python.org/pypi/tumblrbot)
|
|
23
|
+
|
|
24
|
+
Description of original project:
|
|
25
|
+
> 4tv-tumblrbot was a collaborative project I embarked on with my close friend Dima, who goes by @smoqueen on Tumblr. The aim of this endeavor was straightforward yet silly: to develop a Tumblr bot powered by a machine-learning model. This bot would be specifically trained on the content from a particular Tumblr blog or a selected set of blogs, allowing it to mimic the style, tone, and thematic essence of the original posts.
|
|
26
|
+
|
|
27
|
+
This fork is largely a rewrite of the source code with similarities in its structure and process.
|
|
28
|
+
|
|
29
|
+
Features:
|
|
30
|
+
- An [interactive console][Main] for all steps of generating posts for the blog:
|
|
31
|
+
1. Asks for [OpenAI] and [Tumblr] tokens.
|
|
32
|
+
- Stores API tokens using [keyring].
|
|
33
|
+
- Prevents API tokens from printing to the console.
|
|
34
|
+
1. Retrieves [Tumblr] [OAuth] tokens.
|
|
35
|
+
1. [Downloads posts][Download] from the [configured][config] [Tumblr] blogs.
|
|
36
|
+
- Skips redownloading already downloaded posts.
|
|
37
|
+
- Shows progress and previews the current post.
|
|
38
|
+
1. [Creates examples][Examples] to fine-tune the model from your posts.
|
|
39
|
+
- Filters out posts that contain more than just text data.
|
|
40
|
+
- Filters out any posts flagged by the [OpenAI] [Moderation API] (optional).
|
|
41
|
+
- Shows progress and previews the current post.
|
|
42
|
+
- Formats asks as the user message and the responses as the assistant response.
|
|
43
|
+
- Adds custom user messages and assistant responses to the dataset from the [configured][config] file.
|
|
44
|
+
1. Provides cost estimates if the currently saved examples are used to fine-tune the [configured][config] model.
|
|
45
|
+
1. [Uploads examples][Fine-Tune] to [OpenAI] and begins the fine-tuning process.
|
|
46
|
+
- Resumes monitoring the same fine-tuning process when restarted.
|
|
47
|
+
- Stores the output model automatically when fine-tuning is completed.
|
|
48
|
+
1. [Generates and uploads posts][Generate] to the [configured][config] [Tumblr] blog using the [configured][config] fine-tuned model.
|
|
49
|
+
- Creates tags by extracting keywords at the [configured][config] frequency using the [configured][config] model.
|
|
50
|
+
- Uploads posts as drafts to the [configured][config] [Tumblr] blog.
|
|
51
|
+
- Shows progress and previews the current post.
|
|
52
|
+
- Colorful output, progress bars, and post previews using [rich].
|
|
53
|
+
- Automatically keeps the [config] file up-to-date and recreates it if missing.
|
|
54
|
+
|
|
55
|
+
**To-Do:**
|
|
56
|
+
- Add documentation.
|
|
57
|
+
- Finish updating [README.md].
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
**Please submit an issue or contact us for features you want added/reimplemented.**
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
1. Install the latest version of [Python]:
|
|
64
|
+
- Windows: `winget install python3`
|
|
65
|
+
- Linux (apt): `apt install python-pip`
|
|
66
|
+
- Linux (pacman): `pacman install python-pip`
|
|
67
|
+
1. Install the [pip] package: `pip install tumblrbot`
|
|
68
|
+
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
69
|
+
- On Linux, you will have to make a virtual environment.
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
Run `tumblrbot` from anywhere. Run `tumblrbot --help` for command-line options. Every command-line option corresponds to a value from the [config](#configuration).
|
|
73
|
+
|
|
74
|
+
## Obtaining Tokens
|
|
75
|
+
- The [OpenAI] API token can be created [here](https://platform.openai.com/settings/organization/api-keys).
|
|
76
|
+
1. Leave everything at the defaults and set `Project` to `Default Project`.
|
|
77
|
+
1. Press `Create secret key`.
|
|
78
|
+
1. Press `Copy` to copy the API token to your clipboard.
|
|
79
|
+
- The [Tumblr] API tokens can be created [here](https://tumblr.com/oauth/apps).
|
|
80
|
+
1. Press `+ Register Application`.
|
|
81
|
+
1. Enter anything for `Application Name` and `Application Description`.
|
|
82
|
+
1. Enter any URL for `Application Website` and `Default callback URL`, like `https://example.com`.
|
|
83
|
+
1. Enter any email address for `Administrative contact email`. It probably doesn't need to be one you have access to.
|
|
84
|
+
1. Press the checkbox next to `I'm not a robot` and complete the CAPTCHA.
|
|
85
|
+
1. Press `Register`.
|
|
86
|
+
1. You now have access to your `consumer key` next to `Oauth Consumer Key`.
|
|
87
|
+
1. Press `Show secret key` to see your `Consumer Secret`.
|
|
88
|
+
|
|
89
|
+
When running this program, you will be prompted to enter all of these tokens. **The fields are password-protected, so there will be no output to the console.** If something goes wrong while entering the tokens, you can always reset them by running the program again and answering `y` to the relevant prompt.
|
|
90
|
+
|
|
91
|
+
After inputting the [Tumblr] tokens, you will be given a URL that you need to open in your browser. Press `Allow`, then copy and paste the URL of the page you are redirected to into the console.
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
All config options can be found in `config.toml` after running the program once. This will be kept up-to-date if there are changes to the config's format in a future update. This also means it may be worthwhile to double-check the config file after an update. Any changes to the config should be in the changelog for a given version.
|
|
95
|
+
> WIP: There will be more information about the config options soon.
|
|
@@ -6,14 +6,15 @@ from tumblrbot.flow.download import PostDownloader
|
|
|
6
6
|
from tumblrbot.flow.examples import ExamplesWriter
|
|
7
7
|
from tumblrbot.flow.fine_tune import FineTuner
|
|
8
8
|
from tumblrbot.flow.generate import DraftGenerator
|
|
9
|
-
from tumblrbot.utils.
|
|
10
|
-
from tumblrbot.utils.
|
|
9
|
+
from tumblrbot.utils.models import Tokens
|
|
10
|
+
from tumblrbot.utils.tumblr import TumblrClient
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def main() -> None:
|
|
14
14
|
install()
|
|
15
|
+
|
|
15
16
|
tokens = Tokens()
|
|
16
|
-
with OpenAI(api_key=tokens.openai_api_key.get_secret_value()) as openai, TumblrClient(tokens) as tumblr:
|
|
17
|
+
with OpenAI(api_key=tokens.openai_api_key.get_secret_value()) as openai, TumblrClient(tokens=tokens) as tumblr:
|
|
17
18
|
post_downloader = PostDownloader(openai, tumblr)
|
|
18
19
|
if Confirm.ask("Download latest posts?", default=False):
|
|
19
20
|
post_downloader.download()
|
|
@@ -2,11 +2,11 @@ from io import TextIOBase
|
|
|
2
2
|
from json import dump
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
|
-
from tumblrbot.utils.common import
|
|
5
|
+
from tumblrbot.utils.common import FlowClass, PreviewLive
|
|
6
6
|
from tumblrbot.utils.models import Post
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class PostDownloader(
|
|
9
|
+
class PostDownloader(FlowClass):
|
|
10
10
|
def paginate_posts(self, blog_identifier: str, completed: int, after: int, fp: TextIOBase, live: PreviewLive) -> None:
|
|
11
11
|
task_id = live.progress.add_task(f"Downloading posts from '{blog_identifier}'...", total=None, completed=completed)
|
|
12
12
|
|
|
@@ -8,17 +8,18 @@ from typing import IO
|
|
|
8
8
|
|
|
9
9
|
import rich
|
|
10
10
|
from more_itertools import chunked
|
|
11
|
-
from openai import BadRequestError
|
|
11
|
+
from openai import BadRequestError, OpenAI
|
|
12
12
|
from rich.console import Console
|
|
13
13
|
from rich.prompt import Confirm
|
|
14
14
|
from tiktoken import encoding_for_model, get_encoding
|
|
15
15
|
|
|
16
|
-
from tumblrbot.utils.common import
|
|
16
|
+
from tumblrbot.utils.common import FlowClass, PreviewLive
|
|
17
17
|
from tumblrbot.utils.models import Example, Post
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@dataclass
|
|
21
|
-
class ExamplesWriter(
|
|
21
|
+
class ExamplesWriter(FlowClass):
|
|
22
|
+
openai: OpenAI
|
|
22
23
|
data_paths: list[Path]
|
|
23
24
|
|
|
24
25
|
def count_tokens(self) -> Generator[int]:
|
|
@@ -52,7 +53,7 @@ class ExamplesWriter(UtilClass):
|
|
|
52
53
|
with data_path.open(encoding="utf_8") as fp:
|
|
53
54
|
for line in fp:
|
|
54
55
|
post = Post.model_validate_json(line)
|
|
55
|
-
if
|
|
56
|
+
if not (post.is_submission or post.trail) and post.only_text_blocks() and post.get_response_content():
|
|
56
57
|
yield post
|
|
57
58
|
|
|
58
59
|
def get_filtered_posts(self) -> Generator[Post]:
|
|
@@ -67,7 +68,7 @@ class ExamplesWriter(UtilClass):
|
|
|
67
68
|
ceil(len(posts) / chunk_size),
|
|
68
69
|
description="Removing flagged posts...",
|
|
69
70
|
):
|
|
70
|
-
response = self.openai.moderations.create(input=
|
|
71
|
+
response = self.openai.moderations.create(input=["\n".join(post.get_text_content()) for post in chunk])
|
|
71
72
|
for post, moderation in zip(chunk, response.results, strict=True):
|
|
72
73
|
if moderation.flagged:
|
|
73
74
|
removed += 1
|
|
@@ -88,9 +89,11 @@ class ExamplesWriter(UtilClass):
|
|
|
88
89
|
|
|
89
90
|
with self.config.examples_file.open("w", encoding="utf_8") as fp:
|
|
90
91
|
for post in self.get_filtered_posts():
|
|
92
|
+
ask_content, response_content = post.get_text_content()
|
|
93
|
+
|
|
91
94
|
self.write_example(
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
ask_content or self.config.user_message,
|
|
96
|
+
response_content,
|
|
94
97
|
fp,
|
|
95
98
|
)
|
|
96
99
|
|
|
@@ -103,15 +106,12 @@ class ExamplesWriter(UtilClass):
|
|
|
103
106
|
|
|
104
107
|
rich.print(f"[bold]The examples file can be found at: '{self.config.examples_file}'\n")
|
|
105
108
|
|
|
106
|
-
def write_example(self,
|
|
109
|
+
def write_example(self, user_message: str, assistant_message: str, fp: IO[str]) -> None:
|
|
107
110
|
example = Example(
|
|
108
111
|
messages=[
|
|
109
112
|
Example.Message(role="developer", content=self.config.developer_message),
|
|
113
|
+
Example.Message(role="user", content=user_message),
|
|
110
114
|
Example.Message(role="assistant", content=assistant_message),
|
|
111
115
|
],
|
|
112
116
|
)
|
|
113
|
-
|
|
114
|
-
if user_input:
|
|
115
|
-
example.messages.insert(1, Example.Message(role="user", content=user_input))
|
|
116
|
-
|
|
117
117
|
fp.write(f"{example.model_dump_json()}\n")
|
|
@@ -6,11 +6,11 @@ from time import sleep
|
|
|
6
6
|
import rich
|
|
7
7
|
from openai.types.fine_tuning import FineTuningJob
|
|
8
8
|
|
|
9
|
-
from tumblrbot.utils.common import
|
|
9
|
+
from tumblrbot.utils.common import FlowClass, PreviewLive
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@dataclass
|
|
13
|
-
class FineTuner(
|
|
13
|
+
class FineTuner(FlowClass):
|
|
14
14
|
estimated_tokens: int
|
|
15
15
|
|
|
16
16
|
@staticmethod
|
|
@@ -2,11 +2,11 @@ from random import random
|
|
|
2
2
|
|
|
3
3
|
import rich
|
|
4
4
|
|
|
5
|
-
from tumblrbot.utils.common import
|
|
5
|
+
from tumblrbot.utils.common import FlowClass, PreviewLive
|
|
6
6
|
from tumblrbot.utils.models import Post
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class DraftGenerator(
|
|
9
|
+
class DraftGenerator(FlowClass):
|
|
10
10
|
def generate_tags(self, content: Post.Block) -> Post | None:
|
|
11
11
|
if random() < self.config.tags_chance: # noqa: S311
|
|
12
12
|
return self.openai.responses.parse(
|
|
@@ -47,8 +47,8 @@ class DraftGenerator(UtilClass):
|
|
|
47
47
|
post = self.generate_post()
|
|
48
48
|
self.tumblr.create_post(self.config.upload_blog_identifier, post)
|
|
49
49
|
live.custom_update(post)
|
|
50
|
-
except BaseException as
|
|
51
|
-
|
|
50
|
+
except BaseException as exception:
|
|
51
|
+
exception.add_note(f"📉 An error occurred! Generated {i} draft(s) before failing. {message}")
|
|
52
52
|
raise
|
|
53
53
|
|
|
54
54
|
rich.print(f":chart_increasing: [bold green]Generated {self.config.draft_count} draft(s).[/] {message}")
|
|
@@ -9,7 +9,7 @@ from rich.live import Live
|
|
|
9
9
|
from rich.progress import MofNCompleteColumn, Progress, SpinnerColumn, TimeElapsedColumn
|
|
10
10
|
from rich.table import Table
|
|
11
11
|
|
|
12
|
-
from tumblrbot.utils.
|
|
12
|
+
from tumblrbot.utils.config import Config
|
|
13
13
|
from tumblrbot.utils.tumblr import TumblrClient
|
|
14
14
|
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ class PreviewLive(Live):
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@dataclass
|
|
44
|
-
class
|
|
44
|
+
class FlowClass:
|
|
45
45
|
config: ClassVar = Config() # pyright: ignore[reportCallIssue]
|
|
46
46
|
|
|
47
47
|
openai: OpenAI
|
tumblrbot-1.2.0/src/tumblrbot/utils/settings.py → tumblrbot-1.3.0/src/tumblrbot/utils/config.py
RENAMED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
from collections.abc import Generator, Sequence
|
|
1
|
+
from collections.abc import Sequence
|
|
3
2
|
from pathlib import Path
|
|
4
|
-
from typing import TYPE_CHECKING,
|
|
3
|
+
from typing import TYPE_CHECKING, Self, override
|
|
5
4
|
|
|
6
5
|
import rich
|
|
7
6
|
import tomlkit
|
|
8
|
-
from keyring import get_password, set_password
|
|
9
7
|
from openai.types import ChatModel
|
|
10
8
|
from pydantic import Field, PositiveFloat, PositiveInt, Secret, model_validator
|
|
11
9
|
from pydantic_settings import BaseSettings, PydanticBaseSettingsSource, SettingsConfigDict, TomlConfigSettingsSource
|
|
12
|
-
from
|
|
13
|
-
from rich.prompt import Confirm, Prompt
|
|
10
|
+
from rich.prompt import Prompt
|
|
14
11
|
from tomlkit import comment, document
|
|
15
12
|
|
|
16
13
|
if TYPE_CHECKING:
|
|
@@ -92,70 +89,3 @@ class Config(BaseSettings):
|
|
|
92
89
|
tomlkit.dumps(toml_table), # pyright: ignore[reportUnknownMemberType]
|
|
93
90
|
encoding="utf_8",
|
|
94
91
|
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
class Tokens(BaseSettings):
|
|
98
|
-
service_name: ClassVar = "tumblrbot"
|
|
99
|
-
model_config = SettingsConfigDict(toml_file="env.toml")
|
|
100
|
-
|
|
101
|
-
openai_api_key: Secret[str] = Secret("")
|
|
102
|
-
tumblr_client_id: Secret[str] = Secret("")
|
|
103
|
-
tumblr_client_secret: Secret[str] = Secret("")
|
|
104
|
-
tumblr_token: Secret[Any] = Secret({})
|
|
105
|
-
|
|
106
|
-
@staticmethod
|
|
107
|
-
def online_token_prompt(url: str, *tokens: str) -> Generator[Secret[str]]:
|
|
108
|
-
formatted_tokens = [f"[cyan]{token}[/]" for token in tokens]
|
|
109
|
-
formatted_token_string = " and ".join(formatted_tokens)
|
|
110
|
-
|
|
111
|
-
rich.print(f"Retrieve your {formatted_token_string} from: {url}")
|
|
112
|
-
for token in formatted_tokens:
|
|
113
|
-
prompt = f"Enter your {token} [yellow](hidden)"
|
|
114
|
-
yield Secret(Prompt.ask(prompt, password=True).strip())
|
|
115
|
-
|
|
116
|
-
rich.print()
|
|
117
|
-
|
|
118
|
-
@override
|
|
119
|
-
def model_post_init(self, context: object) -> None:
|
|
120
|
-
super().model_post_init(context)
|
|
121
|
-
|
|
122
|
-
for name, _ in self:
|
|
123
|
-
if value := get_password(self.service_name, name):
|
|
124
|
-
setattr(self, name, Secret(json.loads(value)))
|
|
125
|
-
|
|
126
|
-
@model_validator(mode="after")
|
|
127
|
-
def write_to_keyring(self) -> Self:
|
|
128
|
-
if not self.openai_api_key.get_secret_value() or Confirm.ask("Reset OpenAI API key?", default=False):
|
|
129
|
-
(self.openai_api_key,) = self.online_token_prompt("https://platform.openai.com/api-keys", "API key")
|
|
130
|
-
|
|
131
|
-
if not all(
|
|
132
|
-
map(
|
|
133
|
-
Secret[Any].get_secret_value,
|
|
134
|
-
[
|
|
135
|
-
self.tumblr_client_id,
|
|
136
|
-
self.tumblr_client_secret,
|
|
137
|
-
self.tumblr_token,
|
|
138
|
-
],
|
|
139
|
-
),
|
|
140
|
-
) or Confirm.ask("Reset Tumblr API tokens?", default=False):
|
|
141
|
-
self.tumblr_client_id, self.tumblr_client_secret = self.online_token_prompt("https://tumblr.com/oauth/apps", "consumer key", "consumer secret")
|
|
142
|
-
|
|
143
|
-
oauth = OAuth2Session(
|
|
144
|
-
self.tumblr_client_id.get_secret_value(),
|
|
145
|
-
scope=["basic", "write", "offline_access"],
|
|
146
|
-
)
|
|
147
|
-
authorization_url, _ = oauth.authorization_url("https://tumblr.com/oauth2/authorize") # pyright: ignore[reportUnknownMemberType]
|
|
148
|
-
rich.print(f"Please go to {authorization_url} and authorize access.")
|
|
149
|
-
self.tumblr_token = Secret(
|
|
150
|
-
oauth.fetch_token( # pyright: ignore[reportUnknownMemberType]
|
|
151
|
-
"https://api.tumblr.com/v2/oauth2/token",
|
|
152
|
-
authorization_response=Prompt.ask("Enter the full callback URL"),
|
|
153
|
-
client_secret=self.tumblr_client_secret.get_secret_value(),
|
|
154
|
-
),
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
for name, value in self:
|
|
158
|
-
if isinstance(value, Secret):
|
|
159
|
-
set_password(self.service_name, name, json.dumps(value.get_secret_value()))
|
|
160
|
-
|
|
161
|
-
return self
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from typing import Annotated, Any, ClassVar, Literal, override
|
|
3
|
+
|
|
4
|
+
import rich
|
|
5
|
+
from keyring import get_password, set_password
|
|
6
|
+
from openai import BaseModel
|
|
7
|
+
from pydantic import ConfigDict, PlainSerializer, SecretStr
|
|
8
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
9
|
+
from requests_oauthlib import OAuth1Session
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.prompt import Confirm, Prompt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FullyValidatedModel(BaseModel):
|
|
15
|
+
model_config = ConfigDict(
|
|
16
|
+
extra="ignore",
|
|
17
|
+
validate_assignment=True,
|
|
18
|
+
validate_default=True,
|
|
19
|
+
validate_return=True,
|
|
20
|
+
validate_by_name=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Tokens(FullyValidatedModel):
|
|
25
|
+
service_name: ClassVar = "tumblrbot"
|
|
26
|
+
|
|
27
|
+
openai_api_key: SecretStr = SecretStr("")
|
|
28
|
+
tumblr_client_key: SecretStr = SecretStr("")
|
|
29
|
+
tumblr_client_secret: SecretStr = SecretStr("")
|
|
30
|
+
tumblr_resource_owner_key: SecretStr = SecretStr("")
|
|
31
|
+
tumblr_resource_owner_secret: SecretStr = SecretStr("")
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def online_token_prompt(url: str, *tokens: str) -> Generator[SecretStr]:
|
|
35
|
+
formatted_tokens = [f"[cyan]{token}[/]" for token in tokens]
|
|
36
|
+
formatted_token_string = " and ".join(formatted_tokens)
|
|
37
|
+
|
|
38
|
+
rich.print(f"Retrieve your {formatted_token_string} from: {url}")
|
|
39
|
+
for token in formatted_tokens:
|
|
40
|
+
prompt = f"Enter your {token} [yellow](hidden)"
|
|
41
|
+
yield SecretStr(Prompt.ask(prompt, password=True).strip())
|
|
42
|
+
|
|
43
|
+
rich.print()
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
def model_post_init(self, context: object) -> None:
|
|
47
|
+
super().model_post_init(context)
|
|
48
|
+
|
|
49
|
+
for name, _ in self:
|
|
50
|
+
if value := get_password(self.service_name, name):
|
|
51
|
+
setattr(self, name, value)
|
|
52
|
+
|
|
53
|
+
if not self.openai_api_key.get_secret_value() or Confirm.ask("Reset OpenAI API key?", default=False):
|
|
54
|
+
(self.openai_api_key,) = self.online_token_prompt("https://platform.openai.com/api-keys", "API key")
|
|
55
|
+
|
|
56
|
+
if not all(self.get_tumblr_tokens()) or Confirm.ask("Reset Tumblr API tokens?", default=False):
|
|
57
|
+
self.tumblr_client_key, self.tumblr_client_secret = self.online_token_prompt("https://tumblr.com/oauth/apps", "consumer key", "consumer secret")
|
|
58
|
+
|
|
59
|
+
oauth_session = OAuth1Session(*self.get_tumblr_tokens()[:2])
|
|
60
|
+
fetch_response = oauth_session.fetch_request_token("http://tumblr.com/oauth/request_token") # pyright: ignore[reportUnknownMemberType]
|
|
61
|
+
full_authorize_url = oauth_session.authorization_url("http://tumblr.com/oauth/authorize") # pyright: ignore[reportUnknownMemberType]
|
|
62
|
+
(redirect_response,) = self.online_token_prompt(full_authorize_url, "full redirect URL")
|
|
63
|
+
oauth_response = oauth_session.parse_authorization_response(redirect_response.get_secret_value())
|
|
64
|
+
oauth_session = OAuth1Session(
|
|
65
|
+
*self.get_tumblr_tokens()[:2],
|
|
66
|
+
fetch_response["oauth_token"],
|
|
67
|
+
fetch_response["oauth_token_secret"],
|
|
68
|
+
verifier=oauth_response["oauth_verifier"],
|
|
69
|
+
)
|
|
70
|
+
oauth_tokens = oauth_session.fetch_access_token("http://tumblr.com/oauth/access_token") # pyright: ignore[reportUnknownMemberType]
|
|
71
|
+
self.tumblr_resource_owner_key = oauth_tokens["oauth_token"]
|
|
72
|
+
self.tumblr_resource_owner_secret = oauth_tokens["oauth_token_secret"]
|
|
73
|
+
|
|
74
|
+
for name, value in self:
|
|
75
|
+
if isinstance(value, SecretStr):
|
|
76
|
+
set_password(self.service_name, name, value.get_secret_value())
|
|
77
|
+
|
|
78
|
+
def get_tumblr_tokens(self) -> tuple[str, str, str, str]:
|
|
79
|
+
return (
|
|
80
|
+
self.tumblr_client_key.get_secret_value(),
|
|
81
|
+
self.tumblr_client_secret.get_secret_value(),
|
|
82
|
+
self.tumblr_resource_owner_key.get_secret_value(),
|
|
83
|
+
self.tumblr_resource_owner_secret.get_secret_value(),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class Post(FullyValidatedModel):
|
|
88
|
+
class Block(FullyValidatedModel):
|
|
89
|
+
type: str = ""
|
|
90
|
+
text: str = ""
|
|
91
|
+
blocks: list[int] = [] # noqa: RUF012
|
|
92
|
+
|
|
93
|
+
timestamp: SkipJsonSchema[int] = 0
|
|
94
|
+
tags: Annotated[list[str], PlainSerializer(",".join)] = [] # noqa: RUF012
|
|
95
|
+
state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "published"
|
|
96
|
+
|
|
97
|
+
content: SkipJsonSchema[list[Block]] = [] # noqa: RUF012
|
|
98
|
+
layout: SkipJsonSchema[list[Block]] = [] # noqa: RUF012
|
|
99
|
+
trail: SkipJsonSchema[list[Any]] = [] # noqa: RUF012
|
|
100
|
+
|
|
101
|
+
is_submission: SkipJsonSchema[bool] = False
|
|
102
|
+
|
|
103
|
+
def __rich__(self) -> Panel:
|
|
104
|
+
return Panel(
|
|
105
|
+
self.get_text_content(),
|
|
106
|
+
title="Preview",
|
|
107
|
+
subtitle=" ".join(f"#{tag}" for tag in self.tags),
|
|
108
|
+
subtitle_align="left",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@override
|
|
112
|
+
def model_post_init(self, context: object) -> None:
|
|
113
|
+
super().model_post_init(context)
|
|
114
|
+
|
|
115
|
+
indices: set[int] = set()
|
|
116
|
+
for block in self.layout:
|
|
117
|
+
if block.type == "ask":
|
|
118
|
+
indices.update(block.blocks)
|
|
119
|
+
|
|
120
|
+
self.content = [block for i, block in enumerate(self.content) if i not in indices and block.type == "text"]
|
|
121
|
+
|
|
122
|
+
def get_text_content(self) -> str:
|
|
123
|
+
return "\n\n".join(block.text for block in self.content)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class Example(FullyValidatedModel):
|
|
127
|
+
class Message(FullyValidatedModel):
|
|
128
|
+
role: Literal["developer", "user", "assistant"]
|
|
129
|
+
content: str
|
|
130
|
+
|
|
131
|
+
messages: list[Message]
|
|
@@ -1,35 +1,20 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
from pydantic import Secret
|
|
4
3
|
from requests import HTTPError, Response
|
|
5
|
-
from requests_oauthlib import
|
|
4
|
+
from requests_oauthlib import OAuth1Session
|
|
6
5
|
|
|
7
|
-
from tumblrbot.utils.models import Post
|
|
8
|
-
from tumblrbot.utils.settings import Tokens
|
|
6
|
+
from tumblrbot.utils.models import Post, Tokens
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
@dataclass
|
|
12
|
-
class TumblrClient(
|
|
10
|
+
class TumblrClient(OAuth1Session):
|
|
13
11
|
tokens: Tokens
|
|
14
12
|
|
|
15
13
|
def __post_init__(self) -> None:
|
|
16
|
-
super().__init__( # pyright: ignore[reportUnknownMemberType]
|
|
17
|
-
self.tokens.tumblr_client_id.get_secret_value(),
|
|
18
|
-
auto_refresh_url="https://api.tumblr.com/v2/oauth2/token",
|
|
19
|
-
auto_refresh_kwargs={
|
|
20
|
-
"client_id": self.tokens.tumblr_client_id.get_secret_value(),
|
|
21
|
-
"client_secret": self.tokens.tumblr_client_secret.get_secret_value(),
|
|
22
|
-
"token": self.tokens.tumblr_token.get_secret_value(),
|
|
23
|
-
},
|
|
24
|
-
token=self.tokens.tumblr_token.get_secret_value(),
|
|
25
|
-
token_updater=self.token_updater,
|
|
26
|
-
)
|
|
14
|
+
super().__init__(*self.tokens.get_tumblr_tokens()) # pyright: ignore[reportUnknownMemberType]
|
|
27
15
|
|
|
28
16
|
self.hooks["response"].append(self.response_hook)
|
|
29
17
|
|
|
30
|
-
def token_updater(self, token: object) -> None:
|
|
31
|
-
self.tokens.tumblr_token = Secret(token)
|
|
32
|
-
|
|
33
18
|
def response_hook(self, response: Response, **_: object) -> None:
|
|
34
19
|
try:
|
|
35
20
|
response.raise_for_status()
|
tumblrbot-1.2.0/PKG-INFO
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: tumblrbot
|
|
3
|
-
Version: 1.2.0
|
|
4
|
-
Summary: An updated bot that posts to Tumblr, based on your very own blog!
|
|
5
|
-
Requires-Python: >= 3.13
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: keyring
|
|
8
|
-
Requires-Dist: more-itertools
|
|
9
|
-
Requires-Dist: openai
|
|
10
|
-
Requires-Dist: pydantic
|
|
11
|
-
Requires-Dist: pydantic-settings
|
|
12
|
-
Requires-Dist: requests
|
|
13
|
-
Requires-Dist: requests-oauthlib
|
|
14
|
-
Requires-Dist: rich
|
|
15
|
-
Requires-Dist: tiktoken
|
|
16
|
-
Requires-Dist: tomlkit
|
|
17
|
-
Project-URL: Source, https://github.com/MaidThatPrograms/tumblrbot
|
|
18
|
-
|
|
19
|
-
[OpenAI]: https://pypi.org/project/openai
|
|
20
|
-
[Python]: https://python.org/download
|
|
21
|
-
[Rich]: https://pypi.org/project/rich
|
|
22
|
-
|
|
23
|
-
[gpt-4.1-nano-2025-04-14]: https://platform.openai.com/docs/models/gpt-4.1-nano
|
|
24
|
-
[Moderation API]: https://platform.openai.com/docs/api-reference/moderations
|
|
25
|
-
[New Post Format]: https://tumblr.com/docs/npf
|
|
26
|
-
[OAuth 2.0]: https://www.tumblr.com/docs/en/api/v2#oauth2-authorization
|
|
27
|
-
[pip]: https://pypi.org
|
|
28
|
-
|
|
29
|
-
[Download]: tumblrbot/flow/download.py
|
|
30
|
-
[Examples]: tumblrbot/flow/examples.py
|
|
31
|
-
[Fine-Tune]: tumblrbot/flow/fine_tune.py
|
|
32
|
-
[Generate]: tumblrbot/flow/generate.py
|
|
33
|
-
[Settings]: tumblrbot/utils/settings.py
|
|
34
|
-
[Main]: __main__.py
|
|
35
|
-
[README.md]: README.md
|
|
36
|
-
|
|
37
|
-
# tumblrbot
|
|
38
|
-
[](https://python.org/pypi/tumblrbot)
|
|
39
|
-
|
|
40
|
-
Description of original project:
|
|
41
|
-
> 4tv-tumblrbot was a collaborative project I embarked on with my close friend Dima, who goes by @smoqueen on Tumblr. The aim of this endeavor was straightforward yet silly: to develop a Tumblr bot powered by a machine-learning model. This bot would be specifically trained on the content from a particular Tumblr blog or a selected set of blogs, allowing it to mimic the style, tone, and thematic essence of the original posts.
|
|
42
|
-
|
|
43
|
-
This fork is largely a rewrite of the source code with similarities in its structure and process:
|
|
44
|
-
- Updates:
|
|
45
|
-
- Updated to [OAuth 2.0].
|
|
46
|
-
- Updated to the [New Post Format].
|
|
47
|
-
- Updated to the latest version of [OpenAI].
|
|
48
|
-
- Updated the [base model version][Settings] to [gpt-4.1-nano-2025-04-14].
|
|
49
|
-
- Removed features:
|
|
50
|
-
- [Generation][Generate]:
|
|
51
|
-
- Removed clearing drafts behavior.
|
|
52
|
-
- [Training][Examples]:
|
|
53
|
-
- Removed exports that had HTML or reblogs.
|
|
54
|
-
- Removed special word-replacement behavior.
|
|
55
|
-
- Removed filtering by year.
|
|
56
|
-
- Removed setup and related files.
|
|
57
|
-
- Changed/Added features:
|
|
58
|
-
- [Generation][Generate]:
|
|
59
|
-
- Added a link to the blog's draft page.
|
|
60
|
-
- Added error checking for uploading drafts.
|
|
61
|
-
- [Training][Examples]:
|
|
62
|
-
- Added the option to [Download] the latest posts from the [specified blogs][Settings].
|
|
63
|
-
- Added the option to remove posts flagged by the [Moderation API].
|
|
64
|
-
- Added the option to automatically [Fine-Tune] the examples on the [specified base model][Settings].
|
|
65
|
-
- Added the ability to add custom prompts and responses to the example data.
|
|
66
|
-
- Changed to now escape examples automatically.
|
|
67
|
-
- Set encoding for reading post data to `UTF-8` to fix decoding errors.
|
|
68
|
-
- Added newlines between paragraphs.
|
|
69
|
-
- Removed "ALT", submission, ask, and poll text from posts.
|
|
70
|
-
- Improved the estimated token counts and costs.
|
|
71
|
-
- Changed to [Rich] for output.
|
|
72
|
-
- Added progress bars.
|
|
73
|
-
- Added post previews.
|
|
74
|
-
- Added color, formatting, and more information to output.
|
|
75
|
-
- Created a [guided utility][Main] for every step of building your bot blog.
|
|
76
|
-
- Maid scripts wait for user input before the console closes.
|
|
77
|
-
- Added comand-line options to override [Settings] options.
|
|
78
|
-
- Added behavior to regenerate the default [config.toml][Settings] and [env.toml][Settings] if missing.
|
|
79
|
-
- Renamed several files.
|
|
80
|
-
- Renamed several [Settings] options.
|
|
81
|
-
- Changed the value of several [Settings] options.
|
|
82
|
-
- Added full type-checking coverage (fully importable from third-party scripts).
|
|
83
|
-
|
|
84
|
-
To-Do:
|
|
85
|
-
- Add documentation.
|
|
86
|
-
- Finish updating [README.md].
|
|
87
|
-
- Change the differences list to instead just be a list of features.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
**Please submit an issue or contact us for features you want to added/reimplemented.**
|
|
91
|
-
|
|
92
|
-
## Installation
|
|
93
|
-
1. Install the latest version of [Python]:
|
|
94
|
-
- Windows: `winget install python3`
|
|
95
|
-
- Linux (apt): `apt install python-pip`
|
|
96
|
-
- Linux (pacman): `pacman install python-pip`
|
|
97
|
-
1. Install the [pip] package: `pip install tumblrbot`
|
|
98
|
-
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
99
|
-
- On Linux, you will have to make a virtual environment.
|
|
100
|
-
|
|
101
|
-
## Usage
|
|
102
|
-
Run `tumblrbot` from anywhere. Run `tumblrbot --help` for command-line options.
|
|
103
|
-
|
|
104
|
-
## Obtaining Tokens
|
|
105
|
-
> WIP
|
|
106
|
-
|
|
107
|
-
## Configuration
|
|
108
|
-
> WIP
|
|
109
|
-
|
tumblrbot-1.2.0/README.md
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
[OpenAI]: https://pypi.org/project/openai
|
|
2
|
-
[Python]: https://python.org/download
|
|
3
|
-
[Rich]: https://pypi.org/project/rich
|
|
4
|
-
|
|
5
|
-
[gpt-4.1-nano-2025-04-14]: https://platform.openai.com/docs/models/gpt-4.1-nano
|
|
6
|
-
[Moderation API]: https://platform.openai.com/docs/api-reference/moderations
|
|
7
|
-
[New Post Format]: https://tumblr.com/docs/npf
|
|
8
|
-
[OAuth 2.0]: https://www.tumblr.com/docs/en/api/v2#oauth2-authorization
|
|
9
|
-
[pip]: https://pypi.org
|
|
10
|
-
|
|
11
|
-
[Download]: tumblrbot/flow/download.py
|
|
12
|
-
[Examples]: tumblrbot/flow/examples.py
|
|
13
|
-
[Fine-Tune]: tumblrbot/flow/fine_tune.py
|
|
14
|
-
[Generate]: tumblrbot/flow/generate.py
|
|
15
|
-
[Settings]: tumblrbot/utils/settings.py
|
|
16
|
-
[Main]: __main__.py
|
|
17
|
-
[README.md]: README.md
|
|
18
|
-
|
|
19
|
-
# tumblrbot
|
|
20
|
-
[](https://python.org/pypi/tumblrbot)
|
|
21
|
-
|
|
22
|
-
Description of original project:
|
|
23
|
-
> 4tv-tumblrbot was a collaborative project I embarked on with my close friend Dima, who goes by @smoqueen on Tumblr. The aim of this endeavor was straightforward yet silly: to develop a Tumblr bot powered by a machine-learning model. This bot would be specifically trained on the content from a particular Tumblr blog or a selected set of blogs, allowing it to mimic the style, tone, and thematic essence of the original posts.
|
|
24
|
-
|
|
25
|
-
This fork is largely a rewrite of the source code with similarities in its structure and process:
|
|
26
|
-
- Updates:
|
|
27
|
-
- Updated to [OAuth 2.0].
|
|
28
|
-
- Updated to the [New Post Format].
|
|
29
|
-
- Updated to the latest version of [OpenAI].
|
|
30
|
-
- Updated the [base model version][Settings] to [gpt-4.1-nano-2025-04-14].
|
|
31
|
-
- Removed features:
|
|
32
|
-
- [Generation][Generate]:
|
|
33
|
-
- Removed clearing drafts behavior.
|
|
34
|
-
- [Training][Examples]:
|
|
35
|
-
- Removed exports that had HTML or reblogs.
|
|
36
|
-
- Removed special word-replacement behavior.
|
|
37
|
-
- Removed filtering by year.
|
|
38
|
-
- Removed setup and related files.
|
|
39
|
-
- Changed/Added features:
|
|
40
|
-
- [Generation][Generate]:
|
|
41
|
-
- Added a link to the blog's draft page.
|
|
42
|
-
- Added error checking for uploading drafts.
|
|
43
|
-
- [Training][Examples]:
|
|
44
|
-
- Added the option to [Download] the latest posts from the [specified blogs][Settings].
|
|
45
|
-
- Added the option to remove posts flagged by the [Moderation API].
|
|
46
|
-
- Added the option to automatically [Fine-Tune] the examples on the [specified base model][Settings].
|
|
47
|
-
- Added the ability to add custom prompts and responses to the example data.
|
|
48
|
-
- Changed to now escape examples automatically.
|
|
49
|
-
- Set encoding for reading post data to `UTF-8` to fix decoding errors.
|
|
50
|
-
- Added newlines between paragraphs.
|
|
51
|
-
- Removed "ALT", submission, ask, and poll text from posts.
|
|
52
|
-
- Improved the estimated token counts and costs.
|
|
53
|
-
- Changed to [Rich] for output.
|
|
54
|
-
- Added progress bars.
|
|
55
|
-
- Added post previews.
|
|
56
|
-
- Added color, formatting, and more information to output.
|
|
57
|
-
- Created a [guided utility][Main] for every step of building your bot blog.
|
|
58
|
-
- Maid scripts wait for user input before the console closes.
|
|
59
|
-
- Added comand-line options to override [Settings] options.
|
|
60
|
-
- Added behavior to regenerate the default [config.toml][Settings] and [env.toml][Settings] if missing.
|
|
61
|
-
- Renamed several files.
|
|
62
|
-
- Renamed several [Settings] options.
|
|
63
|
-
- Changed the value of several [Settings] options.
|
|
64
|
-
- Added full type-checking coverage (fully importable from third-party scripts).
|
|
65
|
-
|
|
66
|
-
To-Do:
|
|
67
|
-
- Add documentation.
|
|
68
|
-
- Finish updating [README.md].
|
|
69
|
-
- Change the differences list to instead just be a list of features.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
**Please submit an issue or contact us for features you want to added/reimplemented.**
|
|
73
|
-
|
|
74
|
-
## Installation
|
|
75
|
-
1. Install the latest version of [Python]:
|
|
76
|
-
- Windows: `winget install python3`
|
|
77
|
-
- Linux (apt): `apt install python-pip`
|
|
78
|
-
- Linux (pacman): `pacman install python-pip`
|
|
79
|
-
1. Install the [pip] package: `pip install tumblrbot`
|
|
80
|
-
- Alternatively, you can install from this repository: `pip install git+https://github.com/MaidThatPrograms/tumblrbot.git`
|
|
81
|
-
- On Linux, you will have to make a virtual environment.
|
|
82
|
-
|
|
83
|
-
## Usage
|
|
84
|
-
Run `tumblrbot` from anywhere. Run `tumblrbot --help` for command-line options.
|
|
85
|
-
|
|
86
|
-
## Obtaining Tokens
|
|
87
|
-
> WIP
|
|
88
|
-
|
|
89
|
-
## Configuration
|
|
90
|
-
> WIP
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
from typing import Annotated, Any, Literal, override
|
|
2
|
-
|
|
3
|
-
from openai import BaseModel
|
|
4
|
-
from pydantic import ConfigDict, PlainSerializer
|
|
5
|
-
from pydantic.json_schema import SkipJsonSchema
|
|
6
|
-
from rich.panel import Panel
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class FullyValidatedModel(BaseModel):
|
|
10
|
-
model_config = ConfigDict(
|
|
11
|
-
extra="ignore",
|
|
12
|
-
validate_assignment=True,
|
|
13
|
-
validate_default=True,
|
|
14
|
-
validate_return=True,
|
|
15
|
-
validate_by_name=True,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class Post(FullyValidatedModel):
|
|
20
|
-
class Block(FullyValidatedModel):
|
|
21
|
-
type: str = ""
|
|
22
|
-
text: str = ""
|
|
23
|
-
blocks: list[int] = [] # noqa: RUF012
|
|
24
|
-
|
|
25
|
-
timestamp: SkipJsonSchema[int] = 0
|
|
26
|
-
tags: Annotated[SkipJsonSchema[list[str]], PlainSerializer(",".join)] = [] # noqa: RUF012
|
|
27
|
-
state: SkipJsonSchema[Literal["published", "queued", "draft", "private", "unapproved"]] = "published"
|
|
28
|
-
|
|
29
|
-
content: SkipJsonSchema[list[Block]] = [] # noqa: RUF012
|
|
30
|
-
layout: SkipJsonSchema[list[Block]] = [] # noqa: RUF012
|
|
31
|
-
trail: SkipJsonSchema[list[Any]] = [] # noqa: RUF012
|
|
32
|
-
|
|
33
|
-
is_submission: SkipJsonSchema[bool] = False
|
|
34
|
-
|
|
35
|
-
def __rich__(self) -> Panel:
|
|
36
|
-
return Panel(
|
|
37
|
-
self.get_text_content(),
|
|
38
|
-
title="Preview",
|
|
39
|
-
subtitle=" ".join(f"#{tag}" for tag in self.tags),
|
|
40
|
-
subtitle_align="left",
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
@override
|
|
44
|
-
def model_post_init(self, context: object) -> None:
|
|
45
|
-
super().model_post_init(context)
|
|
46
|
-
|
|
47
|
-
indices: set[int] = set()
|
|
48
|
-
for block in self.layout:
|
|
49
|
-
if block.type == "ask":
|
|
50
|
-
indices.update(block.blocks)
|
|
51
|
-
|
|
52
|
-
self.content = [block for i, block in enumerate(self.content) if i not in indices and block.type == "text"]
|
|
53
|
-
|
|
54
|
-
def get_text_content(self) -> str:
|
|
55
|
-
return "\n\n".join(block.text for block in self.content)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class Example(FullyValidatedModel):
|
|
59
|
-
class Message(FullyValidatedModel):
|
|
60
|
-
role: Literal["developer", "user", "assistant"]
|
|
61
|
-
content: str
|
|
62
|
-
|
|
63
|
-
messages: list[Message]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|