# Chapter by Chapter
A Python bot that serializes classic literature to Mastodon, posting one chapter at a time in a daily schedule.
## Overview
Chapter by Chapter is a Mastodon bot built as a plug for the [robotooter](https://codeberg.org/bfordham/robotooter) framework. It takes classic literature from Project Gutenberg and posts it chapter by chapter to social media. The bot processes text files, extracts chapters, and creates engaging daily posts with customizable templates.
## Features
- **Automated Chapter Processing**: Converts full books into individual chapters with metadata
- **Customizable Templates**: Flexible templating system for post formatting
- **Daily Scheduling**: Posts chapters on a configurable schedule
- **Project Gutenberg Integration**: Direct support for Project Gutenberg text files
- **Rich Content Creation**: Generates engaging introductions and endings.
- **Mastodon Integration**: Built on robotooter for reliable social media posting
## Examples
- **[Chapter By Chapter](https://social.naughtybaptist.com/@chapterbychapter)]** The reason I created this. Posts a chapter a day from Public Domain litereature. Victorian-era Netflix.
## Installation
### Prerequisites
- Python 3.12+
- RoboTooter 0.2.2
- Poetry for dependency management
### Installation
#### Via pip
```bash
# Create a virtual environment
mkdir my_bot
cd my_bot
python3 -m venv .venv
# Activate the environment
. .venv/bin/activate
# And install...
pip install chapter_by_chapter
```
#### From source
1. Clone the repository:
```bash
git clone <repository-url>
cd chapter-by-chapter
```
2. Install dependencies:
```bash
just init
# or manually: poetry install
```
## Usage
### Setup
You will need to register the plugin, and then create a new bot.
```bash
❯ robotooter plugins register --plugin chapter_by_chapter
chapter_by_chapter plugin registered
# You will now see the new bot installed
❯ robotooter info
RobotoTooter info
Version: 0.2.0
Root directory: /Users/bryan.fordham/.robotooter
Filters:
BlankLineFilter
GutenbergFilter
ParagraphCombiningFilter
Tooters:
MarkovBot
ChapterByChapterBot
```
And now create the bot, selecting `ChapterByChapter` as the bot you want to use.
```bash
❯ robotooter create
Let's get your bot's information.
Enter the bot name: test
Enter the username, such as 'mybot@mastodon.social': test@not.real
1. ChapterByChapterBot
2. MarkovBot
Select an option: 1
...
```
### Setting up a Book
To configure a new book for serialization:
```bash
robotooter -b <botname> setup --book KEY --file PATH
# or
robotooter -b <botname> setup --book KEY --url URL
```
- `KEY` will be how you refer to the book, and will also be the directory name for its files.
- So, for example, for *A Study in Scarlet* I used `a_study_in_scarlet` but you can use anything.
- `PATH` A local file path to a Project Gutenberg text file
- `URL` A URL to download a text file
Example:
```bash
robotooter -b my_literature_bot setup -f /path/to/book.txt -bk "scarlet"
robotooter -b my_literature_bot setup -u "https://gutenberg.org/files/244/244.txt" -bk "study"
```
### Setting the Book
This bot is mean to do most things automatically. So you have to tell it which book to post.
Examples:
```bash
# Defaults to chapter 1
robotooter --book a_study_in_scarlet
# You can set a specific chapter to start from, if you want
robotooter --book a_study_in_scarlet --chapter 2
```
### Posting Content
After you've told it which book to use, it will post the next chapter automatically.
```bash
robotooter -b <botname> toot
```
If you run the command again, it will post the next chapter.
### Book Queue
You have the ability to set a queue of books. If set, the bot will automatically move to the next book in the queue when the current one finishes.
```bash
$ robotooter -b beta queue --add frankenstein
Added frankenstein to the book queue.
$ robotooter -b beta queue
1 books are currently present in the book queue.
1. Frankenstein; Or, The Modern Prometheus
# And obviously you can also remove books
$ robotooter -b beta queue --remove frankenstein
Removed frankenstein from the book queue.
```
### Periodic Messages
This plugin has the ability to send messages outside the regular book posts.
To send periodic messages (if necessary) run
```bash
robotooter -b beta periodic
```
#### Next book reminders
If turned on, you will receive private messages from the bot when you are a few days from the end of the book.
```bash
# To enable
$ robotooter -b beta periodic --enable
# to disable
$ robotooter -b beta periodic --disable
# to send messages, if needed
$ robotooter -b beta periodic
```
## Development
### Code Quality
Run linting and type checking:
```bash
just lint # Run ruff linter with auto-fix
just type # Run mypy type checker
just build # Run both lint and type checks, then build
```
### Testing
Run the test suite:
```bash
poetry run pytest
```
### Project Structure
```
src/chapter_by_chapter/
bot.py # Main bot implementation
content_creator.py # Content generation and templating
greeter.py # User interaction handling
models.py # Data models and configuration
processor.py # Text processing and chapter extraction
util.py # Utility functions
resources/
templates/ # Post templates
word_choices.json
```
## Configuration
The bot uses a configuration system based on robotooter's `BotConfig`. Key configuration options include:
- **Book Processing**: Chapter extraction settings
- **Content Templates**: Customizable post formatting
- **Scheduling**: Daily posting configuration
- **Social Media**: Mastodon API credentials
## Templates
The bot includes several customizable templates. When you create a new both they are copied to the bot's resources directory. So, if you create "chapter_bot" you will find the templates in `<robotooter_root>/chapter_bot/resources/templates/`:
- `chapter_text.txt` - Main chapter content format
- `book_intro/` - Book introduction variations
- `daily_intro/` - Daily post introductions
- `book_ending/` - Book conclusion formats
- `chapter_spoiler.txt` - Spoiler warning template
The templates use jinja2 as the template engine.
When a specific template is called for, it will first to see if there is a template directory that matches. If so, it will randomly pick one of the files in the directoy as the template to use. This allows for some variety.
If there is not a directory that matches, it will look for `"{template_name}.txt"`.
So, as an example, if you try to render the template `book_intro`, out of the box it will look in the `templates/book_intro` directory and pick a template. If you want to always use the same template, remove the directory and create `templates/book_intro.txt`.
## Contributing
If you would like to add features, please feel free. If you use this yourself, [please let me know](https://infosec.exchange/@bfordham)!
## License
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the [LICENSE](LICENSE) file for details.
The AGPL-3.0 is a copyleft license that requires anyone who distributes the code or runs it on a server to make the source code available under the same license terms.
## Author
Bryan L. Fordham <bfordham@naughtybaptist.com>
Raw data
{
"_id": null,
"home_page": "https://codeberg.org/bfordham/chapter-by-chapter",
"name": "chapter-by-chapter",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.12",
"maintainer_email": null,
"keywords": "mastodon, bot, social-media, markov, automation",
"author": "Bryan L. Fordham",
"author_email": "bfordham@naughtybaptist.com",
"download_url": "https://files.pythonhosted.org/packages/55/81/cd3a3df4a1f382fe15fe90628cfad4e7766693652d00543f8454b212d89e/chapter_by_chapter-0.2.0.tar.gz",
"platform": null,
"description": "# Chapter by Chapter\n\nA Python bot that serializes classic literature to Mastodon, posting one chapter at a time in a daily schedule.\n\n## Overview\n\nChapter by Chapter is a Mastodon bot built as a plug for the [robotooter](https://codeberg.org/bfordham/robotooter) framework. It takes classic literature from Project Gutenberg and posts it chapter by chapter to social media. The bot processes text files, extracts chapters, and creates engaging daily posts with customizable templates.\n\n## Features\n\n- **Automated Chapter Processing**: Converts full books into individual chapters with metadata\n- **Customizable Templates**: Flexible templating system for post formatting\n- **Daily Scheduling**: Posts chapters on a configurable schedule\n- **Project Gutenberg Integration**: Direct support for Project Gutenberg text files\n- **Rich Content Creation**: Generates engaging introductions and endings.\n- **Mastodon Integration**: Built on robotooter for reliable social media posting\n\n## Examples\n\n- **[Chapter By Chapter](https://social.naughtybaptist.com/@chapterbychapter)]** The reason I created this. Posts a chapter a day from Public Domain litereature. Victorian-era Netflix.\n\n## Installation\n\n### Prerequisites\n\n- Python 3.12+\n- RoboTooter 0.2.2\n- Poetry for dependency management\n\n### Installation\n\n#### Via pip\n\n```bash\n# Create a virtual environment\nmkdir my_bot\ncd my_bot\npython3 -m venv .venv\n# Activate the environment\n. .venv/bin/activate\n# And install...\npip install chapter_by_chapter\n```\n\n#### From source\n\n1. Clone the repository:\n```bash\ngit clone <repository-url>\ncd chapter-by-chapter\n```\n\n2. Install dependencies:\n```bash\njust init\n# or manually: poetry install\n```\n\n## Usage\n\n### Setup\n\nYou will need to register the plugin, and then create a new bot.\n\n```bash\n\u276f robotooter plugins register --plugin chapter_by_chapter\nchapter_by_chapter plugin registered\n\n# You will now see the new bot installed\n\u276f robotooter info\nRobotoTooter info\nVersion: 0.2.0\nRoot directory: /Users/bryan.fordham/.robotooter\nFilters:\n BlankLineFilter\n GutenbergFilter\n ParagraphCombiningFilter\nTooters:\n MarkovBot\n ChapterByChapterBot\n```\n\nAnd now create the bot, selecting `ChapterByChapter` as the bot you want to use.\n\n```bash\n\u276f robotooter create\nLet's get your bot's information.\nEnter the bot name: test\nEnter the username, such as 'mybot@mastodon.social': test@not.real\n1. ChapterByChapterBot\n2. MarkovBot\nSelect an option: 1\n...\n```\n\n### Setting up a Book\n\nTo configure a new book for serialization:\n\n```bash\nrobotooter -b <botname> setup --book KEY --file PATH\n# or\nrobotooter -b <botname> setup --book KEY --url URL\n```\n- `KEY` will be how you refer to the book, and will also be the directory name for its files.\n - So, for example, for *A Study in Scarlet* I used `a_study_in_scarlet` but you can use anything.\n- `PATH` A local file path to a Project Gutenberg text file\n- `URL` A URL to download a text file\n\nExample:\n```bash\nrobotooter -b my_literature_bot setup -f /path/to/book.txt -bk \"scarlet\"\nrobotooter -b my_literature_bot setup -u \"https://gutenberg.org/files/244/244.txt\" -bk \"study\"\n```\n\n### Setting the Book\n\nThis bot is mean to do most things automatically. So you have to tell it which book to post.\n\nExamples:\n```bash\n# Defaults to chapter 1\nrobotooter --book a_study_in_scarlet\n\n# You can set a specific chapter to start from, if you want\nrobotooter --book a_study_in_scarlet --chapter 2\n```\n\n### Posting Content\n\nAfter you've told it which book to use, it will post the next chapter automatically.\n\n```bash\nrobotooter -b <botname> toot \n```\n\nIf you run the command again, it will post the next chapter.\n\n### Book Queue\n\nYou have the ability to set a queue of books. If set, the bot will automatically move to the next book in the queue when the current one finishes.\n\n```bash\n$ robotooter -b beta queue --add frankenstein\nAdded frankenstein to the book queue.\n\n$ robotooter -b beta queue\n1 books are currently present in the book queue.\n1. Frankenstein; Or, The Modern Prometheus\n\n# And obviously you can also remove books\n$ robotooter -b beta queue --remove frankenstein\nRemoved frankenstein from the book queue.\n```\n\n### Periodic Messages\n\nThis plugin has the ability to send messages outside the regular book posts.\n\nTo send periodic messages (if necessary) run\n\n```bash\nrobotooter -b beta periodic\n```\n\n#### Next book reminders\n\nIf turned on, you will receive private messages from the bot when you are a few days from the end of the book.\n\n```bash\n# To enable\n$ robotooter -b beta periodic --enable\n\n# to disable\n$ robotooter -b beta periodic --disable\n\n# to send messages, if needed\n$ robotooter -b beta periodic\n```\n\n## Development\n\n### Code Quality\n\nRun linting and type checking:\n\n```bash\njust lint # Run ruff linter with auto-fix\njust type # Run mypy type checker\njust build # Run both lint and type checks, then build\n```\n\n### Testing\n\nRun the test suite:\n\n```bash\npoetry run pytest\n```\n\n### Project Structure\n\n```\nsrc/chapter_by_chapter/\n bot.py # Main bot implementation\n content_creator.py # Content generation and templating\n greeter.py # User interaction handling\n models.py # Data models and configuration\n processor.py # Text processing and chapter extraction\n util.py # Utility functions\n resources/\n templates/ # Post templates\n word_choices.json\n```\n\n## Configuration\n\nThe bot uses a configuration system based on robotooter's `BotConfig`. Key configuration options include:\n\n- **Book Processing**: Chapter extraction settings\n- **Content Templates**: Customizable post formatting\n- **Scheduling**: Daily posting configuration\n- **Social Media**: Mastodon API credentials\n\n## Templates\n\nThe bot includes several customizable templates. When you create a new both they are copied to the bot's resources directory. So, if you create \"chapter_bot\" you will find the templates in `<robotooter_root>/chapter_bot/resources/templates/`:\n\n- `chapter_text.txt` - Main chapter content format\n- `book_intro/` - Book introduction variations\n- `daily_intro/` - Daily post introductions\n- `book_ending/` - Book conclusion formats\n- `chapter_spoiler.txt` - Spoiler warning template\n\nThe templates use jinja2 as the template engine.\n\nWhen a specific template is called for, it will first to see if there is a template directory that matches. If so, it will randomly pick one of the files in the directoy as the template to use. This allows for some variety.\n\nIf there is not a directory that matches, it will look for `\"{template_name}.txt\"`.\n\nSo, as an example, if you try to render the template `book_intro`, out of the box it will look in the `templates/book_intro` directory and pick a template. If you want to always use the same template, remove the directory and create `templates/book_intro.txt`.\n\n## Contributing\n\nIf you would like to add features, please feel free. If you use this yourself, [please let me know](https://infosec.exchange/@bfordham)!\n\n## License\n\nThis project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the [LICENSE](LICENSE) file for details.\n\nThe AGPL-3.0 is a copyleft license that requires anyone who distributes the code or runs it on a server to make the source code available under the same license terms.\n\n## Author\n\nBryan L. Fordham <bfordham@naughtybaptist.com>\n",
"bugtrack_url": null,
"license": "AGPL 3.0",
"summary": "Serializes classic literature to Mastodon",
"version": "0.2.0",
"project_urls": {
"Documentation": "https://codeberg.org/bfordham/chapter-by-chapter#readme",
"Homepage": "https://codeberg.org/bfordham/chapter-by-chapter",
"Repository": "https://codeberg.org/bfordham/chapter-by-chapter"
},
"split_keywords": [
"mastodon",
" bot",
" social-media",
" markov",
" automation"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f6d411acb0cd35a0355fbf0ebee3cda44b33df2c269bce94cfa749ea03706b76",
"md5": "72df9892b76c8c5b08dcdbc3024d4aa9",
"sha256": "af2e6acd70259a3d500effea69ef8d03db2fff51941d86a935ed06bf64bb4580"
},
"downloads": -1,
"filename": "chapter_by_chapter-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "72df9892b76c8c5b08dcdbc3024d4aa9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.12",
"size": 49163,
"upload_time": "2025-08-17T18:41:13",
"upload_time_iso_8601": "2025-08-17T18:41:13.862003Z",
"url": "https://files.pythonhosted.org/packages/f6/d4/11acb0cd35a0355fbf0ebee3cda44b33df2c269bce94cfa749ea03706b76/chapter_by_chapter-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "5581cd3a3df4a1f382fe15fe90628cfad4e7766693652d00543f8454b212d89e",
"md5": "be279a6cf0f66955b0758da28da678a4",
"sha256": "7e06de243c469ab4355a2983837f219680281f6afce64e5efec1315bf2becb5c"
},
"downloads": -1,
"filename": "chapter_by_chapter-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "be279a6cf0f66955b0758da28da678a4",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.12",
"size": 35883,
"upload_time": "2025-08-17T18:41:15",
"upload_time_iso_8601": "2025-08-17T18:41:15.127762Z",
"url": "https://files.pythonhosted.org/packages/55/81/cd3a3df4a1f382fe15fe90628cfad4e7766693652d00543f8454b212d89e/chapter_by_chapter-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-17 18:41:15",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": true,
"codeberg_user": "bfordham",
"codeberg_project": "chapter-by-chapter",
"lcname": "chapter-by-chapter"
}