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
|
1# PBLive
2# Copyright © 2017 RunasSudo (Yingtong Li)
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU Affero General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU Affero General Public License for more details.
13#
14# You should have received a copy of the GNU Affero General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import eventlet
18import flask
19import flask_socketio
20
21# idk why it doesn't work...
22try:
23 from . import data
24except ImportError:
25 import data
26
27import os, os.path
28import random
29import socket
30import sys
31import yaml
32
33eventlet.monkey_patch()
34
35app = flask.Flask('pblive')
36app.jinja_env.globals['data'] = data
37
38socketio = flask_socketio.SocketIO(app)
39
40# Get server IP address
41tmp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
42tmp_socket.connect(('118.138.0.0', 0)) # Connecting to a UDP socket sends no packets
43data.server_ip = tmp_socket.getsockname()[0]
44tmp_socket.close()
45
46# Load session data
47for f in os.listdir('data'):
48 if f.endswith('.yaml') and not f.startswith('.'):
49 session_name = f[:-5]
50 with open(os.path.join('data', f)) as fh:
51 data.sessions[session_name] = data.Session.from_dict(yaml.load(fh), session_name)
52
53@app.route('/')
54def index():
55 return flask.render_template('index.html')
56
57@app.route('/admin')
58def admin():
59 return flask.render_template('admin.html')
60
61@app.route('/session/<session_name>')
62def session(session_name):
63 return flask.render_template('session.html', session=data.sessions[session_name])
64
65@app.route('/image/<location>')
66def image(location):
67 # TODO: Relative path
68 return flask.send_from_directory(os.path.join(os.getcwd(), 'data/img'), location)
69
70@app.route('/admin/session/<session_name>')
71def admin_session(session_name):
72 return flask.render_template('admin_session.html', session=data.sessions[session_name])
73
74@app.route('/debug')
75def debug():
76 assert app.debug == False
77
78@socketio.on('join')
79def socket_join(session_name):
80 app.logger.debug('New client {} connected'.format(flask.request.sid))
81
82 session = data.sessions[session_name]
83 user = data.User(sid=flask.request.sid, session=session)
84 data.users[flask.request.sid] = user
85
86 # Send initial colour picker
87 flask_socketio.emit('update', flask.render_template('colour_picker.html', session=session), room=flask.request.sid)
88 flask_socketio.emit('update_left', render_sidebar(user, session), room=flask.request.sid)
89
90def render_question(user, session, question_num):
91 return flask.render_template(session.questions[question_num].template, session=session, user=user, question_num=session.question_num)
92
93def render_question_admin(session, question_num):
94 return flask.render_template(session.questions[question_num].template_admin, session=session, question_num=session.question_num)
95
96def render_sidebar(user, session):
97 return flask.render_template('users.html', session=session, user=user)
98
99@socketio.on('join_admin')
100def socket_join(session_name):
101 app.logger.debug('New admin {} connected'.format(flask.request.sid))
102
103 session = data.sessions[session_name]
104 user = data.Admin(sid=flask.request.sid, session=session)
105 data.admins_lock.acquire()
106 data.admins[flask.request.sid] = user
107 data.admins_lock.release()
108
109 # Send initial screen
110 flask_socketio.emit('update', render_question_admin(session, session.question_num), room=flask.request.sid)
111 flask_socketio.emit('update_left', render_sidebar(user, session), room=flask.request.sid)
112
113@socketio.on('disconnect')
114def socket_disconnect():
115 app.logger.debug('Client {} disconnected'.format(flask.request.sid))
116
117 if flask.request.sid in data.users:
118 user = data.users[flask.request.sid]
119
120 data.users_lock.acquire()
121 del data.users[flask.request.sid]
122 data.users_lock.release()
123
124 # Release the colour if it's being held
125 if user.colour:
126 user.session.colours.append(user.colour)
127
128 # Relay change
129 for _, other_user in data.iterate_users():
130 if other_user != user and other_user.session == user.session:
131 flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
132 if not other_user.colour:
133 flask_socketio.emit('update', flask.render_template('colour_picker.html', session=user.session), room=other_user.sid)
134 for _, admin in data.iterate_admins():
135 if admin.session == user.session:
136 flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
137
138@socketio.on('register')
139def socket_register(colour_id, colour_name):
140 user = data.users[flask.request.sid]
141
142 if not user.colour and (colour_id, colour_name) in user.session.colours:
143 user.colour = (colour_id, colour_name)
144 user.session.colours.remove(user.colour)
145
146 # Relay change
147 for _, other_user in data.iterate_users():
148 if other_user != user and other_user.session == user.session:
149 flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
150 if not other_user.colour:
151 flask_socketio.emit('update', flask.render_template('colour_picker.html', session=user.session), room=other_user.sid)
152 for _, admin in data.iterate_admins():
153 if admin.session == user.session:
154 flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
155
156 flask_socketio.emit('update', render_question(user, user.session, user.session.question_num), room=user.sid)
157 flask_socketio.emit('update_left', render_sidebar(user, user.session), room=user.sid)
158
159@socketio.on('answer')
160def socket_answer(question_num, answer):
161 user = data.users[flask.request.sid]
162
163 if question_num == user.session.question_num:
164 if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
165 if question_num in user.answers:
166 # Only one shot!
167 return
168
169 user.answers[question_num] = answer
170
171 if isinstance(user.session.questions[user.session.question_num], data.MCQQuestion):
172 flask_socketio.emit('update', render_question(user, user.session, user.session.question_num), room=user.sid)
173
174 # Hurry!
175 if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
176 if user.session.questions[user.session.question_num].timer_thread is None:
177 user.session.questions[user.session.question_num].timer_thread = data.SpeedQuestionTimerThread(flask.copy_current_request_context(do_goto_question), user.session, user.session.question_num + 1)
178 user.session.questions[user.session.question_num].timer_thread.start()
179
180 # Relay change
181 for _, other_user in data.iterate_users():
182 if other_user.session == user.session:
183 flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
184 if isinstance(user.session.questions[user.session.question_num], data.SpeedQuestion):
185 flask_socketio.emit('update', render_question(other_user, user.session, user.session.question_num), room=other_user.sid)
186 for _, admin in data.iterate_admins():
187 if admin.session == user.session:
188 flask_socketio.emit('update', render_question_admin(user.session, user.session.question_num), room=admin.sid)
189 flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
190
191@socketio.on('reveal_answers')
192def socket_reveal_answers(question_num):
193 user = data.admins[flask.request.sid]
194
195 user.session.questions[question_num].revealed = True
196
197 flask_socketio.emit('update', render_question_admin(user.session, user.session.question_num), room=flask.request.sid)
198
199def do_goto_question(session, question_num):
200 # Cleanup old question
201 if isinstance(session.questions[session.question_num], data.SpeedQuestion):
202 if session.questions[session.question_num].timer_thread is not None:
203 session.questions[session.question_num].timer_thread.stop()
204
205 session.question_num = question_num
206
207 # Do work for some questions
208 if isinstance(session.questions[question_num], data.RandomQuestion):
209 session.questions[question_num].answerer = random.choice([other_user for _, other_user in data.users.items() if other_user.session == session and other_user.colour])
210
211 # Relay change
212 for _, other_user in data.iterate_users():
213 if other_user.session == session and other_user.colour:
214 flask_socketio.emit('update', render_question(other_user, session, session.question_num), room=other_user.sid)
215 flask_socketio.emit('update_left', render_sidebar(other_user, session), room=other_user.sid)
216 for _, admin in data.iterate_admins():
217 if admin.session == session:
218 flask_socketio.emit('update', render_question_admin(session, session.question_num), room=admin.sid)
219 flask_socketio.emit('update_left', render_sidebar(admin, session), room=admin.sid)
220
221@socketio.on('goto_question')
222def socket_goto_question(question_num):
223 user = data.admins[flask.request.sid]
224
225 do_goto_question(user.session, question_num)
226
227@socketio.on('pass_question')
228def socket_pass_question():
229 user = data.admins[flask.request.sid] if flask.request.sid in data.admins else data.users[flask.request.sid]
230
231 if isinstance(user.session.questions[user.session.question_num], data.RandomQuestion):
232 # Re-randomise answerer
233 user.session.questions[user.session.question_num].answerer = random.choice([other_user for _, other_user in data.users.items() if other_user.session == user.session and other_user.colour])
234
235 # Relay change
236 for _, other_user in data.iterate_users():
237 if other_user.session == user.session and other_user.colour:
238 flask_socketio.emit('update', render_question(other_user, other_user.session, other_user.session.question_num), room=other_user.sid)
239 flask_socketio.emit('update_left', render_sidebar(other_user, user.session), room=other_user.sid)
240 for _, admin in data.iterate_admins():
241 if admin.session == user.session:
242 flask_socketio.emit('update', render_question_admin(admin.session, admin.session.question_num), room=admin.sid)
243 flask_socketio.emit('update_left', render_sidebar(admin, user.session), room=admin.sid)
244
245# Start server
246if __name__ == '__main__':
247 socketio.run(app, host='0.0.0.0')
|