Upload 4 files
Browse filesUpdated the client service with History of Dialogues (Database usage)
- WarClient.py +6 -4
- WarOnline_Chat.py +47 -27
- config.py +38 -0
- conversationDB.py +51 -16
WarClient.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
import socket
|
|
|
2 |
|
3 |
-
HOST = '129.159.146.88'
|
4 |
-
PORT = 5000
|
5 |
|
6 |
def getReply(message):
|
7 |
# Returns a reply from the server
|
@@ -15,7 +14,7 @@ def getReply(message):
|
|
15 |
|
16 |
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
17 |
try:
|
18 |
-
client_socket.connect((HOST, PORT))
|
19 |
client_socket.sendall(message.encode())
|
20 |
print('Wait...')
|
21 |
data = client_socket.recv(1024)
|
@@ -28,4 +27,7 @@ def getReply(message):
|
|
28 |
client_socket.shutdown(socket.SHUT_RDWR)
|
29 |
client_socket.close()
|
30 |
except:
|
31 |
-
return ""
|
|
|
|
|
|
|
|
1 |
import socket
|
2 |
+
import config # Here the constants are stored
|
3 |
|
|
|
|
|
4 |
|
5 |
def getReply(message):
|
6 |
# Returns a reply from the server
|
|
|
14 |
|
15 |
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
16 |
try:
|
17 |
+
client_socket.connect((config.HOST, config.PORT))
|
18 |
client_socket.sendall(message.encode())
|
19 |
print('Wait...')
|
20 |
data = client_socket.recv(1024)
|
|
|
27 |
client_socket.shutdown(socket.SHUT_RDWR)
|
28 |
client_socket.close()
|
29 |
except:
|
30 |
+
return ""
|
31 |
+
|
32 |
+
if __name__ == '__main__':
|
33 |
+
pass
|
WarOnline_Chat.py
CHANGED
@@ -1,24 +1,16 @@
|
|
1 |
# This is an quote and post library for a specific thread in the WarOnline forum.
|
2 |
|
3 |
import WarClient
|
|
|
4 |
import requests
|
5 |
import re
|
6 |
from bs4 import BeautifulSoup
|
7 |
import urllib.request as urllib
|
8 |
import warnings
|
9 |
-
#import schedule
|
10 |
import time
|
11 |
-
#
|
12 |
-
warnings.filterwarnings("ignore")
|
13 |
-
|
14 |
-
# Define the login URL and the thread URL
|
15 |
-
login_url = 'https://waronline.org/fora/index.php?login/login'
|
16 |
-
thread_url = 'https://waronline.org/fora/index.php?threads/warbot-playground.17636/'
|
17 |
-
post_url = "https://waronline.org/fora/index.php?threads/warbot-playground.17636/add-reply"
|
18 |
|
19 |
-
|
20 |
-
username = 'WarBot'
|
21 |
-
password = 'naP2tion'
|
22 |
|
23 |
# Start a session to persist the login cookie across requests
|
24 |
session = requests.Session()
|
@@ -30,6 +22,9 @@ def fixString(S):
|
|
30 |
S = S.replace(".?", "?")
|
31 |
S = S.replace(",,", ",")
|
32 |
S = S.replace("?.", "?")
|
|
|
|
|
|
|
33 |
S = S.replace(",!", "!")
|
34 |
S = S.replace(",.", ",")
|
35 |
S = S.replace(".]", ".")
|
@@ -61,7 +56,7 @@ def remove_extra_spaces(s):
|
|
61 |
s = re.sub(r"\s+([.,])", r"\1", s) # remove spaces before period or comma
|
62 |
return(s)
|
63 |
|
64 |
-
def getLastPage(thread_url=thread_url):
|
65 |
# Returns the number of the last page
|
66 |
page = 1 # Starting page
|
67 |
lastPage = False
|
@@ -74,11 +69,11 @@ def getLastPage(thread_url=thread_url):
|
|
74 |
return page
|
75 |
|
76 |
|
77 |
-
def login(username=username, password=password, thread_url=thread_url):
|
78 |
# Log-In to the forum and redirect to thread
|
79 |
|
80 |
# Retrieve the login page HTML to get the CSRF token
|
81 |
-
login_page_response = session.get(login_url)
|
82 |
soup = BeautifulSoup(login_page_response.text, 'html.parser')
|
83 |
csrf_token = soup.find('input', {'name': '_xfToken'})['value']
|
84 |
|
@@ -90,14 +85,14 @@ def login(username=username, password=password, thread_url=thread_url):
|
|
90 |
'_xfRedirect': thread_url,
|
91 |
'_xfToken': csrf_token
|
92 |
}
|
93 |
-
response = session.post(login_url, data=login_data)
|
94 |
|
95 |
# Check if the login was successful
|
96 |
if 'Invalid login' in response.text:
|
97 |
print('Login failed!')
|
98 |
exit()
|
99 |
|
100 |
-
def post(message="", thread_url=thread_url, post_url=post_url, quoted_by="",quote_text="",quote_source=""):
|
101 |
#Post a message to the forum (with or without the quote
|
102 |
#quote_source is in format 'post-3920992'
|
103 |
quote_source = quote_source.split('-')[-1] # Take the numbers only
|
@@ -134,7 +129,7 @@ def post(message="", thread_url=thread_url, post_url=post_url, quoted_by="",quot
|
|
134 |
|
135 |
print('Post submitted successfully.')
|
136 |
|
137 |
-
def getMessages(thread_url=thread_url, quotedUser="", startingPage=1):
|
138 |
# Returns all the quotes for #username in the specific multi-page thread url
|
139 |
allquotes =[]
|
140 |
|
@@ -182,6 +177,7 @@ def getMessages(thread_url=thread_url, quotedUser="", startingPage=1):
|
|
182 |
if matchID:
|
183 |
messageID = matchID.group(1)
|
184 |
|
|
|
185 |
matchQuotedName = quotedNamePattern.search(str(data))
|
186 |
if matchQuotedName:
|
187 |
quotedName = matchQuotedName.group(1)
|
@@ -216,28 +212,28 @@ def getMessages(thread_url=thread_url, quotedUser="", startingPage=1):
|
|
216 |
def WarOnlineBot():
|
217 |
|
218 |
lookUpPages = 5 # How many pages back to look in the thread
|
219 |
-
startingPage = getLastPage(thread_url=thread_url) - lookUpPages
|
220 |
if startingPage < 1:
|
221 |
startingPage = 1 # Starting page cannot be less than 1
|
222 |
|
223 |
-
login(username=username, password=password, thread_url=thread_url)
|
224 |
#print("logged in")
|
225 |
|
226 |
# All messages (with quotes) by ALL users:
|
227 |
-
allMessages = getMessages(thread_url=thread_url, quotedUser='', startingPage=startingPage)
|
228 |
|
229 |
# IDs of the quoted messages, replied by the bot:
|
230 |
messages_by_bot_IDs = []
|
231 |
|
232 |
for msg in allMessages:
|
233 |
# Set a list of replied messages IDs
|
234 |
-
if msg['messengerName'] == username: #message posted by the WarBot
|
235 |
messages_by_bot_IDs.append(msg['quotedID'].split(': ')[-1])
|
236 |
# remove empty and repeated elements
|
237 |
messages_by_bot_IDs = list(set([elem for elem in messages_by_bot_IDs if elem]))
|
238 |
|
239 |
# All messages (with quotes) sent _FOR_ the Bot:
|
240 |
-
messagesForBot = getMessages(thread_url=thread_url, quotedUser=username, startingPage=startingPage)
|
241 |
|
242 |
# IDs of the messages, quoting the bot:
|
243 |
messages_for_bot_IDs = []
|
@@ -262,10 +258,27 @@ def WarOnlineBot():
|
|
262 |
quote = remove_non_english_russian_chars(msg['reply'])
|
263 |
quote = remove_extra_spaces(quote)
|
264 |
|
265 |
-
message = ""
|
|
|
266 |
|
267 |
print('Quote: ', originalQuote)
|
268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
269 |
FailureCounter = 0 # In case there is a bug in the model
|
270 |
while (not message) and (FailureCounter<3):
|
271 |
message = WarClient.getReply(message=quote)
|
@@ -281,20 +294,27 @@ def WarOnlineBot():
|
|
281 |
message = fixString(message)
|
282 |
print('Reply: ', message)
|
283 |
|
284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
285 |
time.sleep(1)
|
286 |
-
post(message=message, thread_url=thread_url, post_url=post_url, quoted_by=msg['messengerName'], quote_text=originalQuote, quote_source=msg['messageID'])
|
287 |
|
288 |
time.sleep(10) # Standby time for server load release
|
289 |
|
290 |
|
291 |
if __name__ == '__main__':
|
292 |
-
timeout = 5 # min
|
293 |
|
294 |
# Start the scheduler
|
295 |
while True:
|
|
|
296 |
WarOnlineBot()
|
297 |
|
298 |
-
timer = range(60 * timeout)
|
299 |
for t in timer:
|
300 |
time.sleep(1)
|
|
|
1 |
# This is an quote and post library for a specific thread in the WarOnline forum.
|
2 |
|
3 |
import WarClient
|
4 |
+
import conversationDB
|
5 |
import requests
|
6 |
import re
|
7 |
from bs4 import BeautifulSoup
|
8 |
import urllib.request as urllib
|
9 |
import warnings
|
|
|
10 |
import time
|
11 |
+
import config # Here the constants are stored
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
|
13 |
+
warnings.filterwarnings("ignore")
|
|
|
|
|
14 |
|
15 |
# Start a session to persist the login cookie across requests
|
16 |
session = requests.Session()
|
|
|
22 |
S = S.replace(".?", "?")
|
23 |
S = S.replace(",,", ",")
|
24 |
S = S.replace("?.", "?")
|
25 |
+
S = S.replace("??", "?")
|
26 |
+
S = S.replace(" ?", "?")
|
27 |
+
S = S.replace(" .", ".")
|
28 |
S = S.replace(",!", "!")
|
29 |
S = S.replace(",.", ",")
|
30 |
S = S.replace(".]", ".")
|
|
|
56 |
s = re.sub(r"\s+([.,])", r"\1", s) # remove spaces before period or comma
|
57 |
return(s)
|
58 |
|
59 |
+
def getLastPage(thread_url=config.thread_url):
|
60 |
# Returns the number of the last page
|
61 |
page = 1 # Starting page
|
62 |
lastPage = False
|
|
|
69 |
return page
|
70 |
|
71 |
|
72 |
+
def login(username=config.username, password=config.password, thread_url=config.thread_url):
|
73 |
# Log-In to the forum and redirect to thread
|
74 |
|
75 |
# Retrieve the login page HTML to get the CSRF token
|
76 |
+
login_page_response = session.get(config.login_url)
|
77 |
soup = BeautifulSoup(login_page_response.text, 'html.parser')
|
78 |
csrf_token = soup.find('input', {'name': '_xfToken'})['value']
|
79 |
|
|
|
85 |
'_xfRedirect': thread_url,
|
86 |
'_xfToken': csrf_token
|
87 |
}
|
88 |
+
response = session.post(config.login_url, data=login_data)
|
89 |
|
90 |
# Check if the login was successful
|
91 |
if 'Invalid login' in response.text:
|
92 |
print('Login failed!')
|
93 |
exit()
|
94 |
|
95 |
+
def post(message="", thread_url=config.thread_url, post_url=config.post_url, quoted_by="",quote_text="",quote_source=""):
|
96 |
#Post a message to the forum (with or without the quote
|
97 |
#quote_source is in format 'post-3920992'
|
98 |
quote_source = quote_source.split('-')[-1] # Take the numbers only
|
|
|
129 |
|
130 |
print('Post submitted successfully.')
|
131 |
|
132 |
+
def getMessages(thread_url=config.thread_url, quotedUser="", startingPage=1):
|
133 |
# Returns all the quotes for #username in the specific multi-page thread url
|
134 |
allquotes =[]
|
135 |
|
|
|
177 |
if matchID:
|
178 |
messageID = matchID.group(1)
|
179 |
|
180 |
+
# Match the QuotedName
|
181 |
matchQuotedName = quotedNamePattern.search(str(data))
|
182 |
if matchQuotedName:
|
183 |
quotedName = matchQuotedName.group(1)
|
|
|
212 |
def WarOnlineBot():
|
213 |
|
214 |
lookUpPages = 5 # How many pages back to look in the thread
|
215 |
+
startingPage = getLastPage(thread_url=config.thread_url) - lookUpPages
|
216 |
if startingPage < 1:
|
217 |
startingPage = 1 # Starting page cannot be less than 1
|
218 |
|
219 |
+
login(username=config.username, password=config.password, thread_url=config.thread_url)
|
220 |
#print("logged in")
|
221 |
|
222 |
# All messages (with quotes) by ALL users:
|
223 |
+
allMessages = getMessages(thread_url=config.thread_url, quotedUser='', startingPage=startingPage)
|
224 |
|
225 |
# IDs of the quoted messages, replied by the bot:
|
226 |
messages_by_bot_IDs = []
|
227 |
|
228 |
for msg in allMessages:
|
229 |
# Set a list of replied messages IDs
|
230 |
+
if msg['messengerName'] == config.username: #message posted by the WarBot
|
231 |
messages_by_bot_IDs.append(msg['quotedID'].split(': ')[-1])
|
232 |
# remove empty and repeated elements
|
233 |
messages_by_bot_IDs = list(set([elem for elem in messages_by_bot_IDs if elem]))
|
234 |
|
235 |
# All messages (with quotes) sent _FOR_ the Bot:
|
236 |
+
messagesForBot = getMessages(thread_url=config.thread_url, quotedUser=config.username, startingPage=startingPage)
|
237 |
|
238 |
# IDs of the messages, quoting the bot:
|
239 |
messages_for_bot_IDs = []
|
|
|
258 |
quote = remove_non_english_russian_chars(msg['reply'])
|
259 |
quote = remove_extra_spaces(quote)
|
260 |
|
261 |
+
message = "" #Initiating the reply message by Bot
|
262 |
+
previous_dialogue = "" #Initiating the previous dialogue
|
263 |
|
264 |
print('Quote: ', originalQuote)
|
265 |
|
266 |
+
# Init Connection
|
267 |
+
db = conversationDB.DataBase()
|
268 |
+
# Get the previous dialogue from the database
|
269 |
+
dbmessages = db.getmessages(msg['messengerName'])
|
270 |
+
for dbmessage in dbmessages:
|
271 |
+
previous_dialogue += dbmessage[0]+' '+dbmessage[1]+' '
|
272 |
+
# Update the string and preprocess it
|
273 |
+
quote = previous_dialogue + quote
|
274 |
+
quote = remove_non_english_russian_chars(quote)
|
275 |
+
quote = remove_extra_spaces(quote)
|
276 |
+
# Truncate the quote to return only the last MaxWords of words:
|
277 |
+
quote = " ".join(quote.split()[-config.MaxWords:])
|
278 |
+
|
279 |
+
# Fix the quote string, to eliminate errors:
|
280 |
+
quote = fixString(quote)
|
281 |
+
|
282 |
FailureCounter = 0 # In case there is a bug in the model
|
283 |
while (not message) and (FailureCounter<3):
|
284 |
message = WarClient.getReply(message=quote)
|
|
|
294 |
message = fixString(message)
|
295 |
print('Reply: ', message)
|
296 |
|
297 |
+
# Add the new conversation pair to the database
|
298 |
+
db.setmessages(username=msg['messengerName'], message_text=originalQuote, bot_reply=message)
|
299 |
+
# Clean up the excessive records, leaving only the remaining messages
|
300 |
+
db.cleanup(username=msg['messengerName'], remaining_messages=config.remaining_messages)
|
301 |
+
# Delete the duplicate records
|
302 |
+
db.deleteDuplicates()
|
303 |
+
|
304 |
+
login(username=config.username, password=config.password, thread_url=config.thread_url)
|
305 |
time.sleep(1)
|
306 |
+
post(message=message, thread_url=config.thread_url, post_url=config.post_url, quoted_by=msg['messengerName'], quote_text=originalQuote, quote_source=msg['messageID'])
|
307 |
|
308 |
time.sleep(10) # Standby time for server load release
|
309 |
|
310 |
|
311 |
if __name__ == '__main__':
|
|
|
312 |
|
313 |
# Start the scheduler
|
314 |
while True:
|
315 |
+
print('Starting Session')
|
316 |
WarOnlineBot()
|
317 |
|
318 |
+
timer = range(60 * config.timeout)
|
319 |
for t in timer:
|
320 |
time.sleep(1)
|
config.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Configuration File with stored constants
|
2 |
+
|
3 |
+
# Define the login URL and the thread URL:
|
4 |
+
login_url = 'https://waronline.org/fora/index.php?login/login'
|
5 |
+
thread_url = 'https://waronline.org/fora/index.php?threads/warbot-playground.17636/'
|
6 |
+
post_url = "https://waronline.org/fora/index.php?threads/warbot-playground.17636/add-reply"
|
7 |
+
|
8 |
+
# SSH settings
|
9 |
+
ssh_host = '129.159.146.88'
|
10 |
+
ssh_user = 'ubuntu'
|
11 |
+
ssh_key_path = 'C:/Users/kerts/OneDrive/Documents/Keys/Ubuntu_Oracle/ssh-key-2023-02-12.key'
|
12 |
+
#ssh_key_path = 'ssh-key-2023-02-12.key'
|
13 |
+
|
14 |
+
# MySQL settings:
|
15 |
+
mysql_host = 'localhost' # because we will connect through the SSH tunnel
|
16 |
+
mysql_port = 3306 # the default MySQL port
|
17 |
+
mysql_user = 'root'
|
18 |
+
mysql_password = 'naP2tion'
|
19 |
+
mysql_db = 'warbot'
|
20 |
+
ssh_port = 22
|
21 |
+
dbTableName = 'conversations' # messages data table
|
22 |
+
|
23 |
+
# WarBot Server Settings:
|
24 |
+
HOST = '129.159.146.88'
|
25 |
+
PORT = 5000
|
26 |
+
|
27 |
+
# Define the login credentials:
|
28 |
+
username = 'WarBot'
|
29 |
+
password = 'naP2tion'
|
30 |
+
|
31 |
+
# Maximum number of words in message:
|
32 |
+
MaxWords = 50 # The server is relatively weak to fast-process the long messages
|
33 |
+
|
34 |
+
# Maximum conversation pairs in the Database
|
35 |
+
remaining_messages = 2
|
36 |
+
|
37 |
+
# Time between the reply sessions:
|
38 |
+
timeout = 5 # min
|
conversationDB.py
CHANGED
@@ -1,20 +1,21 @@
|
|
1 |
import paramiko
|
2 |
import pymysql
|
3 |
from sshtunnel import SSHTunnelForwarder
|
|
|
4 |
|
5 |
# SSH settings
|
6 |
-
ssh_host =
|
7 |
-
ssh_user =
|
8 |
-
ssh_key_path =
|
9 |
|
10 |
# MySQL settings
|
11 |
-
mysql_host =
|
12 |
-
mysql_port =
|
13 |
-
mysql_user =
|
14 |
-
mysql_password =
|
15 |
-
mysql_db =
|
16 |
-
ssh_port =
|
17 |
-
dbTableName =
|
18 |
|
19 |
class DataBase():
|
20 |
# manages mySQL connection and send-receive
|
@@ -92,7 +93,7 @@ class DataBase():
|
|
92 |
|
93 |
# close the SSH client
|
94 |
db.ssh_client.close()
|
95 |
-
def cleanup(db, username = "", remaining_messages =
|
96 |
# Cleanup the records, except the last N rows
|
97 |
with SSHTunnelForwarder(
|
98 |
(db.ssh_host, db.ssh_port),
|
@@ -124,13 +125,47 @@ class DataBase():
|
|
124 |
# close the SSH client
|
125 |
db.ssh_client.close()
|
126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
127 |
|
128 |
-
|
129 |
-
|
130 |
-
username = 'user1'
|
131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
132 |
db = DataBase()
|
133 |
db.setmessages(username='user2',message_text='some message',bot_reply='some reply')
|
134 |
-
|
|
|
135 |
messages = db.getmessages(username)
|
136 |
-
|
|
|
|
|
|
1 |
import paramiko
|
2 |
import pymysql
|
3 |
from sshtunnel import SSHTunnelForwarder
|
4 |
+
import config # Here the constants are stored
|
5 |
|
6 |
# SSH settings
|
7 |
+
ssh_host = config.ssh_host
|
8 |
+
ssh_user = config.ssh_user
|
9 |
+
ssh_key_path = config.ssh_key_path
|
10 |
|
11 |
# MySQL settings
|
12 |
+
mysql_host = config.mysql_host
|
13 |
+
mysql_port = config.mysql_port
|
14 |
+
mysql_user = config.mysql_user
|
15 |
+
mysql_password = config.mysql_password
|
16 |
+
mysql_db = config.mysql_db
|
17 |
+
ssh_port = config.ssh_port
|
18 |
+
dbTableName = config.dbTableName
|
19 |
|
20 |
class DataBase():
|
21 |
# manages mySQL connection and send-receive
|
|
|
93 |
|
94 |
# close the SSH client
|
95 |
db.ssh_client.close()
|
96 |
+
def cleanup(db, username = "", remaining_messages = config.remaining_messages):
|
97 |
# Cleanup the records, except the last N rows
|
98 |
with SSHTunnelForwarder(
|
99 |
(db.ssh_host, db.ssh_port),
|
|
|
125 |
# close the SSH client
|
126 |
db.ssh_client.close()
|
127 |
|
128 |
+
def deleteDuplicates(db):
|
129 |
+
# Deletes all the duplicate records
|
130 |
+
with SSHTunnelForwarder(
|
131 |
+
(db.ssh_host, db.ssh_port),
|
132 |
+
ssh_username=db.ssh_user,
|
133 |
+
ssh_pkey=db.private_key,
|
134 |
+
remote_bind_address=(db.mysql_host, db.mysql_port)) as tunnel:
|
135 |
+
# connect to the MySQL server through the SSH tunnel
|
136 |
+
mysql_conn = pymysql.connect(
|
137 |
+
host='localhost', # will be due to the SSH tunneling issue
|
138 |
+
port=tunnel.local_bind_port,
|
139 |
+
user=db.mysql_user,
|
140 |
+
password=db.mysql_password,
|
141 |
+
db=db.mysql_db
|
142 |
+
)
|
143 |
|
144 |
+
# send a query to the MySQL database to delete the records except the last ones
|
145 |
+
with mysql_conn.cursor() as cursor:
|
|
|
146 |
|
147 |
+
query = f"DELETE c1 FROM {dbTableName} c1, {dbTableName} c2 WHERE " \
|
148 |
+
f"c1.id > c2.id AND c1.username = c2.username " \
|
149 |
+
f"AND c1.message_text = c2.message_text;"
|
150 |
+
cursor.execute(query)
|
151 |
+
mysql_conn.commit()
|
152 |
+
|
153 |
+
# close the MySQL connection
|
154 |
+
mysql_conn.close()
|
155 |
+
|
156 |
+
# close the SSH client
|
157 |
+
db.ssh_client.close()
|
158 |
+
|
159 |
+
if __name__ == '__main__':
|
160 |
+
pass
|
161 |
+
"""
|
162 |
+
#This is for testing purpose only:
|
163 |
+
username = 'u1'
|
164 |
db = DataBase()
|
165 |
db.setmessages(username='user2',message_text='some message',bot_reply='some reply')
|
166 |
+
db.cleanup(username='user2',remaining_messages=1)
|
167 |
+
db.deleteDuplicates()
|
168 |
messages = db.getmessages(username)
|
169 |
+
for message in messages:
|
170 |
+
print(message[0]+' '+message[0]+' ')
|
171 |
+
"""
|