I'm trying to make translate bot using twitchdev python code: https://github.com/twitchdev/chat-samples/blob/master/python/chatbot.py
I was able to get messages from channels and send translated text, but I cannot join multiple channels.
What I did below is making list of channels and call using for loop, but it only join to the last channel.
I tried to make another list of channels in def on_welcome(self, c, e) but it also works on the last channel (when I print self.channels in def on_welcome(self, c, e) it printed blank list, and when I print self.channel it only printed last channel)
Any suggestions are welcome.
import sys
import irc.bot
import requests
import config
class TwitchBot(irc.bot.SingleServerIRCBot):
def __init__(self, username, client_id, token, channels):
for channel in channels
self.client_id = client_id
self.token = token
self.channel = '#' + channel
# Get the channel id, we will need this for v5 API calls
url = 'https://api.twitch.tv/kraken/users?login=' + channel
headers = {'Client-ID': client_id, 'Accept': 'application/vnd.twitchtv.v5+json'}
r = requests.get(url, headers=headers).json()
self.channel_id = r['users'][0]['_id']
# Create IRC bot connection
server = 'irc.chat.twitch.tv'
port = 6667
print 'Connecting to ' + server + ' on port ' + str(port) + '...'
irc.bot.SingleServerIRCBot.__init__(self, [(server, port, 'oauth:'+token)], username, username)
def on_welcome(self, c, e):
print 'Joining ' + self.channel
# You must request specific capabilities before you can use them
c.cap('REQ', ':twitch.tv/membership')
c.cap('REQ', ':twitch.tv/tags')
c.cap('REQ', ':twitch.tv/commands')
c.join(self.channel)
def on_pubmsg(self, c, e):
# If a chat message starts with an exclamation point, try to run it as a command
if e.arguments[0][:1] == '!':
cmd = e.arguments[0].split(' ')[0][1:]
print 'Received command: ' + cmd
self.do_command(e, cmd)
return
def do_command(self, e, cmd):
c = self.connection
# Provide basic information to viewers for specific commands
elif cmd == "raffle":
message = "This is an example bot, replace this text with your raffle text."
c.privmsg(self.channel, message)
elif cmd == "schedule":
message = "This is an example bot, replace this text with your schedule text."
c.privmsg(self.channel, message)
def main():
if len(sys.argv) != 5:
print("Usage: twitchbot <username> <client id> <token> <channel>")
sys.exit(1)
username = config.twitch['botname']
client_id = config.twitch['cliendID']
token = config.twitch['oauth']
channels = ["channel1", "channel2"]
bot = TwitchBot(username, client_id, token, channels)
bot.start()
if __name__ == "__main__":
main()
In your main() function, you are establishing an object of the bot:
bot = TwitchBot(username, client_id, token, channels)
Instead of passing multiple channels to the same bot object, create multiple bot objects with single channels. I haven't tested this yet because Twitch updated their OAuth protocol and I haven't been through the docs.
You may need to thread them, and depending on what functions you implement it may not be thread-safe (just test before going into production). That'd probably look something like:
import threading
t1 = threading.Thread(target=TwitchBot, args=(username, client_id, token, channel1))
t2 = threading.Thread(target=TwitchBot, args=(username, client_id, token, channel2))
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
Once again, I haven't tested any of this but will likely have time in a few weeks to patch this in my own code after the holiday.
Related
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import base64
import time
# If modifying these scopes, delete the file token.pickle.
SCOPES = ['https://www.googleapis.com/auth/gmail.modify']
def main():
"""Shows basic usage of the Gmail API.
Lists the user's Gmail labels.
"""
creds = None
# The file token.pickle stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.pickle'):
with open('token.pickle', 'rb') as token:
creds = pickle.load(token)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.pickle', 'wb') as token:
pickle.dump(creds, token)
service = build('gmail', 'v1', credentials=creds)
# Call the Gmail API
repeat = 0
while repeat <= 10:
labelName = "READ-BY-SCRIPT"
LABEL_ID = 'Label_8507504117657095973'
results = service.users().messages().list(
userId='me', q="-label:"+labelName, maxResults=1).execute()
messages = results.get('messages', [])
body = []
if not messages:
repeat += 10
time.sleep(60)
else:
for message in messages:
msg = service.users().messages().get(
userId='me', id=message['id']).execute()
labels = msg['labelIds']
if "INBOX" in labels:
body.append(msg['payload']['parts'])
body = base64.urlsafe_b64decode(
body[0][0]['body']['data'])
body = str(body)
if 'b"\\r\\nHi MOHAMMAD,\\r\\n' or "b'\\r\\nHi MOHAMMAD,\\r\\n" in body:
if 'posted a new assignment in IX K \\r\\n<https://classroom.google.com/c/MTEyNDMxODgyMTE0>.' in body:
body = body.replace(
"\\r\\nIf you don\\'t want to receive emails from Classroom, you can unsubscribe \\r\\n<https://classroom.google.com/s>.\\r\\n\\r\\nGoogle LLC\\r\\n1600 Amphitheatre Pkwy\\r\\nMountain View, CA 94043 USA\\r\\n'", "")
body = body.replace("b\"", "").replace("b'", "").replace('"', '').replace(" ", ' ').replace(
" ", ' ').replace(" \\n<https://classroom.google.com/c/MTEyNDMxODgyMTE0>", "").replace("\\n", "\\n\\n")
body = body.replace("\\r\\n\\nHi MOHAMMAD,\\r\\n\\n", "").replace(" \\r\\n\\n<https://classroom.google.com/c/MTEyNDMxODgyMTE0>.\\r\\n", "").replace(
"\\r\\n\\nIf you don't want to receive emails from Classroom, you can unsubscribe \\r\\n\\n<https://classroom.google.com/s>.\\r\\n\\n\\r\\n\\nGoogle LLC\\r\\n\\n1600 Amphitheatre Pkwy\\r\\n\\nMountain View, CA 94043 USA\\r\\n\\n", "")
body = body.replace("\\r", "\r").replace(
"\\n", "\n").replace("\n\n", "\n").replace("\\\\", "\\")
body = body.replace("\\xe2", "").replace(
"\\x80", "").replace("\\x99", "").replace("\\x98", "")
body = body.replace(
"\\r\\nIf you don\\'t want to receive emails from Classroom, you can unsubscribe \\r\\n<https://classroom.google.com/s>.\\r\\n\\r\\nGoogle LLC\\r\\n1600 Amphitheatre Pkwy\\r\\nMountain View, CA 94043 USA\\r\\n'", "")
body = body.replace("\\'", "")
body = body.replace(
"\\r\\nIf you don\\'t want to receive emails from Classroom, you can unsubscribe", "")
body = body.replace(
"If you dont want to receive emails from Classroom, you can unsubscribe\n <https://classroom.google.com/s>.\nGoogle LLC\n1600 Amphitheatre Pkwy\nMountain View, CA 94043 USA\n", "")
TEACHER_NAME = body.split("posted", 1)[0]
body = body.replace(TEACHER_NAME, "")
LINK = str(body.split("\r\nOPEN \r\n<", 1)[1])
LINK = LINK[:-1]
body = body.replace(LINK, "").replace("<", "").replace(">", "").replace(
'posted a new assignment in IX K\n\r\n', "").replace("\r\nOPEN \r\n", "")
if 'Due: ' in body:
body = body.replace("\n", " ", 1)
DATE = body.split(' ')[0]
body = body.split(' ')[1]
else:
body = body
DATE = 'No Due Date Provided'
service.users().messages().modify(userId='me', id=message['id'], body={
'addLabelIds': ['Label_8507504117657095973']}).execute()
repeat += 1
else:
service.users().messages().modify(userId='me', id=message['id'], body={
'addLabelIds': [LABEL_ID]}).execute()
pass
else:
service.users().messages().modify(userId='me', id=message['id'], body={
'addLabelIds': [LABEL_ID]}).execute()
pass
else:
service.users().messages().modify(userId='me', id=message['id'], body={
'addLabelIds': ['Label_8507504117657095973']}).execute()
pass
if __name__ == '__main__':
main()
This script gets the most recent unread email from gmail. If there are no new emails then it instantly ends the script. If there is an email that isn’t from google classroom it marks that email as read and then repeats the process again until a there are either no new unread emails or if an email from google classroom is found. If there is a new email from google classroom it gets the teacher’s name [TEACHER_NAME], link to the assignment [LINK], due date [DATE], and details of the assignment [body] and then ends the script
I want to make a different python file (bot.py) that runs another python file (gmail.py) containing the above script, 10 times. Each time gmail.py is run, bot.py via discord.py sends each of these variables in different messages if they are defined by gmail.py, and to do nothing if they are not defined by gmail.py. bot.py then waits 1 minute before repeating the entire process over again. How can this be done? Where should I start?
You don't really 'run' the python file, you should rather import the file (or the function from the file) and run the function however many times fit using a for loop.
With the objective of having an application that runs in python 3 and reads incoming emails on an specific gmail account, how would one listen for the reception of this emails?
What it should do is wait until a new mail is received on the inbox, read the subject and body from the email and get the text from the body (without format).
This is what I got so far:
import imaplib
import email
import datetime
import time
mail = imaplib.IMAP4_SSL('imap.gmail.com', 993)
mail.login(user, password)
mail.list()
mail.select('inbox')
status, data = mail.search(None, 'ALL')
for num in data[0].split():
status, data = mail.fetch(num, '(RFC822)')
email_msg = data[0][1]
email_msg = email.message_from_bytes(email_msg)
maintype = email_msg.get_content_maintype()
if maintype == 'multipart':
for part in email_msg.get_payload():
if part.get_content_maintype() == 'text':
print(part.get_payload())
elif maintype == 'text':
print(email_msg.get_payload())
But this has a couple of problems: When the message is multipart each part is printed and sometimes after that the last part is basically the whole message but in html format.
Also, this prints all the messages from the inbox, how would one listen for new emails with imaplib? or with other library.
I'm not sure about the synchronous way of doing that, but if you don't mind having an async loop and defining unread emails as your target then it could work.
(I didn't implement the IMAP polling loop, only the email fetching loop)
My changes
Replace the IMAP search filter from 'ALL' to '(UNSEEN)' to fetch unread emails.
Change the serializing policy to policy.SMTP from the default policy.Compat32.
Use the email.message.walk() method (new API) to run & filter message parts.
Replace the legacy email API calls with the new ones as described in the docs, and demonstrated in these examples.
The result code
import imaplib, email, getpass
from email import policy
imap_host = 'imap.gmail.com'
imap_user = 'example#gmail.com'
# init imap connection
mail = imaplib.IMAP4_SSL(imap_host, 993)
rc, resp = mail.login(imap_user, getpass.getpass())
# select only unread messages from inbox
mail.select('Inbox')
status, data = mail.search(None, '(UNSEEN)')
# for each e-mail messages, print text content
for num in data[0].split():
# get a single message and parse it by policy.SMTP (RFC compliant)
status, data = mail.fetch(num, '(RFC822)')
email_msg = data[0][1]
email_msg = email.message_from_bytes(email_msg, policy=policy.SMTP)
print("\n----- MESSAGE START -----\n")
print("From: %s\nTo: %s\nDate: %s\nSubject: %s\n\n" % ( \
str(email_msg['From']), \
str(email_msg['To']), \
str(email_msg['Date']), \
str(email_msg['Subject'] )))
# print only message parts that contain text data
for part in email_msg.walk():
if part.get_content_type() == "text/plain":
for line in part.get_content().splitlines():
print(line)
print("\n----- MESSAGE END -----\n")
Have you check below script (3_emailcheck.py) from here posted by git user nickoala? Its a python 2 script and in Python3 you need to decode the bytes with the email content first.
import time
from itertools import chain
import email
import imaplib
imap_ssl_host = 'imap.gmail.com' # imap.mail.yahoo.com
imap_ssl_port = 993
username = 'USERNAME or EMAIL ADDRESS'
password = 'PASSWORD'
# Restrict mail search. Be very specific.
# Machine should be very selective to receive messages.
criteria = {
'FROM': 'PRIVILEGED EMAIL ADDRESS',
'SUBJECT': 'SPECIAL SUBJECT LINE',
'BODY': 'SECRET SIGNATURE',
}
uid_max = 0
def search_string(uid_max, criteria):
c = list(map(lambda t: (t[0], '"'+str(t[1])+'"'), criteria.items())) + [('UID', '%d:*' % (uid_max+1))]
return '(%s)' % ' '.join(chain(*c))
# Produce search string in IMAP format:
# e.g. (FROM "me#gmail.com" SUBJECT "abcde" BODY "123456789" UID 9999:*)
def get_first_text_block(msg):
type = msg.get_content_maintype()
if type == 'multipart':
for part in msg.get_payload():
if part.get_content_maintype() == 'text':
return part.get_payload()
elif type == 'text':
return msg.get_payload()
server = imaplib.IMAP4_SSL(imap_ssl_host, imap_ssl_port)
server.login(username, password)
server.select('INBOX')
result, data = server.uid('search', None, search_string(uid_max, criteria))
uids = [int(s) for s in data[0].split()]
if uids:
uid_max = max(uids)
# Initialize `uid_max`. Any UID less than or equal to `uid_max` will be ignored subsequently.
server.logout()
# Keep checking messages ...
# I don't like using IDLE because Yahoo does not support it.
while 1:
# Have to login/logout each time because that's the only way to get fresh results.
server = imaplib.IMAP4_SSL(imap_ssl_host, imap_ssl_port)
server.login(username, password)
server.select('INBOX')
result, data = server.uid('search', None, search_string(uid_max, criteria))
uids = [int(s) for s in data[0].split()]
for uid in uids:
# Have to check again because Gmail sometimes does not obey UID criterion.
if uid > uid_max:
result, data = server.uid('fetch', uid, '(RFC822)') # fetch entire message
msg = email.message_from_string(data[0][1])
uid_max = uid
text = get_first_text_block(msg)
print 'New message :::::::::::::::::::::'
print text
server.logout()
time.sleep(1)
im trying to deal with both InlineKeyboardButton callback_data, and free text data..here's my scenario:
prompting an InlineKeyboard with several buttons, the user clicks one button and then asked to input some free text for the BE to be used.
I have tried to use CallbackQueryHandler(several InlineKeyboardMarkup in the callback function) as an entry point for ConversationHandler which then trigger MessageHandler, with no much success..
I need to catch the free text update (basically wait for user input).
def start(update, context):
keyboard = [[InlineKeyboardButton("bal bla", callback_data='1'),
InlineKeyboardButton("bla bla", callback_data='2')],
[InlineKeyboardButton("bla bla)", callback_data='3'),
InlineKeyboardButton("bla bla", callback_data= '4')],
[InlineKeyboardButton("bla bla", callback_data='5')]]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text('Please choose:', reply_markup=reply_markup)
reply_text = {'text': ''}
def reply_message(update, context):
message = update.message.text
reply_text['text'] = message
return reply_text['text']
def button(update, context, user_data):
query = update.callback_query
query.edit_message_text(text="Loading....\n \r")
if query.data == '1':
pass
elif query.data == '2':
pass
elif query.data == '3':
keyboard = [[InlineKeyboardButton('BBB', callback_data='21'),
InlineKeyboardButton('GGG', callback_data='22')],
[InlineKeyboardButton('PPP', callback_data='23')]]
reply_markup1 = InlineKeyboardMarkup(keyboard)
query.edit_message_text('Please select:', reply_markup=reply_markup1)
elif query.data == '21':
query.edit_message_text('input customer name ')
return 1
if : #no idea which condition to give here
print(reply_text['text'], '\n ^ new free text message here ^')
def main():
conv_handler= ConversationHandler(
entry_points=[
CallbackQueryHandler(button)
],
states={
1 : [MessageHandler(Filters.text, reply_message)],
},
fallbacks= []
)
try:
updater = Updater(bot_token, use_context=True)
updater.dispatcher.add_handler(CommandHandler('start', start))
updater.dispatcher.add_handler(CallbackQueryHandler(button))
updater.dispatcher.add_handler(conv_handler)
updater.start_polling()
updater.idle()
except Exception as e:
print(e)
Hey i solved this with making a function to parse the user input and remember the input with the user_data method of context.
def check_user_input(update, context):
# get last user text
user_input = update.message.text
# get user_data context
user_data = context.user_data
# set new context user_input
user_data['user_input'] = user_input
context.user_data['user_input'] = user_data['user_input']
if "Text" in user_input:
# do something... i.e reply w/o keyboard
update.message.reply_text(
("{}?!, sounds interesting, tell me more".format(
user_input)))
return GUEST_CHOICE
else:
# ask again
reply_keyboard = [['Text'],['Done']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
update.message.reply_text(
("{}?!, i dont know anything...".format(
user_input),reply_markup=markup))
return CHECK_CHOICE
This seems to be the favorized method to handle and remember user inputs.
This function can now be called within the ConversationHandler/MessageHandlers as you need. You only have to return to a specified ConversationHandler state. So i will make two states, a GUEST_CHOICE and a CHECK_CHOICE state, to handle a single user input validation as example.
conv_handler = ConversationHandler(
entry_points=[CommandHandler('start', start)],
states={
# Ask Guest
GUEST_CHOICE: [MessageHandler(Filters.regex('^(Text)$'),
guest_choice),
],
# Verify input
CHECK_CHOICE: [MessageHandler(Filters.text,
check_user_input),
],
},
fallbacks=[MessageHandler(Filters.regex('^Done$'), done, CommandHandler('start', start))],
allow_reentry = False,
per_user=True,
conversation_timeout=3600,
name="test"
)
As defined, /start is used as user to start the conversationhandler. This calls a start() method i.e.:
def start(update, context):
reply_keyboard = [['Text'],['Done']]
markup = ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True)
# Setup keyboard and reply
update.message.reply_text(
("Hey {}, how can i help you?".format(
update.message.chat['first_name'])),
reply_markup=markup)
return CHECK_CHOICE
start() will setup the markup keyboard and any input after will be handled with CHECK_CHOICE state, which will call check_user_input here (see above). This will return GUEST_CHOICE and so it will call guest_choice() i.e.:
def guest_choice(update, context):
user_data = context.user_data
# Check authentication request
if "bob" in user_data['user_input']:
update.message.reply_text("Hey {}".format(user_data['user_input']))
else:
update.message.reply_text("Where is bob?!")
return CHECK_INPUT
The Done method will clean up the conversation
def done(update, context):
markup = get_markup(context)
update.message.reply_text("Bye")
return ConversationHandler.END
Ok that escalated quickly... I rewrote that code from my own working version of a conversation handler, please check for bugs :) Maybe the user_data has to be cleared in the Done method.
I have a Python 3.6 code that connects to MQTT and subscribes to a topic. Every time that the callback function "on_message" gets triggered, it instantiates a class that has a single method that does the following: Opens the db file, save the received data, closes the db file.
The Python script described above works almost fine. It receives about 7 MQTT messages per second, so for each message it needs to [Open_DB - Save_Data - Close_DB]. There are some messages getting PUBACK but not saved, perhaps due to some many unnecesary operations, so I want to improve:
I spent a lot of time (not an expert) trying to create a class that would open the db once, write many thousands of times to the db, and only when done, close the db file. to create a class that would have three methods:
1. MyDbClass.open_db_file()
2. MyDbClass.save_data()
3. MyDbClass.close_db_file()
The problem as you may guess is that it is not possible to call MyDbClass.save_data() from within the "on_message" callback, even when the object has been placed on a global variable. Here is the non-working code with the proposed idea, that I cleaned up for easier reading:
# -----------------------------
This code has been cleaned-up for faster reading
import paho.mqtt.client as mqtt
import time
import json
import sqlite3
Global Variables
db_object = ""
class MyDbClass():
def __init__(self):
pass
def open_db_file(self, dbfile):
self.db_conn = sqlite3.connect(db_file)
return self.db_conn
def save_data(self, json_data):
self.time_stamp = time.strftime('%Y%m%d%H%M%S')
self.data = json.loads(json_data)
self.sql = '''INSERT INTO trans_reqs (received, field_a, field_b, field_c) \
VALUES (?, ?, ?, ?)'''
self.fields_values = ( self.time_stamp, self.data['one'], self.data['two'], self.data['three']] )
self.cur = self.db_conn.cursor()
self.cur.execute(self.sql, self.fields_values)
self.db_conn.commit()
def close_db_file(self):
self.cur.close()
self.db_conn.close()
def on_mqtt_message(client, userdata, msg):
global db_object
m_decode = msg.payload.decode("utf-8","ignore")
db_object.save_data(m_decode)
def main():
global db_object
Database to use - Trying to create an object to manage DB tasks (from MyDbClass)
db_file = "my_filename.sqlite"
db_object = MyDbClass.open_db_file(db_file)
# MQTT -- Set varibles
broker_address= "..."
port = 1883
client_id = "..."
sub_topic = "..."
sub_qos = 1
# MQTT -- Instanciate the MQTT Client class and set callbacks
client = mqtt.Client(client_id)
client.on_connect = on_mqtt_connect
client.on_disconnect = on_mqtt_disconnect
client.on_message = on_mqtt_message
client.on_log = on_mqtt_log
client.clean_session = True
#client.username_pw_set(usr, password=pwd) #set username and password
print('Will connect to broker ', broker_address)
client.connect(broker_address, port=port, keepalive=45 )
client.loop_start()
client.subscribe(sub_topic, sub_qos)
try:
while True:
time.sleep(.1)
except KeyboardInterrupt:
# Disconnects MQTT
client.disconnect()
client.loop_stop()
print("....................................")
print("........ User Interrupted ..........")
print("....................................")
db_object.close_db_file()
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
main()
Any help on how to do this will be greatly appreciated!
In python, I am creating a message system where a client and server can send messages back and forth simeltaneously. Here is my code for the client:
import threading
import socket
# Global variables
host = input("Server: ")
port = 9000
buff = 1024
# Create socket instance
s = socket.socket()
# Connect to server
s.connect( (host, port) )
print("Connected to server\n")
class Recieve(threading.Thread):
def run(self):
while True: # Recieve loop
r_msg = s.recv(buff).decode()
print("\nServer: " + r_msg)
recieve_thread = Recieve()
recieve_thread.start()
while True: # Send loop
s_msg = input("Send message: ")
if s_msg.lower() == 'q': # Quit option
break
s.send( s_msg.encode() )
s.close()
I have a thread in the background to check for server messages and a looping input to send messages to the server. The problem arises when the server sends a message and the user input is immediately bounced up to make room for the servers message. I want it so that the input stays pinned to the bottom of the shell window, while the output is printed from the 2nd line up, leaving the first line alone. I have been told that you can use curses or Queues to do this, but I am not sure which one would be best in my situation nor how to implement these modules into my project.
Any help would be appreciated. Thank you.
I want it so that the input stays pinned to the bottom of the shell
window, while the output is printed from the 2nd line up, leaving the
first line alone. I have been told that you can use curses
Here's a supplemented version of your client code using curses.
import threading
import socket
# Global variables
host = input("Server: ")
port = 9000
buff = 1024
# Create socket instance
s = socket.socket()
# Connect to server
s.connect( (host, port) )
print("Connected to server\n")
import sys
write = sys.stdout.buffer.raw.write
from curses import *
setupterm()
lines = tigetnum('lines')
change_scroll_region = tigetstr('csr')
cursor_up = tigetstr('cuu1')
restore_cursor = tigetstr('rc')
save_cursor = tigetstr('sc')
def pin(input_lines): # protect input_lines at the bottom from scrolling
write(save_cursor + \
tparm(change_scroll_region, 0, lines-1-input_lines) + \
restore_cursor)
pin(1)
class Recieve(threading.Thread):
def run(self):
while True: # Recieve loop
r_msg = s.recv(buff).decode()
write(save_cursor+cursor_up)
print("\nServer: " + r_msg)
write(restore_cursor)
recieve_thread = Recieve()
recieve_thread.daemon = True
recieve_thread.start()
while True: # Send loop
s_msg = input("Send message: ")
if s_msg.lower() == 'q': # Quit option
break
s.send( s_msg.encode() )
pin(0)
s.close()
It changes the scrolling region to leave out the screen's bottom line, enters the scrolling region temporarily to output the server messages, and changes it back at the end.