Refactor to focus on danbooru and support auth properly

This commit is contained in:
pancakes 2025-04-13 17:55:22 +10:00
parent be2d09011e
commit f3601b79eb
Signed by: pancakes
SSH key fingerprint: SHA256:yrp4c4hhaPoPG07fb4QyQIgAdlbUdsJvUAydJEWnfTw
13 changed files with 122 additions and 98 deletions

3
.gitignore vendored
View file

@ -5,4 +5,5 @@
**/**.png **/**.png
.venv/ .venv/
auth/*.secret auth/*.secret
config/*.json config/*.json
download/

3
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

4
.idea/encodings.xml generated Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

10
.idea/fedi-board-bot.iml generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (fedi-board-bot) (2)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View file

@ -0,0 +1,21 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="gi" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="gi.repository.Gtk" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View file

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 (fedi-board-bot)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (fedi-board-bot) (2)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/fedi-board-bot.iml" filepath="$PROJECT_DIR$/.idea/fedi-board-bot.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View file

@ -27,5 +27,3 @@ Run the bot by running
```sh ```sh
python3 bot.py <config-name> python3 bot.py <config-name>
``` ```
Use the environment variables `BOARD_API_KEY` and `BOARD_USERNAME` to login with danbooru.

142
bot.py
View file

@ -1,6 +1,6 @@
from dataclasses import dataclass import base64
import os.path
from mastodon import Mastodon from mastodon import Mastodon
from os import environ
from sys import argv from sys import argv
import json import json
@ -8,13 +8,16 @@ import pathlib
import random import random
import requests import requests
class Config: class Config:
auth: str
block_tags: list[str] block_tags: list[str]
danbooru_api_key: str
danbooru_username: str
nsfw_tags: list[str] nsfw_tags: list[str]
search_tags: list[str] search_tags: list[str]
text_cw: str text_cw: str
text_filler: list[str] text_filler: list[str]
type: str
url: str url: str
def __init__(self, config_name: str): def __init__(self, config_name: str):
@ -22,18 +25,23 @@ class Config:
config = json.load(file) config = json.load(file)
self.block_tags = config["blockTags"] self.block_tags = config["blockTags"]
self.danbooru_api_key = config["danbooruApiKey"]
self.danbooru_username = config["danbooruUsername"]
self.nsfw_tags = config["nsfwTags"] self.nsfw_tags = config["nsfwTags"]
self.search_tags = config["searchTags"] self.search_tags = config["searchTags"]
self.text_cw = config["textCW"] self.text_cw = config["textCW"]
self.text_filler = config["textFiller"] self.text_filler = config["textFiller"]
self.type = config["type"]
self.url = config["url"] self.url = config["url"]
def random_image_danbooru(block_tags: list[str], search_tags: list[str], url: str) -> dict | None: self.auth = "Basic " + base64.b64encode(
bytes(self.danbooru_username + ":" + self.danbooru_api_key, "utf-8")).decode("utf-8")
def random_image(config: Config) -> dict | None:
tags = [] tags = []
for tag in block_tags: for tag in config.block_tags:
tags.append("-" + tag.strip()) tags.append("-" + tag.strip())
for tag in search_tags: for tag in config.search_tags:
tags.append(tag.strip()) tags.append(tag.strip())
tags = " ".join(tags) tags = " ".join(tags)
@ -41,49 +49,17 @@ def random_image_danbooru(block_tags: list[str], search_tags: list[str], url: st
"random": "1", "random": "1",
"tags": tags "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(url + "/posts.json", params=params)
if resp.ok == False: resp = requests.get(config.url + "/posts.json", params=params, headers={"Authorization": config.auth})
if not resp.ok:
print(resp.json()) print(resp.json())
return None return None
elif resp.json() == []: elif not resp.json():
return None return None
return random.choice(resp.json()) 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
post = resp.json()
if len(post["post"]) > 0:
return random.choice(resp.json()["post"])
return None
# Exit if config isn't specified # Exit if config isn't specified
if len(argv) < 2: if len(argv) < 2:
@ -92,87 +68,69 @@ if len(argv) < 2:
config = Config(argv[1]) config = Config(argv[1])
match config.type: # Check auth
case "danbooru": auth = requests.get(config.url + "/profile.json", headers={"Authorization": config.auth})
post = random_image_danbooru(config.block_tags, config.search_tags, config.url)
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(auth.status_code)
print(auth.json())
if not auth.ok or auth.json()["id"] is None:
print("Authentication failed")
exit()
post = random_image(config)
if post is None:
print("Couldn't get an image") print("Couldn't get an image")
exit() exit()
match config.type: filename = f'download/image.{post["file_ext"]}'
case "danbooru": post_url = config.url + "/posts/" + str(post["id"])
characters = post.get("tag_string_character") or ""
filename = f'image.{post["file_ext"]}'
post_url = config.url + "/posts/" + str(post["id"])
case "gelbooru":
characters = ""
filename = post["image"]
post_url = "https://gelbooru.com/index.php?page=post&s=view&id=" + str(post["id"])
case _:
characters = ""
filename = "img.png"
post_url = ""
print(post) print(post)
image = requests.get(post["file_url"]) image = requests.get(post["file_url"])
if image.ok == False: if not image.ok:
print("Couldn't download image") print("Couldn't download image")
exit() exit()
if not os.path.isdir("download"):
if not os.path.exists("download"):
os.makedirs("download")
else:
print("\"download\" exists but isn't a directory")
exit()
with open(filename, "wb") as f: with open(filename, "wb") as f:
f.write(image.content) f.write(image.content)
# Image is NSFW if rated Explicit, Sensitive or Unknown # Image is NSFW if rated Explicit, Sensitive or Unknown
is_nsfw = not post["rating"].startswith("g") or post["rating"] == None is_nsfw = not post["rating"].startswith("g") or post["rating"] == None
if is_nsfw == False: if not is_nsfw:
for tag in config.nsfw_tags: for tag in config.nsfw_tags:
# Check if any config defined NSFW tags are present # Check if any config defined NSFW tags are present
match config.type: if tag in post["tag_string"].split():
case "danbooru": is_nsfw = True
if tag in post["tag_string"].split():
print(tag)
is_nsfw = True
break
case "gelbooru":
if tag in post["tags"].split():
print(tag)
is_nsfw = True
break
case _:
is_nsfw = True
break
# This sucks but it works :3 # This sucks but it works :3
post_content = [ post_content = [
random.choice(config.text_filler), random.choice(config.text_filler),
"\n\n", "\n\n",
"Artist: " + post["tag_string_artist"] + "\n" if config.type == "danbooru" and len(post["tag_string_artist"]) > 0 else "", "Artist: " + post["tag_string_artist"] + "\n" if len(post["tag_string_artist"]) > 0 else "",
"Characters: " + characters + "\n" if characters != "" else "", "Characters: " + post["tag_string_character"] + "\n" if len(
post["tag_string_character"]) > 0 else "",
"Source: " + post["source"] if len(post["source"]) > 0 else "", "Source: " + post["source"] if len(post["source"]) > 0 else "",
"\n\n" + post_url if post_url != "" else "" "\n\n" + post_url if post_url != "" else ""
] ]
print("".join(post_content))
exit()
mastodon = Mastodon(access_token=f"auth/{argv[1]}.auth.secret") 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( media = mastodon.media_post(
description="Automatically posted image. Tagged: " + tags,
media_file=filename media_file=filename
) )

View file

@ -2,6 +2,8 @@
"blockTags": [ "blockTags": [
"List of tags that should be ignored by the bot." "List of tags that should be ignored by the bot."
], ],
"danbooruApiKey": "",
"danbooruUsername": "",
"nsfwTags": [ "nsfwTags": [
"If these tags are present the post will be marked as sensitive" "If these tags are present the post will be marked as sensitive"
], ],
@ -12,6 +14,5 @@
"textFiller": [ "textFiller": [
"Flavor text for posts" "Flavor text for posts"
], ],
"type": "danbooru or gelbooru", "url": "https://safebooru.donmai.us"
"url": "https://"
} }

View file

@ -1 +1,2 @@
Mastodon.py Mastodon.py
requests