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