/
telegram_manager.py
272 lines (200 loc) · 7.42 KB
/
telegram_manager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
from bot_data import token
from enum import Enum
from stateless_bridge import main as bridgeMain
from telepot import Bot, DelegatorBot, glance
from telepot.delegate import per_chat_id, create_open, pave_event_space
from telepot.helper import ChatHandler
from telepot.loop import MessageLoop
from telepot.namedtuple import InlineKeyboardMarkup, InlineKeyboardButton
from telepot.namedtuple import ReplyKeyboardMarkup, KeyboardButton
from threading import Thread
from typing import Dict, List
import time
bot = None
messageBuffer = []
CHAR_TO_ESCAPE_LIST = ['(', ')', '.', '=']
class BotCommand(Enum):
START = '/bridge'
NAME = '/player'
BID = '/bid'
PARTNER = '/partner'
CARD = '/'
HELP = '/help'
STARTING_TEXT = '''Welcome to Lazy Bridge Bot!
Inputs to this bot are to be entered using the following syntax:
/<COMMAND> <SPACE> <VALUE>
Command list:
{start} - Start a game
{name} - Enter a player's name
{bid} - Enter bid
{partner} - Enter partner of choice
{card} - Enter card to be played
Bid value is to be constructed using the following pattern:
<BID LEVEL><SUIT>
where BID LEVEL = no. 1-7
Examples: 1C, 2D, 3H, 4S, 5NT
Partner and card value is to be constructed using the following pattern:
<CARD NUM><SUIT>
where CARD NUM = 2, 3, ... J, Q, K, A
Examples: 2C, 10D, KH, AS
When card inputs are necessary, card buttons showing your current hand will be available to you. You can use these buttons to play your card.
'''.format(
start=BotCommand.START.value,
name=BotCommand.NAME.value,
bid=BotCommand.BID.value,
partner=BotCommand.PARTNER.value,
card=BotCommand.CARD.value
)
class TelegramManager(ChatHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bridgeThread: Thread = None
self.messageBuffer: List[dict] = list()
self.playerIdList: List[int] = list()
self.chatIdMap: Dict[str, int] = dict()
self.nameMap: Dict[str, str] = dict()
def startBridgeThread(self):
bridgeArgs = (
self.getPlayerNameInput,
self.getBidInput,
self.getPartnerInput,
self.getCardInput,
self.showText,
self.showCards,
)
self.bridgeThread = Thread(target=bridgeMain, args=bridgeArgs)
self.bridgeThread.start()
def sanitiseString(self, string: str) -> str:
for char in CHAR_TO_ESCAPE_LIST:
string = string.replace(char, '\\' + char)
return string
def charToEmoji(self, string: str) -> str:
return string.replace('C', '♣️').replace('D', '♦️').replace('H', '♥️').replace('S', '♠️')
def charFromEmoji(self, string: str) -> str:
return string.replace('♣️', 'C').replace('♦️', 'D').replace('♥️', 'H').replace('♠️', 'S')
def on_chat_message(self, msg):
content_type, chat_type, chat_id = glance(msg)
try:
command = msg['text'].strip().lower()
except KeyError:
# ignore non-text messages
return
if command == BotCommand.START.value:
if self.bridgeThread:
self.sender.sendMessage("A game is still running!")
else:
self.chatId = chat_id
self.sender.sendMessage(STARTING_TEXT)
self.startBridgeThread()
else:
self.messageBuffer.append(msg)
print('appended msg')
def getPlayerNameInput(self, targetId: str, text: str = '') -> str:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
text = self.sanitiseString(text)
self.sender.sendMessage(text)
while True:
if self.messageBuffer:
message = self.messageBuffer.pop(0)
commandArgs = message['text'].strip().split(' ')
if commandArgs[0] == BotCommand.NAME.value:
self.chatIdMap[targetId] = message['from']['id']
try:
name = ' '.join(commandArgs[1:])
self.nameMap[targetId] = name
except IndexError:
continue
break
time.sleep(0.01)
return name
def getBidInput(self, targetId: str, text: str = '') -> str:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
warning = r'Card buttons are to display your hand only. DO NOT PRESS ANY OF THE CARD BUTTONS.'
text += '\n' + warning
text = self.sanitiseString(text)
bidMessage = '[{}](tg://user?id={}) '.format(self.nameMap[targetId], self.chatIdMap[targetId]) + text
self.sender.sendMessage(bidMessage, parse_mode='MarkdownV2')
bidInput = ''
while True:
if self.messageBuffer:
message = self.messageBuffer.pop(0)
if message['from']['id'] == self.chatIdMap[targetId]:
commandArgs = message['text'].strip().split(' ')
if commandArgs[0] == BotCommand.BID.value:
try:
bidInput = commandArgs[1]
except IndexError:
continue
break
time.sleep(0.01)
return bidInput
def getPartnerInput(self, targetId: str, text: str = '') -> str:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
text = self.sanitiseString(text)
partnerMessage = '[{}](tg://user?id={}) '.format(self.nameMap[targetId], self.chatIdMap[targetId]) + text
self.sender.sendMessage(partnerMessage, parse_mode='MarkdownV2')
partnerInput = ''
while True:
if self.messageBuffer:
message = self.messageBuffer.pop(0)
if message['from']['id'] == self.chatIdMap[targetId]:
commandArgs = message['text'].strip().split(' ')
if commandArgs[0] == BotCommand.PARTNER.value:
try:
partnerInput = commandArgs[1]
except IndexError:
continue
break
return partnerInput
def getCardInput(self, targetId: str, text: str = '') -> str:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
text = self.sanitiseString(text)
playMessage = '[{}](tg://user?id={}) '.format(self.nameMap[targetId], self.chatIdMap[targetId]) + text
self.sender.sendMessage(playMessage, parse_mode='MarkdownV2')
cardInput = ''
while True:
if self.messageBuffer:
message = self.messageBuffer.pop(0)
if message['from']['id'] == self.chatIdMap[targetId]:
commandArgs = message['text'].strip().split(' ')
if commandArgs[0] == BotCommand.CARD.value:
try:
cardInput = commandArgs[1]
cardInput = self.charFromEmoji(cardInput)
except IndexError:
continue
break
time.sleep(0.01)
return cardInput
def showText(self, text: str = '') -> None:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
self.sender.sendMessage(text)
def showCards(self, player: dict) -> None:
if not self.bridgeThread.is_alive():
self.sender.sendMessage('bridge thread stopped ...')
playerId = self.chatIdMap[player['side']]
sortedReversedDeck = sorted(card['suit'] + card['num'] for card in player['hand'])
handStr = '_'.join([BotCommand.CARD.value + ' ' + rCard[1:] + rCard[0] for rCard in sortedReversedDeck])
handStr = self.charToEmoji(handStr)
newHand = handStr.split('_')
keyboardButtons = [
[newHand[i] for i in range(min(len(newHand), 6))],
[newHand[i+6] for i in range(len(newHand) - 6)]
]
keyboard = ReplyKeyboardMarkup(keyboard=keyboardButtons, resize_keyboard=True, one_time_keyboard=True, selective=True)
self.sender.sendMessage('Display [{}](tg://user?id={}) hand\.'.format(player['name'], playerId), parse_mode='MarkdownV2', reply_markup=keyboard)
def main():
bot = DelegatorBot(token, [ pave_event_space()(per_chat_id(types=['group']), create_open, TelegramManager, timeout=300) ])
MessageLoop(bot).run_as_thread()
while 1:
time.sleep(10)
if __name__ == '__main__':
# todo:
# reply keyboard button text and message text to be different
main()
pass