DotaNoobs main site.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

373 lines
21 KiB

  1. from flask import url_for
  2. from operator import itemgetter
  3. from os import path
  4. import ts3
  5. from datetime import datetime, timedelta
  6. from xml.etree import ElementTree
  7. from bs4 import BeautifulSoup
  8. import models
  9. from app import app, db
  10. def getTeamspeakWindow(window=timedelta(weeks=1)):
  11. current_time = datetime.utcnow()
  12. return models.TeamspeakData.query.filter(models.TeamspeakData.time < current_time, models.TeamspeakData.time > current_time-window).order_by(models.TeamspeakData.time).all()
  13. def registerUserTeamspeakId(user, tsid):
  14. server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
  15. server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
  16. server.use(1)
  17. response = server.send_command('clientdbfind', {'pattern':tsid.encode('utf-8')}, ('uid',))
  18. if response.is_successful:
  19. cdbid = response.data[0]['cldbid']
  20. user.teamspeak_id = tsid
  21. sgid = [entry['sgid'] for entry in server.send_command('servergrouplist').data if entry['name'] == 'Normal' and entry['type'] == '1'][0]
  22. server.send_command('servergroupaddclient', {'sgid': sgid, 'cldbid': cdbid})
  23. db.session.commit()
  24. return True
  25. return False
  26. def create_teamspeak_viewer():
  27. try:
  28. server = ts3.TS3Server(app.config['TS3_HOST'], app.config['TS3_PORT'])
  29. server.login(app.config['TS3_USERNAME'], app.config['TS3_PASSWORD'])
  30. server.use(1)
  31. serverinfo = server.send_command('serverinfo').data
  32. channellist = server.send_command('channellist', opts=("limits", "flags", "voice", "icon")).data
  33. clientlist = server.send_command('clientlist', opts=("away", "voice", "info", "icon", "groups", "country")).data
  34. servergrouplist = server.send_command('servergrouplist').data
  35. channelgrouplist = server.send_command('channelgrouplist').data
  36. soup = BeautifulSoup()
  37. div_tag = soup.new_tag('div')
  38. div_tag['class'] ='devmx-webviewer'
  39. soup.append(div_tag)
  40. def construct_channels(parent_tag, cid):
  41. num_clients = 0
  42. for channel in channellist:
  43. if int(channel['pid']) == int(cid):
  44. # Construct the channel
  45. channel_tag = soup.new_tag('div')
  46. channel_tag['class'] = 'tswv-channel'
  47. # Channel image
  48. image_tag = soup.new_tag('span')
  49. image_tag['class'] = 'tswv-image tswv-image-right'
  50. if int(channel['channel_flag_password']) == 1:
  51. image_tag['class'] += ' tswv-channel-password-right'
  52. if int(channel['channel_flag_default']) == 1:
  53. image_tag['class'] += ' tswv-channel-home'
  54. if int(channel['channel_needed_talk_power']) > 0:
  55. image_tag['class'] += ' tswv-channel-moderated'
  56. if int(channel['channel_icon_id']) != 0:
  57. pass
  58. image_tag.append(' ')
  59. channel_tag.append(image_tag)
  60. # Status image
  61. status_tag = soup.new_tag('span')
  62. status_tag['class'] = 'tswv-image'
  63. if int(channel['channel_flag_password']) == 1:
  64. status_tag['class'] += ' tswv-channel-password'
  65. elif int(channel['total_clients']) == int(channel['channel_maxclients']):
  66. status_tag['class'] += ' tswv-channel-full'
  67. else:
  68. status_tag['class'] += ' tswv-channel-normal'
  69. status_tag.append(' ')
  70. channel_tag.append(status_tag)
  71. # Label
  72. label_tag = soup.new_tag('span')
  73. label_tag['class'] = 'tswv-label'
  74. label_tag.append(channel['channel_name'])
  75. channel_tag.append(label_tag)
  76. # Clients
  77. channel_tag, channel_clients = construct_clients(channel_tag, channel['cid'])
  78. # Recurse through sub-channels, collecting total number of clients as we go
  79. channel_tag, sub_clients = construct_channels(channel_tag, channel['cid'])
  80. channel_clients += sub_clients
  81. # Only show non-empty channels
  82. if channel_clients > 0:
  83. parent_tag.append(channel_tag)
  84. num_clients += channel_clients
  85. return parent_tag, num_clients
  86. def construct_clients(parent_tag, cid):
  87. num_clients = 0
  88. for client in clientlist:
  89. if int(client['cid']) == int(cid):
  90. # Skip ServerQuery clients
  91. if int(client['client_type']) == 1: continue
  92. num_clients += 1
  93. client_tag = soup.new_tag('div')
  94. client_tag['class'] = 'tswv-client'
  95. # Status image
  96. status_tag = soup.new_tag('span')
  97. status_tag['class'] = 'tswv-image'
  98. if int(client['client_type']) == 1:
  99. status_tag['class'] += ' tswv-client-query'
  100. elif int(client['client_away']) == 1:
  101. status_tag['class'] += " tswv-client-away"
  102. elif int(client['client_input_muted']) == 1:
  103. status_tag['class'] += " tswv-client-input-muted"
  104. elif int(client['client_output_muted']) == 1:
  105. status_tag['class'] += " tswv-client-output-muted"
  106. elif int(client['client_input_hardware']) == 0:
  107. status_tag['class'] += " tswv-client-input-muted-hardware"
  108. elif int(client['client_output_hardware']) == 0:
  109. status_tag['class'] += " tswv-client-output-muted-hardware"
  110. elif (int(client['client_flag_talking']) == 1) and (int(client['client_is_channel_commander']) == 1):
  111. status_tag['class'] += " tswv-client-channel-commander-talking"
  112. elif int(client['client_is_channel_commander']) == 1:
  113. status_tag['class'] += " tswv-client-channel-commander"
  114. elif int(client['client_flag_talking']) == 1:
  115. status_tag['class'] += " tswv-client-talking"
  116. else:
  117. status_tag['class'] += " tswv-client-normal"
  118. status_tag.append(' ')
  119. client_tag.append(status_tag)
  120. # Country image
  121. if client['client_country']:
  122. country_tag = soup.new_tag('span')
  123. country_tag['class'] = 'tswv-image tswv-image-right'
  124. country_tag['title'] = ' '.join([word.capitalize() for word in ISO3166_MAPPING[client['client_country']].split(' ')])
  125. country_tag['style'] = 'background: url("%s") center center no-repeat;' % url_for('static', filename='img/ts3_viewer/countries/%s.png' % client['client_country'].lower())
  126. country_tag.append(' ')
  127. client_tag.append(country_tag)
  128. # Server group images
  129. sgids = [int(sg) for sg in client['client_servergroups'].split(',')]
  130. servergroups = [servergroup for servergroup in servergrouplist if int(servergroup['sgid']) in sgids]
  131. servergroups.sort(key=itemgetter('sortid'))
  132. for servergroup in servergroups:
  133. if not servergroup['iconid']: continue
  134. img_fname = 'img/ts3_viewer/%s.png' % servergroup['iconid']
  135. if not path.exists(path.join(app.static_folder, img_fname)):
  136. continue
  137. image_tag = soup.new_tag('span')
  138. image_tag['class'] = 'tswv-image tswv-image-right'
  139. image_tag['title'] = servergroup['name']
  140. image_tag['style'] = 'background-image: url("%s")' % url_for('static', filename=img_fname)
  141. image_tag.append(' ')
  142. client_tag.append(image_tag)
  143. # Check if client is in a moderated channel
  144. channel = [channel for channel in channellist if int(channel['cid']) == int(client['cid'])][0]
  145. if int(channel['channel_needed_talk_power']) > 0:
  146. status_tag = soup.new_tag('span')
  147. status_tag['class'] = 'tswv-image tswv-image-right'
  148. if int(client['client_is_talker']) == 0:
  149. status_tag['class'] += ' tswv-client-input-muted'
  150. else:
  151. status_tag['class'] += ' tswv-client-talkpower-granted'
  152. status_tag.append(' ')
  153. client_tag.append(status_tag)
  154. # Label
  155. label_tag = soup.new_tag('span')
  156. label_tag['class'] = 'tswv-label'
  157. label_tag.append(client['client_nickname'])
  158. client_tag.append(label_tag)
  159. parent_tag.append(client_tag)
  160. return parent_tag, num_clients
  161. div_tag, num_clients = construct_channels(div_tag, 0)
  162. return (soup.prettify(), num_clients)
  163. except Exception as inst:
  164. return "error: %s" % inst
  165. def get_ISO3166_mapping():
  166. with open(path.join(path.dirname(__file__), 'static/country_codes.xml'), mode='r') as d:
  167. data = d.read()
  168. xml = ElementTree.fromstring(data)
  169. d = dict()
  170. for entry in xml.findall('ISO_3166-1_Entry'):
  171. d[entry.find('ISO_3166-1_Alpha-2_Code_element').text] = entry.find('ISO_3166-1_Country_name').text
  172. return d
  173. ISO3166_MAPPING = get_ISO3166_mapping()
  174. #
  175. # Scheduled functions for TeamspeakServer
  176. #
  177. def idle_mover(server):
  178. """ Checks connected clients idle_time, moving to AFK if over TS3_MAX_IDLETIME. """
  179. app.logger.debug("Running TS3 AFK mover...")
  180. exempt_cids = []
  181. permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
  182. if permid_response.is_successful:
  183. join_permid = permid_response.data[0]['permid']
  184. def exempt_check(cid):
  185. # check flags
  186. flag_response = server.send_command('channelinfo', keys={'cid': cid})
  187. if flag_response.is_successful:
  188. if flag_response.data[0]['channel_needed_talk_power'] != '0': return True
  189. permid_response = server.send_command('channelpermlist', keys={'cid': cid})
  190. if permid_response.is_successful:
  191. for perm in permid_response.data:
  192. if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
  193. return False
  194. list_response = server.send_command('channellist')
  195. if list_response.is_successful:
  196. for channel in list_response.data:
  197. if exempt_check(channel['cid']):
  198. exempt_cids.append(channel['cid'])
  199. # get destination
  200. response = server.send_command('channelfind', keys={'pattern': 'AFK'})
  201. if response.is_successful:
  202. afk_channel = response.data[0]
  203. # Get list of clients
  204. clientlist = server.send_command('clientlist', opts=['times']).data
  205. for client in clientlist:
  206. clientinfo = server.send_command('clientinfo', {'clid':client['clid']})
  207. #if clientinfo.is_successful:
  208. #client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
  209. # move idlers to afk channel
  210. for client in clientlist:
  211. if( int(client['client_idle_time']) > app.config['TS3_MAX_IDLETIME']):
  212. if client['cid'] not in exempt_cids:
  213. # Have TeamSpeak move AFK user to appropriate channel
  214. server.send_command('clientmove', keys={'clid': client['clid'], 'cid': afk_channel['cid']})
  215. def store_active_data(server):
  216. """ Take a snapshot of Teamspeak (clients, countries, etc) to feed the ts3_stats page """
  217. app.logger.debug("Taking Teamspeak snapshot...")
  218. # Get exempt channels (AFK, passworded, join powers)
  219. exempt_cids = []
  220. permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
  221. if permid_response.is_successful:
  222. join_permid = permid_response.data[0]['permid']
  223. def exempt_check(cid):
  224. # Check flags
  225. flag_response = server.send_command('channelinfo', keys={'cid': cid})
  226. if flag_response.is_successful:
  227. if flag_response.data[0]['channel_flag_password'] != '0': return True
  228. if flag_response.data[0]['channel_needed_talk_power'] != '0': return True
  229. permid_response = server.send_command('channelpermlist', keys={'cid': cid})
  230. if permid_response.is_successful:
  231. for perm in permid_response.data:
  232. if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
  233. return False
  234. list_response = server.send_command('channellist')
  235. if list_response.is_successful:
  236. for channel in list_response.data:
  237. if exempt_check(channel['cid']):
  238. exempt_cids.append(channel['cid'])
  239. # Get list of clients
  240. clientlist = server.send_command('clientlist', opts=("country",)).data
  241. # Remove the server_query and afk/moderated clients
  242. clientlist = filter(lambda client: client['client_type'] == '0' and client['cid'] not in exempt_cids, clientlist)
  243. # Compile the important information
  244. clients = {}
  245. for client in clientlist:
  246. clientinfo = server.send_command('clientdbinfo', {'cldbid': client['client_database_id']})
  247. if clientinfo.is_successful:
  248. clients[clientinfo.data[0]['client_unique_identifier']] = {'country': client['client_country']}
  249. else:
  250. raise UserWarning('Could not find the clientdbinfo for %s' % client['client_database_id'])
  251. # Update the data
  252. tsdata = models.TeamspeakData(clients)
  253. db.session.add(tsdata)
  254. db.session.commit()
  255. def process_ts3_events(server):
  256. """ Create Teamspeak channels for upcoming events, delete empty event channels that have expired """
  257. app.logger.debug("Processing Teamspeak events...")
  258. # Get list of clients
  259. clientlist = server.clientlist()
  260. for clid, client in clientlist.iteritems():
  261. clientinfo = server.send_command('clientinfo', {'clid':clid})
  262. if clientinfo.is_successful:
  263. client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
  264. # Process any active events
  265. for clid, client in clientlist.iteritems():
  266. u = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
  267. e = models.Event.query.filter(models.Event.start_time <= datetime.utcnow(), models.Event.end_time > datetime.utcnow()).all()
  268. if u and e:
  269. for event in e:
  270. if client['cid'] in event.cids:
  271. event.add_participant(u)
  272. # Add channels for upcoming events
  273. e = models.Event.query.filter(models.Event.start_time >= datetime.utcnow(), \
  274. models.Event.start_time <= (datetime.utcnow() + timedelta(minutes=60))).all()
  275. for event in e:
  276. if not event.cids:
  277. print("Adding channels for event {}".format(event.name))
  278. event.create_channels()
  279. # Remove channels for expired events
  280. e = models.Event.query.filter(models.Event.start_time > (datetime.utcnow() - timedelta(hours=24)), \
  281. models.Event.end_time < (datetime.utcnow() - timedelta(minutes=60))).all()
  282. for event in e:
  283. current_time = datetime.utcnow()
  284. remove_time = event.end_time + timedelta(minutes=60)
  285. warn_time = event.end_time + timedelta(minutes=30)
  286. time_left = remove_time - current_time
  287. message = "This event channel is temporary and will be removed in {} minutes.".format(divmod(time_left.days * 86400 + time_left.seconds, 60)[0])
  288. if event.cids:
  289. if current_time > remove_time:
  290. print("Removing channels for event: {}".format(event.name))
  291. event.remove_channels()
  292. elif current_time > warn_time:
  293. for cid in event.cids:
  294. clients = [client for client in clientlist.values() if int(client['cid']) == int(cid)]
  295. for client in clients:
  296. print("Warning {} about expired event {}".format(client['client_nickname'], event.name))
  297. server.clientpoke(client['clid'], message)
  298. def award_idle_ts3_points(server):
  299. """ Award points for active time spent in the Teamspeak server. """
  300. app.logger.debug("Awarding Teamspeak idle points")
  301. # Get exempt channels (AFK, passwords, join power)
  302. exempt_cids = []
  303. permid_response = server.send_command('permidgetbyname', keys={'permsid': 'i_channel_needed_join_power'})
  304. if permid_response.is_successful:
  305. join_permid = permid_response.data[0]['permid']
  306. def exempt_check(cid):
  307. # Check flags
  308. flag_response = server.send_command('channelinfo', keys={'cid':cid})
  309. if flag_response.is_successful:
  310. if flag_response.data[0]['channel_flag_password'] != '0': return True
  311. if flag_response.data[0]['channel_needed_talk_power'] !='0': return True
  312. permid_response = server.send_command('channelpermlist', keys={'cid': cid})
  313. if permid_response.is_successful:
  314. for perm in permid_response.data:
  315. if perm['permid'] == join_permid and perm['permvalue'] != '0': return True
  316. return False
  317. list_response = server.send_command('channellist')
  318. if list_response.is_successful:
  319. for channel in list_response.data:
  320. if exempt_check(channel['cid']):
  321. exempt_cids.append(channel['cid'])
  322. # Get list of clients
  323. clientlist = server.clientlist()
  324. for clid, client in clientlist.iteritems():
  325. clientinfo = server.send_command('clientinfo', {'clid': clid})
  326. if clientinfo.is_successful:
  327. client['client_unique_identifier'] = clientinfo.data[0]['client_unique_identifier']
  328. # Update the data
  329. active_users = set()
  330. for client in clientlist.values():
  331. if client['cid'] not in exempt_cids:
  332. try:
  333. doob = models.User.query.filter_by(teamspeak_id=client['client_unique_identifier']).first()
  334. if doob:
  335. doob.update_connection()
  336. active_users.add(doob)
  337. except KeyError:
  338. pass
  339. doobs = set(models.User.query.filter(models.User.ts3_starttime != None).all())
  340. for doob in doobs.difference(active_users):
  341. doob.finalize_connection()