Skip to content
This repository has been archived by the owner on Dec 26, 2022. It is now read-only.

✨ Added an imager welcome example #453

Merged
merged 12 commits into from
Apr 9, 2022
6 changes: 6 additions & 0 deletions examples/discord-welcomer/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Your bot token here
BOT_TOKEN=

# The id of the channel to where messages should be sent
WELCOME_CHANNEL=

4 changes: 4 additions & 0 deletions examples/discord-welcomer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
.idea
.vscode
.env
25 changes: 25 additions & 0 deletions examples/discord-welcomer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Image welcomer

A basic implementation of a welcome message with image. Utilized the xiler html -> img
API.

This script has been build more extensive, which could provide some idea's on how you
can load cogs and structure your project so that it is easily maintainable.

![image](https://user-images.githubusercontent.com/38541241/162048398-b9942760-ea67-4f0d-99cd-ebd05c78fd63.png)

## Setup

1. Install all dependencies with pip from the requirements.txt
file. _(`pip install -r requirements.txt`)_

2. Copy the `.env.example` file to `.env`, and fill in the expected values.

3. Run the `main.py` file.

## Limitations

This uses the free version of the html->image api. Which restricts to a max of 100
requests per day. _(window sliding)_
You could properly handle this by looking at the HTTP headers, but this example does not
cover that.
13 changes: 13 additions & 0 deletions examples/discord-welcomer/cogs/error_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
Processes all exceptions that occur in runtime.
"""
import logging

from pincer import Cog, Client


class ErrorHandler(Cog):
@Client.event
async def on_error(self, error: Exception, *args, **kwargs):
# You should probably handle this more specific
logging.error(error)
93 changes: 93 additions & 0 deletions examples/discord-welcomer/cogs/templates/welcome.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta content="IE=edge" http-equiv="X-UA-Compatible"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<title>Welcome template for Pincer example</title>
<style>
main {
width: 700px;
height: 250px;

color: #fff;
background-color: #2f6491;

font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;

border-radius: 15px;

display: -webkit-box;
display: -ms-flexbox;
display: flex;

-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}

h1 {
text-transform: uppercase;
}

.img-wrapper,
img {
width: 200px;
height: 200px;

border-radius: 50%;
}

.img-wrapper {
margin-left: 70px;

border: 4px solid #ffd53d;
Arthurdw marked this conversation as resolved.
Show resolved Hide resolved
}

h1 {
font-size: 45px;
margin: 0;
}

h2,
h3 {
margin: 0;
font-size: 35px;
}

h2 {
margin-top: 5px;
white-space: nowrap;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}

h3 {
margin-top: -7px;
opacity: 0.75;
font-size: 20px;
}

.parent {
margin-left: 30px;
width: 350px;
}
</style>
</head>
<body>
<main>
<div class="img-wrapper">
<img alt="User icon" src="%icon%"/>
</div>

<div class="parent">
<h1>Welcome</h1>
<div>
<h2>%username%<span></span></h2>
<h3>#%discriminator%</h3>
</div>
</div>
</main>
</body>
</html>
90 changes: 90 additions & 0 deletions examples/discord-welcomer/cogs/welcome.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""
Properly welcome your new members into your server!
"""
from __future__ import annotations

import json
import logging
from asyncio import sleep
from os import getenv
from typing import TYPE_CHECKING

from aiohttp import ClientSession

from pincer import Cog, Client
from pincer.objects import File, Message
from pincer.objects.events.guild import GuildMemberAddEvent

if TYPE_CHECKING:
from pincer.objects import Channel, User


class Welcome(Cog):
channel: Channel
BASE_API_URL = "https://api.xiler.net"
REQUEST_HEADERS = {
"Content-Type": "application/json",
}

def __init__(self, client: Client):
super().__init__(client)
self.__session: ClientSession = ClientSession(self.BASE_API_URL)

with open("./cogs/templates/welcome.html", "r") as f:
Arthurdw marked this conversation as resolved.
Show resolved Hide resolved
self.template = f.read()

def _build_html(self, user: User) -> str:
"""Build a HTML template to welcome new users!"""
arguments = {
"icon": user.get_avatar_url(256),
"username": user.username,
"discriminator": user.discriminator
}

res = self.template

for key, value in arguments.items():
res = res.replace(f"%{key}%", value)

return res

async def generate_welcome_for_user(self, user: User, ttl=0) -> File:
"""Generate the welcome file, this gets sent to discord!"""
body = {
"html": self._build_html(user),
"config": {
"format": "png",
"width": 700,
"height": 258,
"transparent": True,
}
}

async with self.__session.post(
"/v1/html-to-image",
data=json.dumps(body),
headers=self.REQUEST_HEADERS
) as res:
if res.ok:
image_bytes = await res.read()
return File(image_bytes, "png", f"welcome-{user.id}.png")

logging.error(await res.read())

# Allow a maximum of 5 retries
if ttl < 5:
await sleep(0.5)
return await self.generate_welcome_for_user(user, ttl + 1)

raise RuntimeError(
"TTL Exceeded, not retrying request! (failed for welcomes)"
)

@Client.event
async def on_ready(self):
self.channel = await self.client.get_channel(int(getenv("WELCOME_CHANNEL")))

@Client.event
async def on_guild_member_add(self, event: GuildMemberAddEvent):
banner = await self.generate_welcome_for_user(event)
await self.channel.send(Message(attachments=[banner]))
63 changes: 63 additions & 0 deletions examples/discord-welcomer/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
A simple discord bot which displays the use case of sending a welcome image when a user joins a guild!
This is a simple example of a more worked out bot.
"""
import logging
from glob import glob
from importlib import import_module
from os import getenv
from time import perf_counter

from dotenv import load_dotenv

from pincer import Client, Cog


class Bot(Client):
loaded_cogs = 0

def __init__(self, *args, **kwargs):
cog_loading_started = perf_counter()
self.load_packages()
cog_loading_ended = perf_counter()
self.cogs_load_time = round((cog_loading_ended - cog_loading_started) * 100, 2)
super().__init__(*args, **kwargs)

@Client.event
async def on_ready(self):
print(f"Successfully running bot on {self.bot}!")
print(f"Loaded {self.loaded_cogs} cog(s) in {self.cogs_load_time}ms")

@staticmethod
def _process_package_location(location: str) -> str:
"""Convert a file path to a python importable path"""
return location.replace("/", ".").replace("\\", ".")[:-3]

def load_packages(self):
"""
Goes through all files in the Cogs directory, and loads the classes that inherit
from pincer.Cog.

Side effects: Imports all files that it maps over in this file.
"""
for container in glob("cogs/*.py"):
module = import_module(self._process_package_location(container))

for definition in dir(module):
if definition.startswith("_"):
continue

if obj := getattr(module, definition, None):
parents = getattr(obj, "__bases__", None) or []

if Cog in parents:
self.load_cog(obj)
self.loaded_cogs += 1
logging.info(
"Successfully loaded %s (%s)" % (definition, container))
Arthurdw marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == "__main__":
load_dotenv()
logging.basicConfig(level=logging.WARN)
Bot(getenv("BOT_TOKEN")).run()
3 changes: 3 additions & 0 deletions examples/discord-welcomer/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pincer
python-dotenv
aiohttp