-
Notifications
You must be signed in to change notification settings - Fork 2
/
functions.py
436 lines (366 loc) · 15.4 KB
/
functions.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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
import datetime
import random
from re import match
import utils
from flask import g, current_app
from exception import MyException
from database import Course, Reservation, Room, Registration, User, Message, Logs, db
def authenticated(func) :
def newFunc(*args, **kwargs) :
user = User.query.filter_by(openId=g.openId).first()
if user is None :
raise MyException('抱歉,您还没有登记。请发送 我是xxx')
g.user = user
return func(*args, **kwargs)
return newFunc
def isAdmin(func) :
def newFunc(*args, **kwargs) :
user = User.query.filter(db.and_(User.openId==g.openId, User.administrator==1)).first()
if user is None :
raise MyException('只有管理员可以使用此命令')
g.user = user
return func(*args, **kwargs)
return newFunc
def getRoom(roomName):
if roomName is None :
return None
room = Room.query.filter_by(name=roomName).first()
if room is None :
raise MyException('没有找到 {} 琴房'.format(roomName))
return room
def overlayedReservation(start, end, room=None) :
query = Reservation.query if room is None else room.reservations
if end is not None :
return query.filter(db.or_(
db.and_(Reservation.start<start, Reservation.end>start), # ( [ )
db.and_(Reservation.start>=start, Reservation.start<end)) # [ ( ]
)
else :
return query.filter(
db.and_(Reservation.start<=start, Reservation.end>=start)
)
def overlayedCourse(start, end, room=None):
query = Course.query if room is None else room.courses
if end is not None :
end -= datetime.timedelta(microseconds=1)
assert start.date() == end.date()
return (query.filter_by(weekday=start.weekday())
.filter(db.and_(Course.startDate<=start.date(),
Course.endDate>=start.date()))
.filter(db.or_(
db.and_(Course.startTime<start.time(), Course.endTime>start.time()),
db.and_(Course.startTime>=start.time(), Course.startTime<=end.time()))))
else :
return (query.filter_by(weekday=start.weekday())
.filter(db.and_(Course.startDate<=start.date(),
Course.endDate>=start.date(), Course.startTime<=start.time(), Course.endTime>=start.time())))
def queryOccupations(start, end, room):
reservations = [dict(
room = x.room.id,
start = x.start.time(),
end = x.end.time(),
repr = '{:02}:{:02}~{:02}:{:02} {}'.format(x.start.hour, x.start.minute,
x.end.hour, x.end.minute, x.user.name),
) for x in overlayedReservation(start, end, room)]
courses = [dict(
room = x.room.id,
start = x.startTime,
end = x.endTime,
repr = '{:02}:{:02}~{:02}:{:02} {} (*)'.format(x.startTime.hour, x.startTime.minute,
x.endTime.hour, x.endTime.minute, x.teacher.name)
) for x in overlayedCourse(start, end, room)]
return (reservations, courses)
def formatOccupation(date, reservations, courses):
dateRepr = utils.formatDate(date)
resultList = reservations + courses
resultList.sort(key=lambda x:(x['room'], x['start'], x['end']))
resultRepr = '{}:'.format(dateRepr)
for i,x in enumerate(resultList):
if i==0 or x['room']!=resultList[i-1]['room']:
resultRepr += '\n'*(i>0) + '\n[{}]'.format(Room.query.get(x['room']).name)
resultRepr += '\n{}'.format(x['repr'])
return resultRepr
# 返回一个随机的表情
def randomEmoji() :
available = [(0x1f31a,0x1f31e), (0x1f646,0x1f64f)]
pos = random.randrange(sum(x[1]-x[0]+1 for x in available))
for x in available:
if pos < x[1]-x[0]+1:
return chr(x[0]+pos)
pos -= x[1]-x[0]+1
def vagueRequest() :
return '嘤嘤嘤,你说的话有点模糊呢,小AI不太理解。输入“帮助”学习如何跟小AI交流'
def processIam(name) :
if len(name) > 10 :
return '“{}”这个名字太长啦'.format(name)
if len(name) < 1 :
return '“{}”这个名字太短啦'.format(name)
user = User.query.filter_by(openId=g.openId).first()
if user is not None:
if user.name == name:
return '您已设置姓名为 {}'.format(name)
elif name == '谁' :
return '您的姓名为 {}'.format(user.name)
registration = Registration.query.filter_by(openId=g.openId).first()
if registration is None:
db.session.add(Registration(openId=g.openId, name=name))
db.session.commit()
if user is not None:
return '您已设置姓名为 {}。再输入一次“我是{}”将更改用户名'.format(user.name, name)
else :
return '您即将设置姓名为 {}。请再输入一次“我是{}”将确认该身份。'.format(name, name)
elif registration.name != name:
db.session.delete(registration)
db.session.commit()
return '两次输入不一致,请重新输入'
else:
cur_user = User.query.filter_by(openId=g.openId).all()
users_by_name = User.query.filter_by(name=name).all()
if len(cur_user) >= 1 : # 改名
if len(cur_user) != 1 :
# ??? 出故障了,出现重复的openId
raise MyException('出现重复openId, 请联系技术部负责人')
if len(users_by_name) != 0 :
# 重名了
db.session.delete(registration)
db.session.commit()
return '该用户名已经存在啦'
cur_user[0].name = name
db.session.delete(registration)
db.session.commit()
return '您已设置姓名为 {}'.format(name)
else : # 新增用户
#如果当前仅有一个重名用户且此人没有openId,则将其视为预先录入的老师,二者为同一人
if len(users_by_name)==1 and users_by_name[0].openId is None:
users_by_name[0].openId = g.openId
db.session.add(users_by_name[0])
elif len(users_by_name) == 0 :
db.session.add(User(openId=g.openId, name=name))
else:
db.session.delete(registration)
db.session.commit()
return '该用户名已经存在啦'
db.session.delete(registration)
db.session.commit()
return '您已设置姓名为 {}'.format(name)
@authenticated
def processReservation(start, end, roomName):
curUser = User.query.filter_by(openId=g.openId).all()
utils.writeLog('Reservation', str(start)+', '+str(end)+', '+str(roomName)+', '+str(curUser), '1;32;40')
if len(curUser) == 0 or curUser[0].authorized != 1 :
return '抱歉,只有经过认证的演奏部成员可以预约'
if len(curUser) > 1 :
return '发现重名openID,请联系技术部负责人!'
result = ''
if end is None :
result += '由于您没有给定结束时间,默认您的预约时常为1小时\n'
end = start + datetime.timedelta(hours=1)
# ???
# After 2020, rooms can be reserved in this day
# if (start.month,start.day)==(6,4):
# return randomEmoji()
# 活跃预约数不超过2
nActiveReservations = (Reservation.query.filter_by(user=g.user)
.filter(Reservation.start>datetime.datetime.now()).count())
if nActiveReservations >= 2:
return '抱歉,每人最多持有 2 个预约。如需添加新的预约,请取消至少一个预约。'
#时长不超过2小时
if (end-start).seconds > 2*3600:
return '抱歉,单次预约时长不能超过 2 个小时。'
room = None if roomName is None else getRoom(roomName)
classRoom = Room.query.filter_by(name='B250').first()
practiceRooms = [Room.query.filter_by(name=x).first() for x in ['B252', 'B253']]
for x in [0]:
roomFound = False
for practiceRoom in practiceRooms:
if room is None or room==practiceRoom:
isIdle = utils.isEmpty(overlayedReservation(start, end, practiceRoom))
if isIdle:
reservation = Reservation(user=g.user, room=practiceRoom, start=start, end=end)
db.session.add(reservation)
db.session.commit()
roomFound = True
break
if roomFound: break
if room is None or room==classRoom:
#在本学期有课还没上完的时候,只有老师可以预约两天之后的classRoom
if not ((not utils.isEmpty(g.user.courses) #是老师
or (start.date()-datetime.datetime.now().date()).days <= 2)):
return '抱歉,只有教课的老师可以预约超过 2 天之后的 {}'.format(classRoom.name)
#没有课
isIdle = utils.isEmpty(overlayedCourse(start,end))
#没有预约
isIdle = isIdle and utils.isEmpty(overlayedReservation(start,end,classRoom))
if isIdle:
reservation = Reservation(user=g.user, room=classRoom, start=start, end=end)
db.session.add(reservation)
db.session.commit()
break
else:
if room is None:
return '此时段预约已满'
else:
return '此时段的 {} 预约已满'.format(room.name)
result += '您已预约 {}'.format(reservation.getDateRoom())
t1,t2 = datetime.time(hour=8), datetime.time(hour=22, minute=30)
if not (t1<=reservation.start.time()<=t2 and t1<=reservation.end.time()<=t2):
result += '\n警告:此时段琴房可能不开'
return result
@authenticated
def processCancel(start, end, roomName):
utils.writeLog('Cancellation', str(start)+', '+str(end)+', '+str(roomName), '1;32;40')
query = Reservation.query.filter_by(user=g.user)
# 如果开始时间只是一个日期,并且没有结束时间,意味着取消这一天的预约
if type(start) is datetime.date and end is None :
start = utils.toDatetime(start)
end = start + datetime.timedelta(days=1)
# 如果开始结束都是一个日期,意味着取消这几天的预约
if type(start) is datetime.date and type(end) is datetime.date :
start = utils.toDatetime(start)
end = utils.toDatetime(end + datetime.timedelta(days=1))
# 如果没有开始日期,意味着取消当前时间之后的预约
if start is None :
start = datetime.datetime.now()
end = start + datetime.timedelta(days=365)
if roomName is not None:
query = query.filter_by(room=getRoom(roomName))
if start is not None and end is not None :
query = query.filter(db.and_(start<=Reservation.start, Reservation.end<=end))
elif start is not None and end is None :
query = query.filter(db.and_(Reservation.start<=start, Reservation.end>=start))
resultList = [r.getDateRoom() for r in query]
if len(resultList)==0:
if start is not None and end is not None :
return '您没有{}到{} '.format(start, end, roomName) + (roomName if roomName is not None else '') + '的预约'
elif start is not None and end is None :
return '您没有包含{} '.format(start) + (roomName if roomName is not None else '') + '的预约'
elif roomName is not None :
return '您没有{} '.format(roomName) + '的预约'
query.delete()
db.session.commit()
return '您已取消{}{}'.format('\n'*(len(resultList)>1), '\n'.join(resultList))
@authenticated
def processQuery(start, end, roomName, userId = None) :
utils.writeLog('Query', str(start)+', '+str(end)+', '+str(roomName)+', '+str(userId), '1;32;40')
matches = []
timeRepr = ''
hasCourse = False
if end is not None and (end.date() - start.date()).days > 33 :
return '您查询的时间区间过长'
# 给定时间区间
if start != None and end != None :
for i in range((end.date() - start.date()).days+1) :
date = (start + datetime.timedelta(days=i)).date()
tmpReserve, tmpCourses = queryOccupations(utils.toDatetime(date), utils.toDatetime(date+datetime.timedelta(days=1)), getRoom(roomName))
if len(tmpReserve) + len(tmpCourses) == 0 :
continue
matches.append(formatOccupation(date, tmpReserve, tmpCourses))
hasCourse = hasCourse or (len(tmpCourses)>0)
timeRepr = utils.formatDate(start) + '至' + utils.formatDate(end)
# 给定时间点
elif start != None and end == None :
tmpReserve, tmpCourses = queryOccupations(start, None, getRoom(roomName))
hasCourse = len(tmpCourses)>0
timeRepr = utils.formatDatetime(start)
# 给定用户名
elif userId != None :
start = datetime.datetime.now()
thisUser = User.query.filter(User.openId == userId).first()
allReserve = thisUser.reservations.filter(db.and_(Reservation.start>=start)).all()
for reserve in allReserve :
matches.append(formatOccupation(reserve.start, *queryOccupations(reserve.start, reserve.end, reserve.room)))
timeRepr = '你:'
# 没有给定时间,默认当前时间
elif start == None and end == None :
start = datetime.datetime.now()
tmpReserve, tmpCourses = queryOccupations(start, None, getRoom(roomName))
hasCourse = len(tmpCourses)>0
timeRepr = utils.formatDatetime(start)
if roomName != None :
timeRepr += '的' + roomName
if len(matches) == 0:
return '{}没有预约'.format(timeRepr)
if hasCourse :
matches.append('(*)钢琴课')
return timeRepr+'\n\n'+'\n\n'.join(matches)
# 从User表中取出某一个名字对应的用户项,如果不存在,则新建一个用户项
def getCreateUser(name):
print('getting', name)
user = User.query.filter_by(name=name)
user = user.first()
if user is None:
utils.writeLog('CreateUser', name, '1;33;40')
user = User(name=name)
db.session.add(user)
db.session.commit()
print('got', name, user)
return user
def refreshCourses(fileName):
if datetime.datetime.now().month < 8 :
startDate = datetime.date(year=2021, month=3, day=1)
endDate = datetime.date(year=2022, month=7, day=1)
else :
startDate = datetime.date(year=2021, month=9, day=1)
endDate = datetime.date(year=2022, month=1, day=1)
B252 = getRoom('B252')
B250 = getRoom('B250')
B253 = getRoom('B253')
Course.query.delete()
for line in open(fileName):
weekday, startHour, endHour, teacherName = line.split()
print(weekday, startHour, endHour, teacherName)
weekday = '周一 周二 周三 周四 周五 周六 周日'.split().index(weekday)
startMinue = 0
endMinue = 0
if ':' in startHour :
startHour, startMinue = startHour.split(':')
if ':' in endHour :
endHour, endMinue = endHour.split(':')
startTime = datetime.time(hour=int(startHour), minute=int(startMinue))
endTime = datetime.time(hour=int(endHour), minute=int(endMinue))
teacher = getCreateUser(teacherName)
room = B250
course = Course(teacher=teacher, room=room, weekday=weekday,
startDate=startDate, endDate=endDate, startTime=startTime, endTime=endTime)
db.session.add(course)
db.session.commit()
def authorizeUsers(name) :
user = getCreateUser(name)
print('authorizing', user)
user.authorized = 1
db.session.commit()
def makeAdmin(name) :
user = getCreateUser(name)
print('authorizing', user)
user.administrator = 1
db.session.commit()
def about() :
return '这里是北京大学钢琴社,欢迎关注钢琴社公众号\n(づ ̄3 ̄)づ╭❤~\n\n\
输入“我是xxx”可以注册新用户,让小AI知道你是谁\n\n\
你还可以在这里留言(后台有人定期回复)\n\n\
输入“帮助”可以获得操作指南\n\n\
输入“常见问题”了解更多\n\n\
同时,欢迎访问我们的网站www.pkupiano.club'
def frequentQuestions() :
return 'Q:钢琴社开设钢琴课吗?什么时候报名?\n\
A:钢琴社每个学期都会开设钢琴课,\
针对从零基础到高基础的同学开设了一期班、二期班、提高班。\
钢琴课将在百团大战社团招新的时候报名,有意愿报名的同学请留意公众号的后续推送。\n\n\
Q:钢琴社什么时候招新?\n\
A:每个学期的百团大战期间,钢琴社都会招新~具体的时间和地点请留意公众号的后续推送。'
def easterEgg() :
return '今天你练琴了吗' + randomEmoji()
def help() :
return '你可以在这里留言(后台有人定期回复),在这里预约琴房(仅供演奏部排练用哦,平时练琴不需要也不应该预约),也可以查询预约情况\n\n\
预约的格式为:“预约+[时间起点]+到/至+[时间终点]+琴房名”,后面两项可以省略\n\n\
查询的格式为:“查询+[时间起点]+到/至+[时间终点]+琴房名”,后面两项可以省略\n\n\
注册的格式为:“我是xxx”\n\n\
输入“常见问题”了解更多'
@isAdmin
def checkLog() :
current_app.adminCode = random.randint(1,1000000000)
return 'http://82.157.114.38/klavx/admin/?code='+str(current_app.adminCode)
def showDatabase(dbName) :
p = dbName.query.all()
for i in p :
print(i)