This commit is contained in:
pancakes 2024-02-19 21:41:28 +10:00
commit 94ce88aa65
8 changed files with 253 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
**/**.gif
**/**.jpeg
**/**.jpg
**/**.mp4
**/**.png
.venv/
auth/*.secret
config/*.json

29
README.md Normal file
View file

@ -0,0 +1,29 @@
# Fedi Board Bot
A bot that posts random images from image boards to Mastodon, Iceshrimp, Sharkey or Akkoma.
## Config
Copy `config/example.json.example` and remove the `.example` from the end. All fields are explained in the file. Config names should not have any spaces.
When config-name is mentioned later do not include the `.json` extension.
## Usage
Install the requirements. You should ideally do this in a venv.
```sh
pip install -r requirements.txt
```
For every new config generate a login token by running
```sh
python3 login.py <config-name> <application-name> <instance-url>
```
Run the bot by running
```sh
python3 bot.py <config-name>
```

0
auth/.gitkeep Normal file
View file

173
bot.py Normal file
View file

@ -0,0 +1,173 @@
from dataclasses import dataclass
from mastodon import Mastodon
from os import environ
from sys import argv
import json
import pathlib
import random
import requests
class Config:
block_tags: list[str]
nsfw_tags: list[str]
search_tags: list[str]
text_cw: str
text_filler: list[str]
type: str
def __init__(self, config_name: str):
with open(f"config/{config_name}.json") as file:
config = json.load(file)
self.block_tags = config["blockTags"]
self.nsfw_tags = config["nsfwTags"]
self.search_tags = config["searchTags"]
self.text_cw = config["textCW"]
self.text_filler = config["textFiller"]
self.type = config["type"]
def random_image_danbooru(block_tags: list[str], search_tags: list[str]) -> dict | None:
tags = []
for tag in block_tags:
tags.append("-" + tag.strip())
for tag in search_tags:
tags.append(tag.strip())
tags = " ".join(tags)
params = {
"random": "1",
"tags": tags
}
if environ.get("BOARD_API_KEY") and environ.get("BOARD_USERNAME"):
params["api_key"] = environ.get("BOARD_API_KEY")
params["login"] = environ.get("BOARD_USERNAME")
resp = requests.get("https://danbooru.donmai.us/posts.json", params=params)
if resp.ok == False:
print(resp.json())
return None
elif resp.json() == []:
return None
return random.choice(resp.json())
def random_image_gelbooru(block_tags: list[str], search_tags: list[str]) -> dict | None:
tags = ["sort:random"]
for tag in block_tags:
tags.append("-" + tag.strip())
for tag in search_tags:
tags.append(tag.strip())
tags = " ".join(tags)
params = {
"page": "dapi",
"s": "post",
"q": "index",
"pid": 0,
"json": "1",
"limit": "1",
"tags": tags
}
resp = requests.get("https://gelbooru.com/index.php", params=params)
if resp.ok == False:
print(resp.json())
return None
elif resp.json()["post"] == []:
return None
return random.choice(resp.json()["post"])
# Exit if config isn't specified
if len(argv) < 2:
print(f"Usage: python3 {argv[0]} <config-name>")
exit()
config = Config(argv[1])
match config.type:
case "danbooru":
post = random_image_danbooru(config.block_tags, config.search_tags)
case "gelbooru":
post = random_image_gelbooru(config.block_tags, config.search_tags)
case _:
print("'type' in config must be 'danbooru' or 'gelbooru'")
exit()
if post == None:
print("Couldn't get an image")
exit()
match config.type:
case "danbooru":
filename = f'{post["md5"]}.{post["file_ext"]}'
case "gelbooru":
filename = post["image"]
case _:
filename = "img.png"
print(post)
image = requests.get(post["file_url"])
if image.ok == False:
print("Couldn't download image")
exit()
with open(filename, "wb") as f:
f.write(image.content)
# Image is NSFW if rated Explicit, Sensitive or Unknown
is_nsfw = post["rating"].startswith("e") or post["rating"].startswith("s") or post["rating"] == None
if is_nsfw == False:
for tag in config.nsfw_tags:
# Check if any config defined NSFW tags are present
match config.type:
case "danbooru":
if tag in post["tag_string"] or tag in post["tag_string_general"] or tag in post["tag_string_artist"] or tag in post["tag_string_copyright"] or tag in post["tag_string_character"] or tag in post["tag_string_meta"]:
is_nsfw = True
break
case "gelbooru":
if tag in post["tags"]:
is_nsfw = True
break
case _:
is_nsfw = True
break
# This sucks but it works :3
post_content = [
random.choice(config.text_filler),
"\n\n",
"Artist: " + post["tag_string_artist"] + "\n" if config.type == "danbooru" and len(post["tag_string_artist"]) > 0 else "",
"Score: " + str(post["score"]) + "\n",
"Source: " + post["source"] + "\n\n" if len(post["source"]) > 0 else "\n",
]
mastodon = Mastodon(access_token=f"auth/{argv[1]}.auth.secret")
match config.type:
case "danbooru":
tags = post["tag_string"]
case "gelbooru":
tags = post["tags"]
case _:
tags = "unknown"
media = mastodon.media_post(
description="Automatically posted image. Tagged: " + tags,
media_file=filename
)
status = mastodon.status_post(
"".join(post_content),
media_ids=[media["id"]],
sensitive=is_nsfw,
spoiler_text="(NSFW) " + config.text_cw if is_nsfw else config.text_cw,
visibility="unlisted"
)
print(status["url"])
pathlib.Path(filename).unlink()

View file

@ -0,0 +1,16 @@
{
"blockTags": [
"List of tags that should be ignored by the bot."
],
"nsfwTags": [
"If these tags are present the post will be marked as sensitive"
],
"searchTags": [
"Tags that will be searched for"
],
"textCW": "All posts will have this content warning",
"textFiller": [
"Flavor text for posts"
],
"type": "danbooru or gelbooru"
}

11
login.py Normal file
View file

@ -0,0 +1,11 @@
from mastodon import Mastodon
from sys import argv
if len(argv) < 4:
print(f"Usage: python3 {argv[0]} <config-name> <application-name> <instance-url>")
exit()
Mastodon.create_app(argv[2], scopes=["write:media", "write:statuses"], to_file=f"auth/{argv[1]}.client.secret", api_base_url=argv[3])
mastodon = Mastodon(client_id=f"auth/{argv[1]}.client.secret", api_base_url=argv[3])
print(mastodon.auth_request_url(scopes=["write:media", "write:statuses"]))
mastodon.log_in(code=input("Code="), scopes=["write:media", "write:statuses"], to_file=f"auth/{argv[1]}.auth.secret")

1
requirements.txt Normal file
View file

@ -0,0 +1 @@
Mastodon.py

15
shell.nix Normal file
View file

@ -0,0 +1,15 @@
{ pkgs ? import <nixpkgs> { } }:
pkgs.mkShell {
packages = [
(pkgs.python3.withPackages (python-packages: [
python-packages.black
python-packages.pip
python-packages.virtualenv
]))
];
shellHook = ''
python3 -m venv .venv
source .venv/bin/activate
'';
}