Completed
Push — main ( c013d1...e22552 )
by Yohann
29s queued 12s
created

tweet_generator.trans_paste()   A

Complexity

Conditions 1

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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