Passed
Pull Request — main (#241)
by
unknown
01:47
created

tweet_generator.Bot.on_ready()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 1
1
import re
2
import textwrap
3
import os
4
import sys
5
from datetime import datetime
6
from PIL import Image, ImageFont, ImageDraw, ImageOps
7
from pincer import command, Client
8
from pincer.commands import CommandArg, Description
9
from pincer.objects import Message, Embed, MessageContext
10
11
12
# you need to manually download the font files and put them into the folder
13
# ./examples/tweet_generator/ to make the script works using this link:
14
# https://fonts.google.com/share?selection.family=Noto%20Sans:wght@400;700
15
if not all(font in os.listdir() for font in ["NotoSans-Regular.ttf", "NotoSans-Bold.ttf"]):
16
    sys.exit()
17
18
19
class Bot(Client):
20
    @Client.event
21
    async def on_ready(self):
22
        print(
23
            f"Started client on {self.bot}\n"
24
            f"Registered commands: {', '.join(self.chat_commands)}"
25
        )
26
27
    @command(
28
        description="to create fake tweets",
29
    )
30
    async def twitter(
31
        self,
32
        ctx: MessageContext,
33
        content: CommandArg[str, Description["The content of the message"]]
34
    ):
35
        await ctx.interaction.ack()
36
37
        for text_match, user_id in re.findall(
38
            re.compile(r"(<@!(\d+)>)"), content
39
        ):
40
            content = content.replace(
41
                text_match, f"@{await self.get_user(user_id)}"
42
            )
43
44
        if len(content) > 280:
45
            return "A tweet can be at maximum 280 characters long"
46
47
        # download the profile picture and convert it into Image object
48
        avatar = (await ctx.author.user.get_avatar()).resize((128, 128))
49
        avatar = circular_avatar(avatar)
50
51
        # create the tweet by pasting the profile picture into a white image
52
        tweet = trans_paste(
53
            avatar,
54
            Image.new("RGBA", (800, 250 + 50 * len(textwrap.wrap(content, 38))), (255, 255, 255)),
55
            box=(15, 15),
56
        )
57
58
        # add the fonts
59
        font_normal = ImageFont.truetype("NotoSans-Regular.ttf", 40)
60
        font_small = ImageFont.truetype("NotoSans-Regular.ttf", 30)
61
        font_bold = ImageFont.truetype("NotoSans-Bold.ttf", 40)
62
63
        # write the name and username on the Image
64
        draw = ImageDraw.Draw(tweet)
65
        draw.text(
66
            (180, 20), str(ctx.author.user),
67
            fill=(0, 0, 0), font=font_bold
68
        )
69
        draw.text(
70
            (180, 70),
71
            f"@{ctx.author.user.username}",
72
            fill=(120, 120, 120), font=font_normal,
73
        )
74
75
        content = add_color_to_mentions(content)
76
77
        # write the text
78
        tweet = draw_multicolored_text(tweet, content, font_normal)
79
80
        # write the footer
81
        draw.text(
82
            (30, tweet.size[1] - 60),
83
            datetime.now().strftime(
84
                "%I:%M %p · %d %b. %Y · Twitter for Discord"
85
            ),
86
            fill=(120, 120, 120),
87
            font=font_small,
88
        )
89
90
        return Message(
91
            embeds=[
92
                Embed(title="Twitter for Discord", description="").set_image(
93
                    url="attachment://image0.png"
94
                )
95
            ],
96
            attachments=[tweet],
97
        )
98
99
100
def trans_paste(fg_img, bg_img, box=(0, 0)):
101
    """
102
    https://stackoverflow.com/a/53663233/15485584
103
    paste an image into one another
104
    """
105
    fg_img_trans = Image.new("RGBA", fg_img.size)
106
    fg_img_trans = Image.blend(fg_img_trans, fg_img, 1.0)
107
    bg_img.paste(fg_img_trans, box, fg_img_trans)
108
    return bg_img
109
110
111
def circular_avatar(avatar):
112
    mask = Image.new("L", (128, 128), 0)
113
    draw = ImageDraw.Draw(mask)
114
    draw.ellipse((0, 0, 128, 128), fill=255)
115
    avatar = ImageOps.fit(avatar, mask.size, centering=(0.5, 0.5))
116
    avatar.putalpha(mask)
117
    return avatar
118
119
120
def add_color_to_mentions(message):
121
    """
122
    generate a dict to set were the text need to be in different colors.
123
    if a word starts with '@' it will be write in blue.
124
125
    Parameters
126
    ----------
127
    message: the text
128
129
    Returns
130
    -------
131
    a list with all colors selected
132
    example:
133
        [
134
            {'color': (0, 0, 0), 'text': 'hello world '},
135
            {'color': (0, 154, 234), 'text': '@drawbu'}
136
        ]
137
138
    """
139
    message = textwrap.wrap(message, 38)
140
    message = "\n".join(message).split(" ")
141
    result = []
142
    for word in message:
143
        for index, text in enumerate(word.splitlines()):
144
145
            text += "\n" if index != len(word.split("\n")) - 1 else " "
146
147
            if not result:
148
                result.append({"color": (0, 0, 0), "text": text})
149
                continue
150
151
            if not text.startswith("@"):
152
                if result[-1:][0]["color"] == (0, 0, 0):
153
                    result[-1:][0]["text"] += text
154
                    continue
155
156
                result.append({"color": (0, 0, 0), "text": text})
157
                continue
158
159
            result.append({"color": (0, 154, 234), "text": text})
160
    return result
161
162
163
def draw_multicolored_text(image, message, font):
164
    draw = ImageDraw.Draw(image)
165
    x = 30
166
    y = 170
167
    for text in message:
168
        y -= font.getsize(" ")[1]
169
        for l_index, line in enumerate(text["text"].splitlines()):
170
            if l_index:
171
                x = 30
172
            y += font.getsize(" ")[1]
173
            draw.text((x, y), line, fill=text["color"], font=font)
174
            x += font.getsize(line)[0]
175
    return image
176
177
178
if __name__ == "__main__":
179
    # Of course we have to run our client, you can replace the
180
    # XXXYOURBOTTOKENHEREXXX with your token, or dynamically get it
181
    # through a dotenv/env.
182
    Bot("XXXYOURBOTTOKENHEREXXX").run()
183