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