Module ts3client
[hide private]
[frames] | no frames]

Source Code for Module ts3client

  1  import os 
  2   
  3  from PythonQt.QtCore import Qt 
  4  from PythonQt.QtSql import QSqlDatabase 
  5  from PythonQt.QtGui import QPixmap, QPainter, QPen 
  6   
  7  import ts3lib 
  8  import ts3defines 
  9   
 10  from configparser import ConfigParser 
 11  from zipfile import ZipFile 
 12   
 13  import base64 
14 15 16 -class Config(object):
17 """ 18 Offers an interface to query the TeamSpeak 3 client's config 19 database (settings.db). 20 You should always del a reference to this object if not needed anymore to 21 assure the database connection is closed. 22 """ 23
24 - class _config(object):
25 - def __init__(self):
26 self.db = QSqlDatabase.addDatabase("QSQLITE", "__pyTSon_config__") 27 self.db.setDatabaseName(os.path.join(ts3lib.getConfigPath(), 28 "settings.db")) 29 30 if not self.db.isValid(): 31 raise Exception("Database not valid") 32 33 if not self.db.open(): 34 raise Exception("Database could not be opened")
35
36 - def __del__(self):
37 self.db.close() 38 self.db.delete() 39 QSqlDatabase.removeDatabase("__pyTSon_config__")
40
41 - def query(self, sql):
42 """ 43 Executes a query on the database. 44 @param sql: the query to be executed 45 @type sql: str 46 @return: the QSqlQuery object 47 @rtype: QSqlQuery 48 """ 49 return self.db.exec_(sql)
50
51 - def lastError(self):
52 """ 53 Returns the last error occured in the QSqlDatabase as string. 54 @return: the error 55 @rtype: str 56 """ 57 return self.db.lastError().text()
58 59 objcount = 0 60 instance = None 61
62 - def __init__(self):
66
67 - def __del__(self):
68 Config.objcount -= 1 69 70 if Config.objcount == 0: 71 del Config.instance 72 Config.instance = None
73
74 - def __getattr__(self, name):
75 return getattr(Config.instance, name)
76
77 78 -class IconPack(object):
79 """ 80 Offers an interface to the TeamSpeak 3 Client's iconpack. IconPack is also 81 a context manager. 82 """ 83 84 _prefixes = ["200x200_", "100x100_", "32x32_", "24x24_", "20x20_", 85 "16x16_", "8x7_", "7x5_", ""] 86 87 _varNames = ['3D_SOUND', '3D_SOUND_ME', 'ABOUT', 'ACTIVATE_MICROPHONE', 88 'ADD', 'ADD_FOE', 'ADD_FRIEND', 'ADDON', 'ADDON_COLLECTION', 89 'ADDON_BROWSER', 'ADDON_ICONPACK', 'ADDON_SOUNDPACK', 90 'ADDON_THEME', 'ADDON_TRANSLATION', 'ARROW_LEFT', 91 'ARROW_RIGHT', 'AWAY', 'BAN_CLIENT', 'BAN_LIST', 92 'BOOKMARK_ADD', 'BOOKMARK_ADD_FOLDER', 'BOOKMARK_DUPLICATE', 93 'BOOKMARK_MANAGER', 'BOOKMARK_REMOVE', 'BROKEN_IMAGE', 94 'CAPTURE', 'CHANGE_NICKNAME', 'CHANGELOG', 'CHANNEL_CHAT', 95 'CHANNEL_COLLAPSE_ALL', 'CHANNEL_COMMANDER', 'CHANNEL_CREATE', 96 'CHANNEL_CREATE_SUB', 'CHANNEL_DELETE', 'CHANNEL_EDIT', 97 'CHANNEL_EXPAND_ALL', 'CHANNEL_GREEN', 98 'CHANNEL_GREEN_SUBSCRIBED', 'CHANNEL_PRIVATE', 'CHANNEL_RED', 99 'CHANNEL_RED_SUBSCRIBED', 'CHANNEL_SWITCH', 'CHANNEL_YELLOW', 100 'CHANNEL_YELLOW_SUBSCRIBED', 'CHECK_UPDATE', 'CONNECT', 101 'COPY', 'DEFAULT', 'DELETE', 'DELETE_AVATAR', 'DISCONNECT', 102 'DOWN', 'DOWN_ARROW', 'EDIT', 'EDIT_FRIEND_FOE_STATUS', 103 'EMOTICON', 'ERROR', 'FILE_HOME', 'FILE_REFRESH', 'FILE_UP', 104 'FILETRANSFER', 'FIND', 'GROUP_100', 'GROUP_200', 'GROUP_300', 105 'GROUP_500', 'GROUP_600', 'GUISETUP', 'HARDWARE_INPUT_MUTED', 106 'HARDWARE_OUTPUT_MUTED', 'HOSTER_BUTTON', 'HOTKEYS', 107 'ICONSVIEW', 'IDENTITY_ADD', 'IDENTITY_DEFAULT', 108 'IDENTITY_EXPORT', 'IDENTITY_IMPORT', 'IDENTITY_MANAGER', 109 'IDENTITY_REMOVE', 'INFO', 'INPUT_MUTED', 'INPUT_MUTED_LOCAL', 110 'IS_TALKER', 'KICK_FROM_CHANNEL', 'KICK_FROM_SERVER', 111 'LISTVIEW', 'LOADING_IMAGE', 'MESSAGE_INCOMING', 112 'MESSAGE_INFO', 'MESSAGE_OUTGOING', 'MODERATED', 'MUSIC', 113 'MYTS_ACCOUNT_ONLINE', 'MYTS_ACCOUNT_OFFLINE', 114 'MYTS_ACCOUNT_ERROR', 'MYTS_ACCOUNT', 'NEW_CHAT', 115 'NOTIFICATIONS', 'OFFLINE_MESSAGES', 'ON_WHISPERLIST', 116 'OUTPUT_MUTED', 'PERMISSION_OVERVIEW', 117 'PERMISSIONS_CHANNEL_CLIENT', 'PERMISSIONS_CHANNEL_GROUPS', 118 'PERMISSIONS_CHANNELS', 'PERMISSIONS_CLIENTS', 119 'PERMISSIONS_SERVER_GROUPS', 'PHONETICSNICKNAME', 'PING_1', 120 'PING_2', 'PING_3', 'PING_4', 'PING_CALCULATING', 121 'PING_DISCONNECTED', 'PLAY', 'PLAYBACK', 'PLAYER_CHAT', 122 'PLAYER_COMMANDER_OFF', 'PLAYER_COMMANDER_ON', 'PLAYER_OFF', 123 'PLAYER_ON', 'PLAYER_WHISPER', 'PLUGINS', 'POKE', 'PRESENT', 124 'QUIT', 'RECORDING_START', 'RECORDING_STOP', 'REGISTER', 125 'REMOVE_FOE', 'REMOVE_FRIEND', 'RELOAD', 'SECURITY', 126 'SELECTFOLDER', 'SEND_COMPLAINT', 'SERVER_GREEN', 127 'SERVER_LOG', 'SERVER_QUERY', 'SETTINGS', 'SETUP_WIZARD', 128 'STOP', 'SUBSCRIBE_TO_ALL_CHANNELS', 'SUBSCRIBE_TO_CHANNEL', 129 'SYNC_ENABLE', 'SYNC_DISABLE', 'SYNC', 'TAB_CLOSE_BUTTON', 130 'TEMP_SERVER_PASSWORD', 'TEMP_SERVER_PASSWORD_ADD', 131 'TEXTFORMAT', 'TEXTFORMAT_BOLD', 'TEXTFORMAT_FOREGROUND', 132 'TEXTFORMAT_ITALIC', 'TEXTFORMAT_UNDERLINE', 133 'TOGGLE_BLOCK_INCOMING_WHISPERS', 134 'TOGGLE_SERVER_QUERY_CLIENTS', 'TOKEN', 135 'UNSUBSCRIBE_FROM_ALL_CHANNELS', 'UNSUBSCRIBE_FROM_CHANNEL', 136 'UP', 'UP_ARROW', 'UPLOAD_AVATAR', 'URLCATCHER', 137 'VIRTUALSERVER_EDIT', 'VOLUME', 'WARNING', 'WEBLIST', 138 'WHISPERLISTS', 'WARNING_EXTERNAL_LINK'] 139 140 @staticmethod
141 - def current():
142 """ 143 Returns the current iconpack used (an Exception is raised if something 144 failed). 145 @return: the iconpack 146 @rtype: IconPack 147 """ 148 cfg = Config() 149 q = cfg.query("SELECT value FROM application WHERE key='IconPack'") 150 if not q.next(): 151 err = cfg.lastError() 152 del cfg 153 raise Exception("Query failed: %s" % err) 154 155 del cfg 156 return IconPack(info=q.value(0))
157
158 - def __init__(self, info=None, name=None):
159 """ 160 Instantiates a new IconPack object referenced by its name or the 161 internal info string (an Exception is raised if the iconpack could 162 not be located). 163 @param info: the info string used in the settings.db 164 @type info: str 165 @param name: the name of the iconpack 166 @type name: str 167 """ 168 if name: 169 cfg = Config() 170 q = cfg.query("SELECT value FROM addons WHERE value LIKE " 171 "'%%name=%s%%'" % name) 172 173 if not q.next(): 174 del cfg 175 raise Exception("No iconpack with name %s found in database" % 176 name) 177 178 for l in q.value(0).split(' '): 179 if l.startswith("info="): 180 self.info = l.split('=')[1] 181 break 182 183 del cfg 184 185 if self.info == "default.zip": 186 self.info = "default" 187 elif not self.info: 188 raise Exception("Unknown settings format in database") 189 elif info: 190 self.info = info 191 else: 192 raise Exception("No info or name given") 193 194 self.path = None 195 self.zip = False 196 self.emos = {} 197 198 if info[-4:].lower() == ".zip": 199 self.zip = True 200 201 if os.path.isfile(os.path.join(ts3lib.getResourcesPath(), 202 "gfx", info)): 203 self.path = os.path.join(ts3lib.getResourcesPath(), 204 "gfx", info) 205 elif os.path.isfile(os.path.join(ts3lib.getConfigPath(), "gfx", 206 info)): 207 self.path = os.path.join(ts3lib.getConfigPath(), "gfx", info) 208 else: 209 if os.path.isdir(os.path.join(ts3lib.getResourcesPath(), "gfx", 210 info)): 211 self.path = os.path.join(ts3lib.getResourcesPath(), "gfx", 212 info) 213 elif os.path.isfile(os.path.join(ts3lib.getResourcesPath(), "gfx", 214 info + ".zip")): 215 self.zip = True 216 self.path = os.path.join(ts3lib.getResourcesPath(), "gfx", 217 info + ".zip") 218 elif os.path.isdir(os.path.join(ts3lib.getConfigPath(), "gfx", 219 info)): 220 self.path = os.path.join(ts3lib.getConfigPath(), "gfx", info) 221 elif os.path.isfile(os.path.join(ts3lib.getConfigPath(), "gfx", 222 info + ".zip")): 223 self.zip = True 224 self.path = os.path.join(ts3lib.getConfigPath(), "gfx", 225 info + ".zip") 226 227 if not self.path: 228 raise Exception("Iconpack %s not found" % info)
229
230 - def _loadEmoticonSettings(self, f):
231 self.emos.clear() 232 for line in f: 233 if self.zip: 234 line = line.decode('utf-8') 235 e = line.split('=') 236 if len(e) == 2: 237 txt = e[1].strip() 238 if txt.startswith('"') and txt.endswith('"') and len(txt) >= 3: 239 self.emos[txt[1:-1]] = e[0].strip()
240
241 - def open(self):
242 """ 243 Reads the settings for the iconpack and if it's zip-based, opens the 244 file for reading. Must be called once before any icon can be accessed. 245 """ 246 if self.zip: 247 self.cont = ZipFile(self.path) 248 if self.info != "default": 249 if "settings.ini" not in self.cont.namelist(): 250 raise Exception("No settings.ini in iconpack found") 251 252 self.settings = ConfigParser(strict=False) 253 with self.cont.open("settings.ini") as f: 254 self.settings.read_string(f.read().decode('utf-8')) 255 256 emconf = os.path.join("emoticons", "emoticons.txt") 257 if emconf in self.cont.namelist(): 258 with self.cont.open(emconf) as f: 259 self._loadEmoticonSettings(f) 260 else: 261 if ("settings.ini" not in os.listdir(self.path) or 262 not os.path.isfile(os.path.join(self.path, "settings.ini"))): 263 raise Exception("No settings.ini in iconpack found") 264 265 self.settings = ConfigParser(strict=False) 266 self.settings.read(os.path.join(self.path, "settings.ini")) 267 268 emconf = os.path.join(self.path, "emoticons", "emoticons.txt") 269 if os.path.isfile(emconf): 270 with open(emconf, "r") as f: 271 self._loadEmoticonSettings(f)
272
273 - def close(self):
274 """ 275 If the iconpack is zip-based, the file is closed. After this is called, 276 no icons can be accessed (till open is called again). 277 """ 278 if self.zip: 279 self.cont.close()
280
281 - def __enter__(self):
282 self.open() 283 return self
284
285 - def __exit__(self, type, value, traceback):
286 self.close()
287 288 @staticmethod
289 - def defaultName(var):
290 """ 291 Returns the variable name used in the default iconpack. 292 @param var: the variable used in an iconpack 293 @type var:str 294 @return: the variable name 295 @rtype: str 296 """ 297 if var == "3D_SOUND_OTHER": 298 var = "3D_SOUND_USER" 299 elif var == "CHANNEL_COLLAPSE_SUB": 300 var = "CHANNEL_COLLAPSE_ALL" 301 elif var == "CHANNEL_EXPAND_SUB": 302 var = "CHANNEL_EXPAND_ALL" 303 elif var == "COMPLAINT_LIST": 304 var = "SEND_COMPLAINT" 305 elif var == "CLIENT_HIDE": 306 var = "UNSUBSCRIBE_FROM_CHANNEL" 307 elif var == "CLIENT_SHOW": 308 var = "SUBSCRIBE_TO_CHANNEL" 309 elif var == "CONNECT_NEW_TAB": 310 var = "CONNECT" 311 elif var == "CONTACT": 312 var = "EDIT_FRIEND_FOE_STATUS" 313 elif var == "COPY_URL": 314 var = "COPY" 315 elif var == "DELETE_AVATAR_OTHER": 316 var = "DELETE_AVATAR" 317 elif var == "DISCONNECT_ALL": 318 var = "DISCONNECT" 319 elif var == "DOWNLOAD": 320 var = "DOWN" 321 elif var == "FILTER_CLEAR": 322 var = "ERROR" 323 elif var == "ICONVIEWER": 324 var = "GROUP_300" 325 elif var == "INVITE_BUDDY": 326 var = "NOTIFICATIONS" 327 elif var == "MAKE_CURRENT_CHANNEL_DEFAULT": 328 var = "CHECK_UPDATE" 329 elif var == "MESSAGES": 330 var = "AWAY" 331 elif var == "MOVE_CLIENT_TO_OWN_CHANNEL": 332 var = "CHECK_UPDATE" 333 elif var == "SEARCH_CLEAR": 334 var = "ERROR" 335 elif var == "SEARCH_HIDE": 336 var = "ERROR" 337 elif var == "SUBSCRIBE_MODE": 338 var = "SUBSCRIBE_TO_ALL_CHANNELS" 339 elif var == "SUBSCRIBE_TO_CHANNEL_FAMILY": 340 var = "SUBSCRIBE_TO_ALL_CHANNELS" 341 elif var == "SWITCH_ADVANCED" or var == "SWITCH_STANDARD": 342 var = "SETUP_WIZARD" 343 elif var == "TALK_POWER_REQUEST": 344 var = "REQUEST_TALK_POWER" 345 elif var == "TALK_POWER_CANCEL_REQUEST": 346 var = "REQUEST_TALK_POWER_CANCEL" 347 elif var == "TALK_POWER_GRANT": 348 var = "IS_TALKER" 349 elif var == "TALK_POWER_REVOKE": 350 var = "REVOKE_TALKER" 351 elif var == "UNSUBSCRIBE_MODE": 352 var = "UNSUBSCRIBE_FROM_ALL_CHANNELS" 353 elif var == "UNSUBSCRIBE_TO_CHANNEL_FAMILY": 354 var = "UNSUBSCRIBE_FROM_ALL_CHANNELS" 355 elif var == "UPLOAD": 356 var = "UP" 357 elif var == "TOKEN_USE": 358 var = "TOKEN" 359 elif var == "WHISPER": 360 var = "WHISPERLISTS" 361 elif var == "WHISPERHISTORY": 362 var = "WHISPERLISTS" 363 elif var in IconPack._varNames: 364 return var 365 else: 366 return "" 367 368 return var 369 370 """ 371 not matched (not used anymore?): 372 APPLY 373 BOOKMARK_SET_FOR_ALL 374 CHANNEL_CREATE_SPACER 375 CLOSE_BUTTON 376 FOLDER 377 FOLDER_CREATE 378 REFRESH 379 RENAME 380 SEARCH 381 SORT_BY_NAME 382 SOUNDPACK 383 TALK_POWER_GRANT_NEXT 384 TALK_POWER_REVOKE_ALL_GRANT_NEXT 385 """
386
387 - def fallback(self, var):
388 """ 389 Returns the fallback icon for a variable according to the 390 iconpack's settings. 391 @param var: the variable name 392 @type var: str 393 @return: the resulting pixmap 394 @rtype: QPixmap 395 """ 396 if self.info == "default": 397 return QPixmap() 398 399 if (self.settings.has_option("options", "FALLBACK") and 400 self.settings.getboolean("options", "FALLBACK")): 401 try: 402 with IconPack(info="default") as defpack: 403 return defpack.icon(var) 404 except: 405 return QPixmap() 406 elif (self.settings.has_option("options", "PLACEHOLDER") and 407 self.settings.getint("options", "PLACEHOLDER") == 2): 408 ret = QPixmap(32, 32) 409 painter = QPainter(ret) 410 painter.setPen(QPen(Qt.blue, 2)) 411 painter.drawRect(0, 0, 128, 128) 412 painter.delete() 413 414 return ret 415 else: 416 return QPixmap()
417
418 - def icons(self):
419 """ 420 Returns the list of variables used in the iconpack (excluding 421 fallback mechanisms). 422 @return: a list of variable names 423 @rtype: list[str] 424 """ 425 return self.settings.options("gfxfiles")
426
427 - def _findDefaultFilename(self, var):
428 if self.zip: 429 for p in self._prefixes: 430 n = "%s%s.png" % (p, var.lower()) 431 if n in self.cont.namelist(): 432 return n 433 else: 434 for p in self._prefixes: 435 n = "%s%s.png" % (p, var.lower()) 436 if os.path.isfile(os.path.join(self.path, n)): 437 return n 438 return ""
439
440 - def icon(self, var):
441 """ 442 Returns the icon representing a variable used in the iconpack. 443 If the icon cannot be found, the iconpack's fallback mechanisms are 444 used. If everything fails, an empty pixmap is returned. 445 @param var: the variable name 446 @type var: str 447 @return: the resulting pixmap 448 @rtype: QPixmap 449 """ 450 if self.info == "default": 451 path = self._findDefaultFilename(self.defaultName(var)) 452 if path == "": 453 return QPixmap() 454 else: 455 if not self.settings.has_option("gfxfiles", var): 456 return self.fallback(var) 457 path = self.settings.get("gfxfiles", var) 458 if path == "": 459 return self.fallback(var) 460 461 if self.zip: 462 if path in self.cont.namelist(): 463 with self.cont.open(path) as f: 464 ret = QPixmap() 465 if ret.loadFromData(f.read()): 466 return ret 467 else: 468 return QPixmap() 469 else: 470 return self.fallback(var) 471 else: 472 if os.path.isfile(os.path.join(self.path, path)): 473 return QPixmap(os.path.join(self.path, path)) 474 else: 475 return self.fallback(var)
476
477 - def emoticons(self):
478 """ 479 Returns the list of emoticon replacements used in the iconpack. 480 @return: a list of emoticon strings 481 @rtype: list[str] 482 """ 483 return self.emos.keys()
484
485 - def emoticon(self, text):
486 """ 487 Returns the icon replacing the emoticon string. 488 @param text: the emoticon as string 489 @type text: str 490 @return: the resulting pixmap 491 @rtype: QPixmap 492 """ 493 if text in self.emos: 494 empath = os.path.join("emoticons", self.emos[text]) 495 if self.zip: 496 if empath in self.cont.namelist(): 497 with self.cont.open(empath) as f: 498 ret = QPixmap() 499 if ret.loadFromData(f.read()): 500 return ret 501 else: 502 if os.path.isfile(os.path.join(self.path, empath)): 503 return QPixmap(os.path.join(self.path, empath)) 504 505 return QPixmap()
506
507 508 -class ServerCache:
509 """ 510 Offers an interface to the cached data of a TeamSpeak 3 server. 511 """ 512
513 - def __init__(self, schid):
514 """ 515 Instantiates a new ServerCache object referenced by the server 516 connection handler id (an Exception is raised if the path in the 517 filesystem could not be located). 518 @param schid: the ID of the serverconnection 519 @type schid: int 520 """ 521 err, uid = (ts3lib.getServerVariableAsString(schid, 522 ts3defines.VirtualServerProperties. 523 VIRTUALSERVER_UNIQUE_IDENTIFIER)) 524 525 if err != ts3defines.ERROR_ok: 526 raise Exception("Error getting Server unique identifier: %s" % err) 527 528 self.path = os.path.join(ts3lib.getConfigPath(), "cache", 529 base64.b64encode( 530 uid.encode("utf-8")).decode("utf-8")) 531 if not os.path.isdir(self.path): 532 raise Exception("No such file or directory: %s" % self.path)
533
534 - def icon(self, iconid):
535 """ 536 Returns an icon cached on disk. 537 @param iconid: ID of the icon 538 @type iconid: int 539 @return: the icon 540 @rtype: QPixmap 541 """ 542 return QPixmap(os.path.join(self.path, "icons", "icon_%s" % iconid))
543
544 545 -class CountryFlags:
546 """ 547 Offers an interface to get the client's country flags. CountryFlags is 548 also a context manager. 549 """ 550
551 - def __init__(self):
552 """ 553 Instantiates a new object. This will raise an exception, if the 554 Zipfile could not be located. 555 """ 556 self.path = os.path.join(ts3lib.getResourcesPath(), "gfx", 557 "countries.zip") 558 if not os.path.isfile(self.path): 559 raise Exception("Could not locate countries.zip")
560
561 - def open(self):
562 """ 563 Opens the Zipfile for reading. This must be called before any flag is 564 requested with flag. 565 """ 566 self.zip = ZipFile(self.path)
567
568 - def close(self):
569 """ 570 Closes the Zipfile. 571 """ 572 self.zip.close()
573
574 - def flag(self, code):
575 """ 576 Returns a QPixmap containing the flag of the given country code if 577 exist. 578 @param code: the country code 579 @type code: str 580 @returns: the flag 581 @rtype: QPixmap 582 """ 583 fname = "%s.png" % code.lower() 584 if fname in self.zip.namelist(): 585 with self.zip.open(fname) as f: 586 ret = QPixmap() 587 if ret.loadFromData(f.read()): 588 return ret 589 590 return QPixmap()
591
592 - def __enter__(self):
593 self.open() 594 return self
595
596 - def __exit__(self, type, value, traceback):
597 self.close()
598