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