-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
136 lines (105 loc) · 4.5 KB
/
main.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
import argparse
import asyncio
import importlib
import logging
import os
import pathlib
import random
import sys
import socketio
from aiohttp import web
#TODO: Better variable names
sio = socketio.AsyncServer() # sio - the SocketIO Server
app = web.Application()
sio.attach(app)
logger = logging.getLogger(__name__) # Log stuff using logging, rather than print()
@sio.event
async def connect(sid, environ):
logger.info(f'[CONN] client {sid} connected.')
# Ask for a nickname when connecting.
await sio.emit('nick-please', room=sid)
@sio.on('nick-please')
async def on_change_nick(sid, data):
logger.info(f'[NICK] client {sid} changing nick to {data}.')
async with sio.session(sid) as session:
#TODO: Validate the nickname before setting it.
session['nick'] = data
# Broadcast a new-nick message.
# '' is the old nickname, an empty string, which will produce a special
# join message.
await sio.emit('new-nick', ('', data))
return True # acknowledgement
# True if the nick is good.
# TODO: Should emit an error string if it isn't.
@sio.on('chat')
async def on_chat_message(sid, msg):
# Here, session is read-only and will not update if we change it.
session = await sio.get_session(sid)
# `session` may be undefined, use `.get()` instead of `session[]` to always have a valid value.
nick = session.get('nick', 'UnnickedUser')
logger.info(f'[CHAT] {nick}: {msg}')
await sio.emit('chat', (nick, str(msg)))
# We allow bots without an on_chat method, so we don't care
# if it's undefined or not since the Bot class implements
# a default.
async with sio.session(sid) as session:
await asyncio.gather(*[bot.on_chat_message(sid, msg, session) for bot in bots])
@sio.event
def disconnect(sid):
logger.info(f'[DCON] client {sid} disconnected.')
# Serve static files (stylesheets, JS, etc.)
app.router.add_static('/static/', 'static')
# Serve index.html without the filename in the URL
# Function receives 1 argument, but we don't need it, hence the underscore.
def index(_):
return web.FileResponse('./static/index.html')
app.router.add_get('/', index)
# Code in this “__main__ guard” will only run if this file is launched from the console,
# rather than imported as a library in another Python file.
if __name__ == '__main__':
# Get command-line arguments, so we can specify the port
# the HTTP server should use.
parser = argparse.ArgumentParser(description="An HTTP/WebSockets chat server!")
parser.add_argument("port", type=int, help="The port number that the server should use.")
# Log level command-line argument
levels = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
parser.add_argument('--log-level', default='INFO', choices=levels)
args = parser.parse_args()
# Settting up the loggers
# Use the command-line argument for log level.
logger.setLevel(args.log_level)
# Log everything, including debug events, to a file log.
# mode="w" overwrites the log between runs, instad of appending.
fh = logging.FileHandler('debug.log', mode="w")
fh.setLevel(logging.DEBUG) # Capture everything in the file log...
# But only INFO or higher-level messages in the console log.
stdout = logging.StreamHandler(sys.stdout)
stdout.setLevel(logging.INFO)
# Use the handlers we just set up:
logging.basicConfig(
format='%(levelname)s:%(name)s: %(message)s',
handlers=[
stdout,
fh
]
)
# Look through the `bots` directory and load every python module.
bots = []
for filename in os.listdir(pathlib.Path.cwd().joinpath("bots")):
# *Not at all* a reliable check for python files. Good enough, however.
if ".py" in filename and filename != 'bot.py':
bot_module = importlib.import_module(f"bots.{filename[:-3]}")
# If the bot has a logger associated with it, set the level to
# the main log level.
bot_logger = getattr(bot_module, 'logger', None)
if bot_logger:
bot_logger.setLevel(args.log_level)
# Each bot must have a `create()` function that accepts the SocketIO
# server instance as an argument.
bot_class = getattr(bot_module, 'bot')
if bot_class:
bot = bot_class(sio)
bots.append(bot)
else:
logger.error(f"Bot in {filename} has no `bot` variable!")
web.run_app(app, port=args.port)