@hamann.jonas: Da ist so vieles komisch bis falsch, dass man glatt vermuten könnte Du versuchst Dir von ChatGPT auch das Programm schreiben zu lassen. Das wird nicht wirklich funktionieren. Man muss dafür so viel Ahnung vom Programmieren haben, dass man es auch selbst machen könnte, denn sonst kann man nicht beurteilen ob das sinnvoll ist, was ChatGPT da ausspuckt, und kann auch die Fehler nicht erkennen und beheben die es da immer wieder einbaut.
Teilweise sind da auch total offensichtliche Fehler drin, die zu sehr eindeutigen Fehlermeldungen führen, was ja bedeutet, dass Du das überhaupt gar nicht hast laufen lassen, denn sonst hättest Du ja diese Fehler hier gepostet. Oder behoben.
`asyncio` und `shutil` werden importiert aber nicht verwendet. `asyncio` *sollte* verwendet werden, aber `shutil` kann weg.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.
Dateien die man öffnet, sollte man auch wieder schliessen. Wo möglich verwendet man dazu die ``with``-Anweisung.
Beim öffnen von Textdateien sollte man immer explizit die Kodierung angeben.
`load_dotenv()` sollte man so früh wie möglich aufrufen. Wobei zumindest aus dem gezeigten Quelltext nicht ersichtlich wird ob das überhaupt gebraucht wird. Da könnte man einen Kommentar dran schreiben was das bewirken soll.
`save_prompts()` macht nichts asynchrones, da macht es keinen Sinn die Funktion als ``async`` zu definieren. Die Funktion wird auch nirgends aufgerufen. Was in `generate_prompts()` aufgerufen wird ist `save_promts()`, was es nicht gibt. Das wäre einer der Fehler die definitiv aufgefallen wären, wenn das mal gelaufen wäre.
Dateimodi mit "+" sind bei Textdateien eigentlich immer falsch. So auch in `save_promts()`.
Das `join()` in der Funktion sorgt für eine Leerzeile zwischen den Prompts, beim einlesen sieht der Code allerdings so aus, als sollte in jeder Zeile ein Prompt stehen und nicht nur in jeder zweiten. Zudem ist `join()` falsch, denn das setzt die Zeilenendezeichen ja nur *zwischen* die Prompts, schliesst aber den letzten *nicht* mit einem Zeilenendezeichen ab.
Falsche Kommentare sind schlimmer als keine Kommentare. Ein Kommentar sollen eventuelle Fragen mit dem Code klären, aber wenn dort falsche Informationen drin stehen, oder gar welche die dem Code direkt wiedersprechen, erreicht man genau das Gegenteil. Der Leser weiss dann nicht was falsch ist, der Code oder der Kommentar. Der Kommentar das in `generate_prompts()` eine Endlosschleife ist, stimmt nicht.
`generate_prompts()` macht ebenfalls nichts asynchrones, braucht also auch kein ``async``.
Die Funktion wird auch überhaupt nicht aufgerufen. Damit werden dann natürlich auch keine Prompts vom Benutzer abgefragt.
In `on_message()` wird `file_prefix` für jeden Anhang bestimmt, aber aufgrund des Nachrichteninhalts der ja für alle Anhänge unverändert ist. Das gehört also vor die Schleife.
Eine Schleife die immer genau einmal durchlaufen wird, ist offensichtlich keine Schleife.
Bei der Fernsteuerung des Browsers steht zweimal der gleiche Code in der Funktion. So etwas macht man als Programmierer nicht. Das würde man in eine eigene Funktion heraus ziehen.
Dabei braucht man dann auch das `asyncio`-Modul, denn in einer ``async``-Funktion verwendet man kein `time.sleep()` das die Async-Loop blockiert.
Ist das tatsächlich so gewollt das pro Anhang zu machen, ohne das irgendwelche Informationen aus dem Anhang selbst verwendet werden‽
`quit()` gibt es nicht. Das ist eine Funktion die wegen der interaktiven Python-Shell existiert, die hat in normalen Programmen nichts zu suchen. Es erscheint mir hier auch nicht wirklich sinnvoll das Programm nach *einer* empfangenen Nachricht abbrechen zu wollen.
In `download_image()`, das zumindest potentiell aufgerufen werden kann, wenn entsprechende Nachrichten eintreffen, sind wieder krass auffällige Fehler. Gleich in der ersten Zeile wird `requests` benutzt, was aber gar nicht importiert wird, und in der Funktion wird versucht ein undefiniertes `directory` zu verwenden.
Es werden Pfade mit Zeichenkettenoperationen erstellt obwohl im Programm auch `os.path.join()` verwendet wird.
In neuem Code sollte man auch nicht mehr die `os.path`-Funktionen verwenden, sondern `pathlib`.
Gespeichert wird die Bilddatei *mit* dem undefinierten `directory`, zum aufteilen des Bildes wird dann aber versucht ohne diesen zusätzlich Pfadpräfix die Datei zu öffnen. Wieder so ein anzeichen, dass dieser Code nie ausprobiert wurde, weil das dabei ja aufgefallen wäre. Ebenso das `os.remove()` natürlich nicht funktioniert wenn man vorher im ``else``-Zweig die Datei die entfernt werden soll umbenannt hat.
Auch `download_image()` ist wieder völlig unnötig als ``async`` definiert.
Ungetesteter Zwischenstand bei dem unbedingt die globalen Variablen verschwinden sollten:
Code: Alles auswählen
#!/usr/bin/env python3
import asyncio
import discord
import openai
import pyautogui
import requests
from discord.ext import commands
from dotenv import load_dotenv
from PIL import Image
DISCORD_TOKEN = "YOUR_DISCORD_TOKEN"
PROMPTS_FILENAME = "prompts.txt"
#
# TODO Whatever `directory` in `download_image()` was supposed to be.
#
IMAGES_PATH = Path(".")
#
# FIXME Remove global variables!!!
#
client = commands.Bot(command_prefix="*", intents=discord.Intents.all())
prompts = []
prompt_counter = 0
def save_prompts(prompts):
with open("prompts.txt", "w", encoding="utf-8") as file:
file.writelines(prompt + "/n" for prompt in prompts)
def generate_prompts():
save_prompts(
[
openai.Completion.create(
engine="gpt-3.5-turbo",
prompt=input("give a detailed prompt for midjourney: "),
max_tokens=1024,
n=1,
stop=None,
temperature=0.5,
)
.choices[0]
.text.strip()
for _ in range(10)
]
)
def split_image(file_path):
with Image.open(file_path) as image:
width, height = image.size
center_x = width // 2
center_y = height // 2
return (
image.crop((0, 0, center_x, center_y)),
image.crop((center_x, 0, width, center_y)),
image.crop((0, center_y, center_x, height)),
image.crop((center_x, center_y, width, height)),
)
def download_image(url, filename):
response = requests.get(url, timeout=None)
if response.status_code == 200:
input_path = IMAGES_PATH / "input"
output_path = IMAGES_PATH / "output"
input_path.mkdir(parents=True, exist_ok=True)
output_path.mkdir(parents=True, exist_ok=True)
input_file_path = input_path / filename
input_file_path.write_bytes(response.content)
print(f"Image downloaded: {filename}")
if input_file_path.name.startswith("UPSCALED_"):
input_file_path.rename(output_path / input_file_path.name)
else:
name_prefix = input_file_path.stem
for image, name_suffix in zip(
split_image(input_file_path),
["top_left", "top_right", "bottom_left", "bottom_right"],
):
image.save(output_path / f"{name_prefix}_{name_suffix}.jpg")
input_file_path.unlink()
async def automate_browser(prompt):
await asyncio.sleep(3)
pyautogui.write("/imagine")
await asyncio.sleep(5)
pyautogui.press("tab")
pyautogui.write(prompt)
await asyncio.sleep(3)
pyautogui.press("enter")
await asyncio.sleep(5)
@client.event
async def on_ready():
print("Bot connected")
@client.event
async def on_message(message):
global prompt_counter
print(message)
#
# TODO Instead of passing this information as part of the filename to
# `download_image()` it would be cleaner as an explicit flag, passed as an
# additional argument.
#
file_prefix = "UPSCALED_" if "Upscaled by" in message.content else ""
for attachment in message.attachments:
if attachment.filename.lower().endswith(
(".png", ".jpg", ".jpeg", ".gif")
):
download_image(
attachment.url, f"{file_prefix}{attachment.filename}"
)
while prompt_counter < len(prompts):
#
# Start automation by typing "automation" in the discord channel.
#
if message.content == "automation":
await asyncio.sleep(3)
pyautogui.press("tab")
automate_browser(prompts[prompt_counter])
prompt_counter += 1
#
# Continue automation as soon Midjourney bot sends a message with
# attachment.
#
for _ in message.attachments:
automate_browser(prompts[prompt_counter])
prompt_counter += 1
def main():
global prompts
load_dotenv()
openai.api_key = "YOUR KEY"
with open("prompts.txt", "r", encoding="utf-8") as file:
prompts = list(file)
client.run(DISCORD_TOKEN)
if __name__ == "__main__":
main()
Was hauptsächlich falsch gemacht wurde: Anscheinend wurde keine Funktion mal getestet bevor sie verwendet wurde, weil dabei vieles sehr leicht aufgefallen wäre. Programme schreibt man nicht in einem grossen Stück runter, sondern entwickelt sie Funktion für Funktion, und testet jede Funktion ausgiebig, bevor man weiter macht. Und man macht erst weiter wenn die zuletzt geschriebene Funktion auch tatsächlich funktioniert.