Gift Book Gent

Ihr sucht ein originelles und lustiges Geburtstagsgeschenk für einen Freund oder eine Freundin? Statt ein fertiges Geschenkbuch online zu kaufen, warum nicht selbst eines gestalten? Mit etwas Hilfe von Python und ein paar humorvollen Inhalten von Mastodon bastelt ihr ein persönliches Geschenkbuch, das garantiert für ein Lächeln sorgt.

Schritt 1: Inhalte sammeln

Eine tolle Quelle für lustige Inhalte ist der Mastodon-Kanal @punypunypuns, der Wortspiele auf Deutsch veröffentlicht – mit Bildern illustriert oder einfach als Text. Ich habe ein Python-Skript geschrieben, das alle Beiträge dieses Kanals herunterlädt und zu einem PDF zusammenstellt, das sich perfekt als Geschenkbuch eignet. Der Kanal gehört mir, ihr bekommt also keine Probleme mit dem Urheberrecht, solange ihr die Heftchen nicht verkauft.

Schritt 2: Mit dem Skript das PDF erstellen

Das Skript ist auf den Kanal @punypunypuns zugeschnitten, lässt sich aber leicht für andere Mastodon-Kanäle anpassen. Wenn ihr es für einen anderen Kanal nutzen wollt, holt euch vorher die Erlaubnis der Person, der die Inhalte gehören. Das Skript extrahiert die Beiträge, bereinigt den Text und stellt alles in einer PDF-Datei zusammen. Ihr findet das Skript hier auf GitLab oder weiter unten.

Schritt 3: Aus dem PDF ein Heftchen machen

Wenn ihr euer PDF habt, könnt ihr es mit einem Tool wie online2pdf.com in ein A5-Heftchen umwandeln. Das Tool ordnet die Seiten so an, dass sie in der richtigen Reihenfolge sind, wenn ihr sie auf A4 druckt, in der Mitte faltet und bindet.

Schritt 4: Das Heftchen mit einem Tacker binden

Mit einem Tacker (Hefter) lässt sich das Heftchen ganz einfach binden. So geht’s:

  1. Heftchen drucken: Druckt das erstellte A5-Heftchen auf A4-Papier aus – unbedingt doppelseitig.

  2. Seiten falten: Faltet den Stapel gedruckter Seiten in der Mitte und richtet die Kanten sorgfältig aus.

  3. Rücken heften: Heftet die gefaltete Kante (den Rücken) mit dem Tacker. Setzt zwei oder drei Klammern gleichmäßig entlang des Rückens, damit die Seiten gut zusammenhalten.

Fertig ist ein handgemachtes, lustiges Geschenkbuch, das persönlich und kreativ ist und euren Freunden garantiert den Tag versüßt!

import os
import json
from datetime import datetime
from mastodon import Mastodon
from bs4 import BeautifulSoup
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import inch
from reportlab.lib.utils import ImageReader
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import (
    SimpleDocTemplate,
    Paragraph,
    Spacer,
    Image,
    PageBreak,
    Frame,
    PageTemplate,
)

# Load configuration from config.json
# {
#     "client_id": "your_client_id",
#     "client_secret": "your_client_secret",
#     "access_token": "your_access_token",
#     "api_base_url": "https://your.instance.url"
# }
with open("config.json", "r") as config_file:
    config = json.load(config_file)

# Set up Mastodon API instance
mastodon = Mastodon(
    client_id=config["client_id"],
    client_secret=config["client_secret"],
    access_token=config["access_token"],
    api_base_url=config["api_base_url"],
)


# Function to fetch all toots from a user
def fetch_all_toots(username):
    user = mastodon.account_search(username)[0]
    user_id = user["id"]

    toots = []
    max_id = None

    while True:
        if max_id:
            new_toots = mastodon.account_statuses(user_id, max_id=max_id)
        else:
            new_toots = mastodon.account_statuses(user_id)

        if not new_toots:
            break

        toots.extend(new_toots)
        max_id = new_toots[-1]["id"] - 1

    return toots


# Custom JSON encoder to handle datetime objects
class DateTimeEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.isoformat()
        return super(DateTimeEncoder, self).default(obj)


# Fetch all toots from the user @punypunypuns
username = "punypunypuns"
toots = fetch_all_toots(username)

# Create output directory if it does not exist
output_dir = "punypunypuns"
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# Save toots to a file
output_file = os.path.join(output_dir, f"{username}_toots.json")
with open(output_file, "w", encoding="utf-8") as f:
    json.dump(toots, f, ensure_ascii=False, indent=4, cls=DateTimeEncoder)

print(f"All toots from @{username} have been downloaded and saved to {output_file}")

# Extract the required properties, remove HTML tags from content, and skip toots containing "yanzi"
extracted_data = []
for toot in toots:
    content = BeautifulSoup(toot["content"], "html.parser").get_text()
    if "yanzi" in content.lower():
        continue
    toot_data = {
        "content": content,
        "media_attachments": [media["url"] for media in toot["media_attachments"]],
    }
    extracted_data.append(toot_data)

# Save the extracted data to another JSON file
extracted_output_file = os.path.join(output_dir, f"{username}_extracted_toots.json")
with open(extracted_output_file, "w", encoding="utf-8") as f:
    json.dump(extracted_data, f, ensure_ascii=False, indent=4)

print(f"Extracted data from toots have been saved to {extracted_output_file}")

# Generate a PDF file with the extracted toots
pdf_file = os.path.join(output_dir, f"{username}_toots.pdf")
doc = SimpleDocTemplate(pdf_file, pagesize=A4)
width, height = A4
styles = getSampleStyleSheet()
font_size = 16

# Define custom styles for the title, subtitle, and content
title_style = ParagraphStyle(
    "Title",
    parent=styles["Normal"],
    fontSize=36,
    leading=42,
    alignment=1,  # Center alignment
    spaceAfter=20,
)

subtitle_style = ParagraphStyle(
    "Subtitle",
    parent=styles["Normal"],
    fontSize=24,
    leading=28,
    alignment=1,  # Center alignment
    spaceAfter=40,
)

content_style = ParagraphStyle(
    "Content",
    parent=styles["Normal"],
    fontSize=font_size,
    leading=font_size * 1.2,
    alignment=1,  # Center alignment
    spaceAfter=12,
)

story = []

# Add the title page
title = Paragraph("puny puny puns", title_style)
subtitle = Paragraph("von Sonja und Lorenz", subtitle_style)

# Center the title and subtitle vertically and horizontally
title_page_frame = Frame(
    doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="title_page_frame"
)


def title_page(canvas, doc):
    canvas.saveState()
    title_page_frame.addFromList([Spacer(1, height / 2.5), title, subtitle], canvas)
    canvas.restoreState()


title_page_template = PageTemplate(
    id="TitlePage", frames=title_page_frame, onPage=title_page
)
content_page_template = PageTemplate(
    id="ContentPage",
    frames=[
        Frame(
            doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id="content_frame"
        )
    ],
)

doc.addPageTemplates([title_page_template, content_page_template])

# Add the title page and then a page break
story.append(PageBreak())

# Add the extracted toots
for toot in extracted_data:
    if toot["media_attachments"]:
        # Load the image
        image_url = toot["media_attachments"][0]
        try:
            image = ImageReader(image_url)
            image_width, image_height = image.getSize()
            aspect_ratio = image_height / float(image_width)

            # Determine the display size while maintaining the aspect ratio
            max_display_width = width * 0.75
            max_display_height = height * 0.75

            if image_width > max_display_width or image_height > max_display_height:
                if (max_display_width / image_width) < (
                    max_display_height / image_height
                ):
                    display_width = max_display_width
                    display_height = display_width * aspect_ratio
                else:
                    display_height = max_display_height
                    display_width = display_height / aspect_ratio
            else:
                display_width = image_width
                display_height = image_height

            image_element = Image(image_url, width=display_width, height=display_height)
            story.append(image_element)
        except Exception as e:
            print(f"Error loading image {image_url}: {e}")
            pass

    # Add a spacer to vertically center the text if no images are present
    if not toot["media_attachments"]:
        story.append(Spacer(1, height / 2.5))

    # Add the text
    content = Paragraph(toot["content"], content_style)
    story.append(content)
    story.append(PageBreak())  # Add a page break after each toot

doc.build(story)

print(f"PDF file with toots has been generated: {pdf_file}")

# Delete the JSON files if the PDF was successfully created
if os.path.exists(pdf_file):
    os.remove(output_file)
    os.remove(extracted_output_file)
    print("JSON files have been deleted.")
else:
    print("PDF file was not created successfully. JSON files have not been deleted.")

ChatGPT-Quelle