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