1# COPYRIGHT (C) 2020-2021 Nicotine+ Team 2# COPYRIGHT (C) 2009-2011 Quinox <quinox@users.sf.net> 3# COPYRIGHT (C) 2007-2009 Daelstorm <daelstorm@gmail.com> 4# COPYRIGHT (C) 2003-2004 Hyriand <hyriand@thegraveyard.org> 5# COPYRIGHT (C) 2001-2003 Alexander Kanavin 6# 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19 20import socket 21import struct 22import zlib 23 24from pynicotine.config import config 25from pynicotine.logfacility import log 26from pynicotine.utils import debug 27 28""" This module contains message classes, that networking and UI thread 29exchange. Basically there are three types of messages: internal messages, 30server messages and p2p messages (between clients). """ 31 32 33INT_SIZE = struct.calcsize("<i") 34INT64_SIZE = struct.calcsize("<q") 35 36INT_UNPACK = struct.Struct("<i").unpack 37UINT_UNPACK = struct.Struct("<I").unpack 38UINT64_UNPACK = struct.Struct("<Q").unpack 39 40INT_PACK = struct.Struct("<i").pack 41UINT_PACK = struct.Struct("<I").pack 42UINT64_PACK = struct.Struct("<Q").pack 43 44SEARCH_TOKENS_ALLOWED = set() 45 46 47class InternalMessage: 48 pass 49 50 51class Conn(InternalMessage): 52 53 __slots__ = ("conn", "addr", "init") 54 55 def __init__(self, conn=None, addr=None, init=None): 56 self.conn = conn 57 self.addr = addr 58 self.init = init 59 60 61class InitServerConn(Conn): 62 """ NicotineCore sends this to make networking thread establish a server connection. 63 When a connection is established, networking thread returns an object of this type 64 to NicotineCore. """ 65 66 67class InitPeerConn(Conn): 68 """ NicotineCore sends this to make networking thread establish a peer connection. 69 When a connection is established, networking thread returns an object of this type 70 to NicotineCore. """ 71 72 73class IncConn(Conn): 74 """ Sent by networking thread to indicate an incoming connection.""" 75 76 77class ConnClose(InternalMessage): 78 """ Sent by networking thread to indicate a connection has been closed.""" 79 80 __slots__ = ("conn", "addr", "callback") 81 82 def __init__(self, conn=None, addr=None, callback=True): 83 self.conn = conn 84 self.addr = addr 85 self.callback = callback 86 87 88class ConnCloseIP(InternalMessage): 89 """ Sent by the main thread to the networking thread in order to close any connections 90 using a certain IP address. """ 91 92 def __init__(self, addr=None): 93 self.addr = addr 94 95 96class ConnectError(InternalMessage): 97 """ Sent when a socket exception occurs. It's up to UI thread to 98 handle this.""" 99 100 __slots__ = ("connobj", "err") 101 102 def __init__(self, connobj=None, err=None): 103 self.connobj = connobj 104 self.err = err 105 106 107class ConnectToPeerTimeout: 108 109 __slots__ = ("conn",) 110 111 def __init__(self, conn): 112 self.conn = conn 113 114 115class MessageProgress(InternalMessage): 116 """ Used to indicate progress of long transfers. """ 117 118 __slots__ = ("user", "msg_type", "position", "total") 119 120 def __init__(self, user=None, msg_type=None, position=None, total=None): 121 self.user = user 122 self.msg_type = msg_type 123 self.position = position 124 self.total = total 125 126 127class TransferTimeout: 128 129 __slots__ = ("transfer",) 130 131 def __init__(self, transfer): 132 self.transfer = transfer 133 134 135class CheckDownloadQueue(InternalMessage): 136 """ Sent from a timer to the main thread to indicate that stuck downloads 137 should be checked. """ 138 139 140class CheckUploadQueue(InternalMessage): 141 """ Sent from a timer to the main thread to indicate that the upload queue 142 should be checked. """ 143 144 145class DownloadFile(InternalMessage): 146 """ Sent by networking thread to indicate file transfer progress. 147 Sent by UI to pass the file object to write. """ 148 149 __slots__ = ("conn", "file") 150 151 def __init__(self, conn=None, file=None): 152 self.conn = conn 153 self.file = file 154 155 156class UploadFile(InternalMessage): 157 158 __slots__ = ("conn", "file", "size", "sentbytes", "offset") 159 160 def __init__(self, conn=None, file=None, size=None, sentbytes=0, offset=None): 161 self.conn = conn 162 self.file = file 163 self.size = size 164 self.sentbytes = sentbytes 165 self.offset = offset 166 167 168class FileError(InternalMessage): 169 """ Sent by networking thread to indicate that a file error occurred during 170 filetransfer. """ 171 172 __slots__ = ("conn", "file", "strerror") 173 174 def __init__(self, conn=None, file=None, strerror=None): 175 self.conn = conn 176 self.file = file 177 self.strerror = strerror 178 179 180class SetUploadLimit(InternalMessage): 181 """ Sent by the GUI thread to indicate changes in bandwidth shaping rules""" 182 183 def __init__(self, uselimit, limit, limitby): 184 self.uselimit = uselimit 185 self.limit = limit 186 self.limitby = limitby 187 188 189class SetDownloadLimit(InternalMessage): 190 """ Sent by the GUI thread to indicate changes in bandwidth shaping rules""" 191 192 def __init__(self, limit): 193 self.limit = limit 194 195 196class SetCurrentConnectionCount(InternalMessage): 197 """ Sent by networking thread to update the number of current 198 connections shown in the GUI. """ 199 200 __slots__ = ("msg",) 201 202 def __init__(self, msg): 203 self.msg = msg 204 205 206class SlskMessage: 207 """ This is a parent class for all protocol messages. """ 208 209 def get_object(self, message, obj_type, start=0, getsignedint=False, getunsignedlonglong=False): 210 """ Returns object of specified type, extracted from message (which is 211 a binary array). start is an offset.""" 212 213 try: 214 if obj_type is int: 215 if getsignedint: 216 # little-endian signed integer (4 bytes) 217 return INT_SIZE + start, INT_UNPACK(message[start:start + INT_SIZE])[0] 218 219 if getunsignedlonglong: 220 # little-endian unsigned long long (8 bytes) 221 try: 222 return INT64_SIZE + start, UINT64_UNPACK(message[start:start + INT64_SIZE])[0] 223 224 except Exception: 225 # Fall back to unsigned integer 226 pass 227 228 # little-endian unsigned integer (4 bytes) 229 return INT_SIZE + start, UINT_UNPACK(message[start:start + INT_SIZE])[0] 230 231 if obj_type is bytes: 232 length = UINT_UNPACK(message[start:start + INT_SIZE])[0] 233 content = message[start + INT_SIZE:start + length + INT_SIZE] 234 235 return length + INT_SIZE + start, content 236 237 if obj_type is str: 238 length = UINT_UNPACK(message[start:start + INT_SIZE])[0] 239 string = message[start + INT_SIZE:start + length + INT_SIZE] 240 241 try: 242 string = str(string, "utf-8") 243 except Exception: 244 # Older clients (Soulseek NS) 245 246 try: 247 string = str(string, "latin-1") 248 except Exception as error: 249 log.add("Error trying to decode string '%s': %s", (string, error)) 250 251 return length + INT_SIZE + start, string 252 253 return start, None 254 255 except struct.error as error: 256 log.add("%s %s trying to unpack %s at '%s' at %s/%s", 257 (self.__class__, error, obj_type, bytes(message[start:]), start, len(message))) 258 raise struct.error(error) 259 260 def pack_object(self, obj, signedint=False, unsignedlonglong=False, latin1=False): 261 """ Returns object (integer, long or string packed into a 262 binary array.""" 263 264 if isinstance(obj, int): 265 if signedint: 266 return INT_PACK(obj) 267 268 if unsignedlonglong: 269 return UINT64_PACK(obj) 270 271 return UINT_PACK(obj) 272 273 if isinstance(obj, bytes): 274 return UINT_PACK(len(obj)) + obj 275 276 if isinstance(obj, str): 277 if latin1: 278 try: 279 # Try to encode in latin-1 first for older clients (Soulseek NS) 280 encoded = bytes(obj, "latin-1") 281 except Exception: 282 encoded = bytes(obj, "utf-8", "replace") 283 else: 284 encoded = bytes(obj, "utf-8", "replace") 285 286 return UINT_PACK(len(encoded)) + encoded 287 288 log.add("Warning: unknown object type %(obj_type)s in message %(msg_type)s", 289 {'obj_type': type(obj), 'msg_type': self.__class__}) 290 291 return b"" 292 293 def make_network_message(self): 294 """ Returns binary array, that can be sent over the network""" 295 296 log.add("Empty message made, class %s", self.__class__) 297 return b"" 298 299 def parse_network_message(self, _message): 300 """ Extracts information from the message and sets up fields 301 in an object""" 302 303 log.add("Can't parse incoming messages, class %s", self.__class__) 304 305 def debug(self, message=None): 306 debug(type(self).__name__, self.__dict__, message.__repr__()) 307 308 309""" 310Server Messages 311""" 312 313 314class ServerMessage(SlskMessage): 315 """ This is a parent class for all server messages. """ 316 317 318class Login(ServerMessage): 319 """ Server code: 1 """ 320 """ We sent this to the server right after the connection has been 321 established. Server responds with the greeting message. """ 322 323 def __init__(self, username=None, passwd=None, version=None, minorversion=None): 324 self.username = username 325 self.passwd = passwd 326 self.version = version 327 self.minorversion = minorversion 328 self.success = None 329 self.reason = None 330 self.banner = None 331 self.ip_address = None 332 self.checksum = None 333 334 def make_network_message(self): 335 from hashlib import md5 336 337 msg = bytearray() 338 msg.extend(self.pack_object(self.username)) 339 msg.extend(self.pack_object(self.passwd)) 340 msg.extend(self.pack_object(self.version)) 341 342 payload = self.username + self.passwd 343 md5hash = md5(payload.encode()).hexdigest() 344 msg.extend(self.pack_object(md5hash)) 345 346 msg.extend(self.pack_object(self.minorversion)) 347 348 return msg 349 350 def parse_network_message(self, message): 351 pos, self.success = 1, message[0] 352 353 if not self.success: 354 pos, self.reason = self.get_object(message, str, pos) 355 else: 356 pos, self.banner = self.get_object(message, str, pos) 357 358 if not message[pos:]: 359 return 360 361 try: 362 pos, self.ip_address = pos + 4, socket.inet_ntoa(bytes(message[pos:pos + 4][::-1])) 363 364 except Exception as error: 365 log.add("Error unpacking IP address: %s", error) 366 367 # MD5 hexdigest of the password you sent 368 if message[pos:]: 369 pos, self.checksum = self.get_object(message, str, pos) 370 371 372class SetWaitPort(ServerMessage): 373 """ Server code: 2 """ 374 """ We send this to the server to indicate the port number that we 375 listen on (2234 by default). """ 376 377 def __init__(self, port=None): 378 self.port = port 379 380 def make_network_message(self): 381 return self.pack_object(self.port) 382 383 384class GetPeerAddress(ServerMessage): 385 """ Server code: 3 """ 386 """ We send this to the server to ask for a peer's address 387 (IP address and port), given the peer's username. """ 388 389 def __init__(self, user=None): 390 self.user = user 391 self.ip_address = None 392 self.port = None 393 394 def make_network_message(self): 395 return self.pack_object(self.user) 396 397 def parse_network_message(self, message): 398 pos, self.user = self.get_object(message, str) 399 pos, self.ip_address = pos + 4, socket.inet_ntoa(bytes(message[pos:pos + 4][::-1])) 400 pos, self.port = self.get_object(message, int, pos, 1) 401 402 403class AddUser(ServerMessage): 404 """ Server code: 5 """ 405 """ Used to be kept updated about a user's stats. When a user's 406 stats have changed, the server sends a GetUserStats response message 407 with the new user stats. """ 408 409 def __init__(self, user=None): 410 self.user = user 411 self.userexists = None 412 self.status = None 413 self.avgspeed = None 414 self.uploadnum = None 415 self.files = None 416 self.dirs = None 417 self.country = None 418 419 def make_network_message(self): 420 return self.pack_object(self.user) 421 422 def parse_network_message(self, message): 423 pos, self.user = self.get_object(message, str) 424 pos, self.userexists = pos + 1, message[pos] 425 426 if not message[pos:]: 427 # User does not exist 428 return 429 430 pos, self.status = self.get_object(message, int, pos) 431 pos, self.avgspeed = self.get_object(message, int, pos) 432 pos, self.uploadnum = self.get_object(message, int, pos, getunsignedlonglong=True) 433 434 pos, self.files = self.get_object(message, int, pos) 435 pos, self.dirs = self.get_object(message, int, pos) 436 437 if not message[pos:]: 438 # User is offline 439 return 440 441 pos, self.country = self.get_object(message, str, pos) 442 443 444class RemoveUser(ServerMessage): 445 """ Server code: 6 """ 446 """ Used when we no longer want to be kept updated about a 447 user's stats. """ 448 449 def __init__(self, user=None): 450 self.user = user 451 452 def make_network_message(self): 453 return self.pack_object(self.user) 454 455 456class GetUserStatus(ServerMessage): 457 """ Server code: 7 """ 458 """ The server tells us if a user has gone away or has returned. """ 459 460 def __init__(self, user=None): 461 self.user = user 462 self.status = None 463 self.privileged = None 464 465 def make_network_message(self): 466 return self.pack_object(self.user) 467 468 def parse_network_message(self, message): 469 pos, self.user = self.get_object(message, str) 470 pos, self.status = self.get_object(message, int, pos) 471 472 # Soulfind support 473 if message[pos:]: 474 pos, self.privileged = pos + 1, message[pos] 475 476 477class SayChatroom(ServerMessage): 478 """ Server code: 13 """ 479 """ Either we want to say something in the chatroom, or someone else did. """ 480 481 def __init__(self, room=None, msg=None): 482 self.room = room 483 self.msg = msg 484 self.user = None 485 486 def make_network_message(self): 487 return self.pack_object(self.room) + self.pack_object(self.msg) 488 489 def parse_network_message(self, message): 490 pos, self.room = self.get_object(message, str) 491 pos, self.user = self.get_object(message, str, pos) 492 pos, self.msg = self.get_object(message, str, pos) 493 494 495class UserData: 496 """ When we join a room, the server sends us a bunch of these for each user. """ 497 498 __slots__ = ("username", "status", "avgspeed", "uploadnum", "files", "dirs", "slotsfull", "country") 499 500 def __init__(self, username=None, status=None, avgspeed=None, uploadnum=None, files=None, dirs=None, 501 slotsfull=None, country=None): 502 self.username = username 503 self.status = status 504 self.avgspeed = avgspeed 505 self.uploadnum = uploadnum 506 self.files = files 507 self.dirs = dirs 508 self.slotsfull = slotsfull 509 self.country = country 510 511 512class JoinRoom(ServerMessage): 513 """ Server code: 14 """ 514 """ We send this message to the server when we want to join a room. If the 515 room doesn't exist, it is created. 516 517 Server responds with this message when we join a room. Contains users list 518 with data on everyone. """ 519 520 def __init__(self, room=None, private=None): 521 self.room = room 522 self.private = private 523 self.owner = None 524 self.users = [] 525 self.operators = [] 526 527 def make_network_message(self): 528 if self.private is not None: 529 return self.pack_object(self.room) + self.pack_object(self.private) 530 531 return self.pack_object(self.room) 532 533 def parse_network_message(self, message): 534 pos, self.room = self.get_object(message, str) 535 pos1 = pos 536 pos, self.users = self.get_users(message[pos:]) 537 pos = pos1 + pos 538 539 if message[pos:]: 540 self.private = True 541 pos, self.owner = self.get_object(message, str, pos) 542 543 if message[pos:] and self.private: 544 pos, numops = self.get_object(message, int, pos) 545 546 for _ in range(numops): 547 pos, operator = self.get_object(message, str, pos) 548 549 self.operators.append(operator) 550 551 def get_users(self, message): 552 pos, numusers = self.get_object(message, int) 553 554 users = [] 555 for i in range(numusers): 556 users.append(UserData()) 557 pos, users[i].username = self.get_object(message, str, pos) 558 559 pos, statuslen = self.get_object(message, int, pos) 560 for i in range(statuslen): 561 pos, users[i].status = self.get_object(message, int, pos) 562 563 pos, statslen = self.get_object(message, int, pos) 564 for i in range(statslen): 565 pos, users[i].avgspeed = self.get_object(message, int, pos) 566 pos, users[i].uploadnum = self.get_object(message, int, pos, getunsignedlonglong=True) 567 pos, users[i].files = self.get_object(message, int, pos) 568 pos, users[i].dirs = self.get_object(message, int, pos) 569 570 pos, slotslen = self.get_object(message, int, pos) 571 for i in range(slotslen): 572 pos, users[i].slotsfull = self.get_object(message, int, pos) 573 574 if message[pos:]: 575 pos, countrylen = self.get_object(message, int, pos) 576 for i in range(countrylen): 577 pos, users[i].country = self.get_object(message, str, pos) 578 579 return pos, users 580 581 582class LeaveRoom(ServerMessage): 583 """ Server code: 15 """ 584 """ We send this to the server when we want to leave a room. """ 585 586 def __init__(self, room=None): 587 self.room = room 588 589 def make_network_message(self): 590 return self.pack_object(self.room) 591 592 def parse_network_message(self, message): 593 _pos, self.room = self.get_object(message, str) 594 595 596class UserJoinedRoom(ServerMessage): 597 """ Server code: 16 """ 598 """ The server tells us someone has just joined a room we're in. """ 599 600 def __init__(self): 601 self.room = None 602 self.userdata = None 603 604 def parse_network_message(self, message): 605 pos, self.room = self.get_object(message, str) 606 607 self.userdata = UserData() 608 pos, self.userdata.username = self.get_object(message, str, pos) 609 pos, self.userdata.status = self.get_object(message, int, pos) 610 pos, self.userdata.avgspeed = self.get_object(message, int, pos) 611 pos, self.userdata.uploadnum = self.get_object(message, int, pos, getunsignedlonglong=True) 612 pos, self.userdata.files = self.get_object(message, int, pos) 613 pos, self.userdata.dirs = self.get_object(message, int, pos) 614 pos, self.userdata.slotsfull = self.get_object(message, int, pos) 615 616 # Soulfind support 617 if message[pos:]: 618 pos, self.userdata.country = self.get_object(message, str, pos) 619 620 621class UserLeftRoom(ServerMessage): 622 """ Server code: 17 """ 623 """ The server tells us someone has just left a room we're in. """ 624 625 def __init__(self): 626 self.room = None 627 self.username = None 628 629 def parse_network_message(self, message): 630 pos, self.room = self.get_object(message, str) 631 pos, self.username = self.get_object(message, str, pos) 632 633 634class ConnectToPeer(ServerMessage): 635 """ Server code: 18 """ 636 """ Either we ask server to tell someone else we want to establish a 637 connection with them, or server tells us someone wants to connect with us. 638 Used when the side that wants a connection can't establish it, and tries 639 to go the other way around (direct connection has failed). 640 """ 641 642 def __init__(self, token=None, user=None, conn_type=None): 643 self.token = token 644 self.user = user 645 self.conn_type = conn_type 646 self.ip_address = None 647 self.port = None 648 self.privileged = None 649 650 def make_network_message(self): 651 msg = bytearray() 652 msg.extend(self.pack_object(self.token)) 653 msg.extend(self.pack_object(self.user)) 654 msg.extend(self.pack_object(self.conn_type)) 655 656 return msg 657 658 def parse_network_message(self, message): 659 pos, self.user = self.get_object(message, str) 660 pos, self.conn_type = self.get_object(message, str, pos) 661 pos, self.ip_address = pos + 4, socket.inet_ntoa(bytes(message[pos:pos + 4][::-1])) 662 pos, self.port = self.get_object(message, int, pos, 1) 663 pos, self.token = self.get_object(message, int, pos) 664 665 # Soulfind support 666 if message[pos:]: 667 pos, self.privileged = pos + 1, message[pos] 668 669 670class MessageUser(ServerMessage): 671 """ Server code: 22 """ 672 """ Chat phrase sent to someone or received by us in private. """ 673 674 def __init__(self, user=None, msg=None): 675 self.user = user 676 self.msg = msg 677 self.msgid = None 678 self.timestamp = None 679 self.newmessage = None 680 681 def make_network_message(self): 682 msg = bytearray() 683 msg.extend(self.pack_object(self.user)) 684 msg.extend(self.pack_object(self.msg)) 685 686 return msg 687 688 def parse_network_message(self, message): 689 pos, self.msgid = self.get_object(message, int) 690 pos, self.timestamp = self.get_object(message, int, pos) 691 pos, self.user = self.get_object(message, str, pos) 692 pos, self.msg = self.get_object(message, str, pos) 693 694 if message[pos:]: 695 pos, self.newmessage = pos + 1, message[pos] 696 else: 697 self.newmessage = 1 698 699 700class MessageAcked(ServerMessage): 701 """ Server code: 23 """ 702 """ We send this to the server to confirm that we received a private message. 703 If we don't send it, the server will keep sending the chat phrase to us. 704 """ 705 706 def __init__(self, msgid=None): 707 self.msgid = msgid 708 709 def make_network_message(self): 710 return self.pack_object(self.msgid) 711 712 713class FileSearch(ServerMessage): 714 """ Server code: 26 """ 715 """ We send this to the server when we search for something. Alternatively, 716 the server sends this message outside the distributed network to tell us that 717 someone is searching for something, currently used for UserSearch and RoomSearch 718 requests. 719 720 The search id is a random number generated by the client and is used to track the 721 search results. 722 """ 723 724 def __init__(self, requestid=None, text=None): 725 self.searchid = requestid 726 self.searchterm = text 727 self.user = None 728 729 if text: 730 self.searchterm = ' '.join((x for x in text.split() if x != '-')) 731 732 def make_network_message(self): 733 msg = bytearray() 734 msg.extend(self.pack_object(self.searchid)) 735 msg.extend(self.pack_object(self.searchterm, latin1=True)) 736 737 return msg 738 739 def parse_network_message(self, message): 740 pos, self.user = self.get_object(message, str) 741 pos, self.searchid = self.get_object(message, int, pos) 742 pos, self.searchterm = self.get_object(message, str, pos) 743 744 745class SetStatus(ServerMessage): 746 """ Server code: 28 """ 747 """ We send our new status to the server. Status is a way to define whether 748 you're available or busy. 749 750 1 = Away 751 2 = Online 752 """ 753 754 def __init__(self, status=None): 755 self.status = status 756 757 def make_network_message(self): 758 return self.pack_object(self.status) 759 760 761class ServerPing(ServerMessage): 762 """ Server code: 32 """ 763 """ We test if the server responds. """ 764 """ DEPRECATED """ 765 766 def make_network_message(self): 767 return b"" 768 769 def parse_network_message(self, message): 770 # Empty message 771 pass 772 773 774class SendConnectToken(ServerMessage): 775 """ Server code: 33 """ 776 """ OBSOLETE, no longer used """ 777 778 def __init__(self, user, token): 779 self.user = user 780 self.token = token 781 782 def make_network_message(self): 783 return self.pack_object(self.user) + self.pack_object(self.token) 784 785 def parse_network_message(self, message): 786 pos, self.user = self.get_object(message, str) 787 pos, self.token = self.get_object(message, int, pos) 788 789 790class SendDownloadSpeed(ServerMessage): 791 """ Server code: 34 """ 792 """ We used to send this after a finished download to let the server update 793 the speed statistics for a user. """ 794 """ OBSOLETE, use SendUploadSpeed server message """ 795 796 def __init__(self, user=None, speed=None): 797 self.user = user 798 self.speed = speed 799 800 def make_network_message(self): 801 msg = bytearray() 802 msg.extend(self.pack_object(self.user)) 803 msg.extend(self.pack_object(self.speed)) 804 805 return msg 806 807 808class SharedFoldersFiles(ServerMessage): 809 """ Server code: 35 """ 810 """ We send this to server to indicate the number of folder and files 811 that we share. """ 812 813 def __init__(self, folders=None, files=None): 814 self.folders = folders 815 self.files = files 816 817 def make_network_message(self): 818 msg = bytearray() 819 msg.extend(self.pack_object(self.folders)) 820 msg.extend(self.pack_object(self.files)) 821 822 return msg 823 824 825class GetUserStats(ServerMessage): 826 """ Server code: 36 """ 827 """ The server sends this to indicate a change in a user's statistics, 828 if we've requested to watch the user in AddUser previously. A user's 829 stats can also be requested by sending a GetUserStats message to the 830 server, but AddUser should be used instead. """ 831 832 def __init__(self, user=None): 833 self.user = user 834 self.avgspeed = None 835 self.uploadnum = None 836 self.files = None 837 self.dirs = None 838 839 def make_network_message(self): 840 return self.pack_object(self.user) 841 842 def parse_network_message(self, message): 843 pos, self.user = self.get_object(message, str) 844 pos, self.avgspeed = self.get_object(message, int, pos) 845 pos, self.uploadnum = self.get_object(message, int, pos, getunsignedlonglong=True) 846 pos, self.files = self.get_object(message, int, pos) 847 pos, self.dirs = self.get_object(message, int, pos) 848 849 850class QueuedDownloads(ServerMessage): 851 """ Server code: 40 """ 852 """ The server sends this to indicate if someone has download slots available 853 or not. """ 854 """ OBSOLETE, no longer sent by the server """ 855 856 def __init__(self): 857 self.user = None 858 self.slotsfull = None 859 860 def parse_network_message(self, message): 861 pos, self.user = self.get_object(message, str) 862 pos, self.slotsfull = self.get_object(message, int, pos) 863 864 865class Relogged(ServerMessage): 866 """ Server code: 41 """ 867 """ The server sends this if someone else logged in under our nickname, 868 and then disconnects us. """ 869 870 def parse_network_message(self, message): 871 # Empty message 872 pass 873 874 875class UserSearch(ServerMessage): 876 """ Server code: 42 """ 877 """ We send this to the server when we search a specific user's shares. 878 The ticket/search id is a random number generated by the client and is 879 used to track the search results. """ 880 881 def __init__(self, user=None, requestid=None, text=None): 882 self.user = user 883 self.searchid = requestid 884 self.searchterm = text 885 886 def make_network_message(self): 887 msg = bytearray() 888 msg.extend(self.pack_object(self.user)) 889 msg.extend(self.pack_object(self.searchid)) 890 msg.extend(self.pack_object(self.searchterm, latin1=True)) 891 892 return msg 893 894 # Soulfind support, the official server sends a FileSearch message (code 26) instead 895 def parse_network_message(self, message): 896 pos, self.user = self.get_object(message, str) 897 pos, self.searchid = self.get_object(message, int, pos) 898 pos, self.searchterm = self.get_object(message, str, pos) 899 900 901class AddThingILike(ServerMessage): 902 """ Server code: 51 """ 903 """ We send this to the server when we add an item to our likes list. """ 904 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 905 906 def __init__(self, thing=None): 907 self.thing = thing 908 909 def make_network_message(self): 910 return self.pack_object(self.thing) 911 912 913class RemoveThingILike(ServerMessage): 914 """ Server code: 52 """ 915 """ We send this to the server when we remove an item from our likes list. """ 916 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 917 918 def __init__(self, thing=None): 919 self.thing = thing 920 921 def make_network_message(self): 922 return self.pack_object(self.thing) 923 924 925class Recommendations(ServerMessage): 926 """ Server code: 54 """ 927 """ The server sends us a list of personal recommendations and a number 928 for each. """ 929 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 930 931 def __init__(self): 932 self.recommendations = [] 933 self.unrecommendations = [] 934 935 def make_network_message(self): 936 return b"" 937 938 def parse_network_message(self, message): 939 self.unpack_recommendations(message) 940 941 def unpack_recommendations(self, message, pos=0): 942 pos, num = self.get_object(message, int, pos) 943 944 for _ in range(num): 945 pos, key = self.get_object(message, str, pos) 946 pos, rating = self.get_object(message, int, pos, getsignedint=True) 947 948 # The server also includes unrecommendations here for some reason, don't add them 949 if rating >= 0: 950 self.recommendations.append((key, rating)) 951 952 if not message[pos:]: 953 return 954 955 pos, num2 = self.get_object(message, int, pos) 956 957 for _ in range(num2): 958 pos, key = self.get_object(message, str, pos) 959 pos, rating = self.get_object(message, int, pos, getsignedint=True) 960 961 # The server also includes recommendations here for some reason, don't add them 962 if rating < 0: 963 self.unrecommendations.append((key, rating)) 964 965 966class GlobalRecommendations(Recommendations): 967 """ Server code: 56 """ 968 """ The server sends us a list of global recommendations and a number 969 for each. """ 970 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 971 972 973class UserInterests(ServerMessage): 974 """ Server code: 57 """ 975 """ We ask the server for a user's liked and hated interests. The server 976 responds with a list of interests. """ 977 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 978 979 def __init__(self, user=None): 980 self.user = user 981 self.likes = [] 982 self.hates = [] 983 984 def make_network_message(self): 985 return self.pack_object(self.user) 986 987 def parse_network_message(self, message): 988 pos, self.user = self.get_object(message, str) 989 pos, likesnum = self.get_object(message, int, pos) 990 991 for _ in range(likesnum): 992 pos, key = self.get_object(message, str, pos) 993 994 self.likes.append(key) 995 996 pos, hatesnum = self.get_object(message, int, pos) 997 998 for _ in range(hatesnum): 999 pos, key = self.get_object(message, str, pos) 1000 1001 self.hates.append(key) 1002 1003 1004class AdminCommand(ServerMessage): 1005 """ Server code: 58 """ 1006 """ We send this to the server to run an admin command (e.g. to ban or 1007 silence a user) if we have admin status on the server. """ 1008 """ OBSOLETE, no longer used since Soulseek stopped supporting third-party 1009 servers in 2002 """ 1010 1011 def __init__(self, command=None, command_args=None): 1012 self.command = command 1013 self.command_args = command_args 1014 1015 def make_network_message(self): 1016 msg = bytearray() 1017 msg.extend(self.pack_object(self.command)) 1018 msg.extend(self.pack_object(len(self.command_args))) 1019 1020 for i in self.command_args: 1021 msg.extend(self.pack_object(i)) 1022 1023 return msg 1024 1025 1026class PlaceInLineResponse(ServerMessage): 1027 """ Server code: 60 """ 1028 """ The server sends this to indicate change in place in queue while we're 1029 waiting for files from another peer. """ 1030 """ OBSOLETE, use PlaceInQueue peer message """ 1031 1032 def __init__(self, user=None, req=None, place=None): 1033 self.req = req 1034 self.user = user 1035 self.place = place 1036 1037 def make_network_message(self): 1038 msg = bytearray() 1039 msg.extend(self.pack_object(self.user)) 1040 msg.extend(self.pack_object(self.req)) 1041 msg.extend(self.pack_object(self.place)) 1042 1043 return msg 1044 1045 def parse_network_message(self, message): 1046 pos, self.user = self.get_object(message, str) 1047 pos, self.req = self.get_object(message, int, pos) 1048 pos, self.place = self.get_object(message, int, pos) 1049 1050 1051class RoomAdded(ServerMessage): 1052 """ Server code: 62 """ 1053 """ The server tells us a new room has been added. """ 1054 """ OBSOLETE, no longer sent by the server """ 1055 1056 def __init__(self): 1057 self.room = None 1058 1059 def parse_network_message(self, message): 1060 _pos, self.room = self.get_object(message, str) 1061 1062 1063class RoomRemoved(ServerMessage): 1064 """ Server code: 63 """ 1065 """ The server tells us a room has been removed. """ 1066 """ OBSOLETE, no longer sent by the server """ 1067 1068 def __init__(self): 1069 self.room = None 1070 1071 def parse_network_message(self, message): 1072 _pos, self.room = self.get_object(message, str) 1073 1074 1075class RoomList(ServerMessage): 1076 """ Server code: 64 """ 1077 """ The server tells us a list of rooms and the number of users in 1078 them. When connecting to the server, the server only sends us rooms 1079 with at least 5 users. A few select rooms are also excluded, such as 1080 nicotine and The Lobby. Requesting the room list yields a response 1081 containing the missing rooms. """ 1082 1083 def __init__(self): 1084 self.rooms = [] 1085 self.ownedprivaterooms = [] 1086 self.otherprivaterooms = [] 1087 1088 def make_network_message(self): 1089 return b"" 1090 1091 def parse_network_message(self, message): 1092 pos, numrooms = self.get_object(message, int) 1093 1094 for i in range(numrooms): 1095 pos, room = self.get_object(message, str, pos) 1096 1097 self.rooms.append([room, None]) 1098 1099 pos, numusers = self.get_object(message, int, pos) 1100 1101 for i in range(numusers): 1102 pos, usercount = self.get_object(message, int, pos) 1103 1104 self.rooms[i][1] = usercount 1105 1106 if not message[pos:]: 1107 return 1108 1109 pos, self.ownedprivaterooms = self.get_rooms(pos, message) 1110 pos, self.otherprivaterooms = self.get_rooms(pos, message) 1111 1112 def get_rooms(self, originalpos, message): 1113 try: 1114 pos, numrooms = self.get_object(message, int, originalpos) 1115 1116 rooms = [] 1117 for i in range(numrooms): 1118 pos, room = self.get_object(message, str, pos) 1119 1120 rooms.append([room, None]) 1121 1122 pos, numusers = self.get_object(message, int, pos) 1123 1124 for i in range(numusers): 1125 pos, usercount = self.get_object(message, int, pos) 1126 1127 rooms[i][1] = usercount 1128 1129 return (pos, rooms) 1130 1131 except Exception as error: 1132 log.add("Exception during parsing %(area)s: %(exception)s", {'area': 'RoomList', 'exception': error}) 1133 return (originalpos, []) 1134 1135 1136class ExactFileSearch(ServerMessage): 1137 """ Server code: 65 """ 1138 """ We send this to search for an exact file name and folder, 1139 to find other sources. """ 1140 """ OBSOLETE, no results even with official client """ 1141 1142 def __init__(self, req=None, file=None, folder=None, size=None, checksum=None): 1143 self.req = req 1144 self.file = file 1145 self.folder = folder 1146 self.size = size 1147 self.checksum = checksum 1148 self.user = None 1149 1150 def make_network_message(self): 1151 msg = bytearray() 1152 msg.extend(self.pack_object(self.req)) 1153 msg.extend(self.pack_object(self.file)) 1154 msg.extend(self.pack_object(self.folder)) 1155 msg.extend(self.pack_object(self.size, unsignedlonglong=True)) 1156 msg.extend(self.pack_object(self.checksum)) 1157 1158 return msg 1159 1160 def parse_network_message(self, message): 1161 pos, self.user = self.get_object(message, str) 1162 pos, self.req = self.get_object(message, int, pos) 1163 pos, self.file = self.get_object(message, str, pos) 1164 pos, self.folder = self.get_object(message, str, pos) 1165 pos, self.size = self.get_object(message, int, pos, getunsignedlonglong=True) 1166 pos, self.checksum = self.get_object(message, int, pos) 1167 1168 1169class AdminMessage(ServerMessage): 1170 """ Server code: 66 """ 1171 """ A global message from the server admin has arrived. """ 1172 1173 def __init__(self): 1174 self.msg = None 1175 1176 def parse_network_message(self, message): 1177 _pos, self.msg = self.get_object(message, str) 1178 1179 1180class GlobalUserList(ServerMessage): 1181 """ Server code: 67 """ 1182 """ We send this to get a global list of all users online. """ 1183 """ OBSOLETE, no longer used """ 1184 1185 def __init__(self): 1186 self.users = None 1187 1188 def make_network_message(self): 1189 return b"" 1190 1191 def parse_network_message(self, message): 1192 _pos, self.users = self.get_users(message) 1193 1194 def get_users(self, message): 1195 pos, numusers = self.get_object(message, int) 1196 1197 users = [] 1198 for i in range(numusers): 1199 users.append(UserData()) 1200 pos, users[i].username = self.get_object(message, str, pos) 1201 1202 pos, statuslen = self.get_object(message, int, pos) 1203 for i in range(statuslen): 1204 pos, users[i].status = self.get_object(message, int, pos) 1205 1206 pos, statslen = self.get_object(message, int, pos) 1207 for i in range(statslen): 1208 pos, users[i].avgspeed = self.get_object(message, int, pos) 1209 pos, users[i].uploadnum = self.get_object(message, int, pos, getunsignedlonglong=True) 1210 pos, users[i].files = self.get_object(message, int, pos) 1211 pos, users[i].dirs = self.get_object(message, int, pos) 1212 1213 pos, slotslen = self.get_object(message, int, pos) 1214 for i in range(slotslen): 1215 pos, users[i].slotsfull = self.get_object(message, int, pos) 1216 1217 if message[pos:]: 1218 pos, countrylen = self.get_object(message, int, pos) 1219 for i in range(countrylen): 1220 pos, users[i].country = self.get_object(message, str, pos) 1221 1222 return pos, users 1223 1224 1225class TunneledMessage(ServerMessage): 1226 """ Server code: 68 """ 1227 """ Server message for tunneling a chat message. """ 1228 """ OBSOLETE, no longer used """ 1229 1230 def __init__(self, user=None, req=None, code=None, msg=None): 1231 self.user = user 1232 self.req = req 1233 self.code = code 1234 self.msg = msg 1235 self.addr = None 1236 1237 def make_network_message(self): 1238 msg = bytearray() 1239 msg.extend(self.pack_object(self.user)) 1240 msg.extend(self.pack_object(self.req)) 1241 msg.extend(self.pack_object(self.code)) 1242 msg.extend(self.pack_object(self.msg)) 1243 1244 return msg 1245 1246 def parse_network_message(self, message): 1247 pos, self.user = self.get_object(message, str) 1248 pos, self.code = self.get_object(message, int, pos) 1249 pos, self.req = self.get_object(message, int, pos) 1250 1251 pos, ip_address = pos + 4, socket.inet_ntoa(bytes(message[pos:pos + 4][::-1])) 1252 pos, port = self.get_object(message, int, pos, 1) 1253 self.addr = (ip_address, port) 1254 1255 pos, self.msg = self.get_object(message, str, pos) 1256 1257 1258class PrivilegedUsers(ServerMessage): 1259 """ Server code: 69 """ 1260 """ The server sends us a list of privileged users, a.k.a. users who 1261 have donated. """ 1262 1263 def __init__(self): 1264 self.users = [] 1265 1266 def parse_network_message(self, message): 1267 pos, numusers = self.get_object(message, int) 1268 1269 for _ in range(numusers): 1270 pos, user = self.get_object(message, str, pos) 1271 1272 self.users.append(user) 1273 1274 1275class HaveNoParent(ServerMessage): 1276 """ Server code: 71 """ 1277 """ We inform the server if we have a distributed parent or not. 1278 If not, the server eventually sends us a PossibleParents message with a 1279 list of 10 possible parents to connect to. """ 1280 1281 def __init__(self, noparent=None): 1282 self.noparent = noparent 1283 1284 def make_network_message(self): 1285 return bytes([self.noparent]) 1286 1287 1288class SearchParent(ServerMessage): 1289 """ Server code: 73 """ 1290 """ We send the IP address of our parent to the server. """ 1291 """ DEPRECATED, sent by Soulseek NS but not SoulseekQt """ 1292 1293 def __init__(self, parentip=None): 1294 self.parentip = parentip 1295 1296 @staticmethod 1297 def strunreverse(string): 1298 strlist = string.split(".") 1299 strlist.reverse() 1300 return '.'.join(strlist) 1301 1302 def make_network_message(self): 1303 return self.pack_object(socket.inet_aton(self.strunreverse(self.parentip))) 1304 1305 1306class ParentMinSpeed(ServerMessage): 1307 """ Server code: 83 """ 1308 """ The server informs us about the minimum upload speed required to become 1309 a parent in the distributed network. """ 1310 1311 def __init__(self): 1312 self.speed = None 1313 1314 def parse_network_message(self, message): 1315 _pos, self.speed = self.get_object(message, int) 1316 1317 1318class ParentSpeedRatio(ServerMessage): 1319 """ Server code: 84 """ 1320 """ The server sends us a speed ratio determining the number of children we 1321 can have in the distributed network. The maximum number of children is our 1322 upload speed divided by the speed ratio. """ 1323 1324 def __init__(self): 1325 self.ratio = None 1326 1327 def parse_network_message(self, message): 1328 _pos, self.ratio = self.get_object(message, int) 1329 1330 1331class ParentInactivityTimeout(ServerMessage): 1332 """ Server code: 86 """ 1333 """ OBSOLETE, no longer sent by the server """ 1334 1335 def __init__(self): 1336 self.seconds = None 1337 1338 def parse_network_message(self, message): 1339 _pos, self.seconds = self.get_object(message, int) 1340 1341 1342class SearchInactivityTimeout(ServerMessage): 1343 """ Server code: 87 """ 1344 """ OBSOLETE, no longer sent by the server """ 1345 1346 def __init__(self): 1347 self.seconds = None 1348 1349 def parse_network_message(self, message): 1350 _pos, self.seconds = self.get_object(message, int) 1351 1352 1353class MinParentsInCache(ServerMessage): 1354 """ Server code: 88 """ 1355 """ OBSOLETE, no longer sent by the server """ 1356 1357 def __init__(self): 1358 self.num = None 1359 1360 def parse_network_message(self, message): 1361 _pos, self.num = self.get_object(message, int) 1362 1363 1364class DistribAliveInterval(ServerMessage): 1365 """ Server code: 90 """ 1366 """ OBSOLETE, no longer sent by the server """ 1367 1368 def __init__(self): 1369 self.seconds = None 1370 1371 def parse_network_message(self, message): 1372 _pos, self.seconds = self.get_object(message, int) 1373 1374 1375class AddToPrivileged(ServerMessage): 1376 """ Server code: 91 """ 1377 """ The server sends us the username of a new privileged user, which we 1378 add to our list of global privileged users. """ 1379 """ OBSOLETE, no longer sent by the server """ 1380 1381 def __init__(self): 1382 self.user = None 1383 1384 def parse_network_message(self, message): 1385 _pos, self.user = self.get_object(message, str) 1386 1387 1388class CheckPrivileges(ServerMessage): 1389 """ Server code: 92 """ 1390 """ We ask the server how much time we have left of our privileges. 1391 The server responds with the remaining time, in seconds. """ 1392 1393 def __init__(self): 1394 self.seconds = None 1395 1396 def make_network_message(self): 1397 return b"" 1398 1399 def parse_network_message(self, message): 1400 _pos, self.seconds = self.get_object(message, int) 1401 1402 1403class EmbeddedMessage(ServerMessage): 1404 """ Server code: 93 """ 1405 """ The server sends us an embedded distributed message. The only type 1406 of distributed message sent at present is DistribSearch (distributed code 3). 1407 If we receive such a message, we are a branch root in the distributed network, 1408 and we distribute the embedded message (not the unpacked distributed message) 1409 to our child peers. """ 1410 1411 __slots__ = ("distrib_code", "distrib_message") 1412 1413 def __init__(self): 1414 self.distrib_code = None 1415 self.distrib_message = None 1416 1417 def parse_network_message(self, message): 1418 self.distrib_code = message[0] 1419 self.distrib_message = message[1:] 1420 1421 1422class AcceptChildren(ServerMessage): 1423 """ Server code: 100 """ 1424 """ We tell the server if we want to accept child nodes. """ 1425 1426 def __init__(self, enabled=None): 1427 self.enabled = enabled 1428 1429 def make_network_message(self): 1430 return bytes([self.enabled]) 1431 1432 1433class PossibleParents(ServerMessage): 1434 """ Server code: 102 """ 1435 """ The server send us a list of 10 possible distributed parents to connect to. 1436 This message is sent to us at regular intervals until we tell the server we don't 1437 need more possible parents, through a HaveNoParent message. """ 1438 1439 def __init__(self): 1440 self.list = {} 1441 1442 def parse_network_message(self, message): 1443 pos, num = self.get_object(message, int) 1444 1445 for _ in range(num): 1446 pos, username = self.get_object(message, str, pos) 1447 pos, ip_address = pos + 4, socket.inet_ntoa(bytes(message[pos:pos + 4][::-1])) 1448 pos, port = self.get_object(message, int, pos) 1449 1450 self.list[username] = (ip_address, port) 1451 1452 1453class WishlistSearch(FileSearch): 1454 """ Server code: 103 """ 1455 1456 1457class WishlistInterval(ServerMessage): 1458 """ Server code: 104 """ 1459 1460 def __init__(self): 1461 self.seconds = None 1462 1463 def parse_network_message(self, message): 1464 _pos, self.seconds = self.get_object(message, int) 1465 1466 1467class SimilarUsers(ServerMessage): 1468 """ Server code: 110 """ 1469 """ The server sends us a list of similar users related to our interests. """ 1470 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1471 1472 def __init__(self): 1473 self.users = [] 1474 1475 def make_network_message(self): 1476 return b"" 1477 1478 def parse_network_message(self, message): 1479 pos, num = self.get_object(message, int) 1480 1481 for _ in range(num): 1482 pos, user = self.get_object(message, str, pos) 1483 pos, _rating = self.get_object(message, int, pos) 1484 1485 self.users.append(user) 1486 1487 1488class ItemRecommendations(Recommendations): 1489 """ Server code: 111 """ 1490 """ The server sends us a list of recommendations related to a specific 1491 item, which is usually present in the like/dislike list or an existing 1492 recommendation list. """ 1493 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1494 1495 def __init__(self, thing=None): 1496 super().__init__() 1497 self.thing = thing 1498 1499 def make_network_message(self): 1500 return self.pack_object(self.thing) 1501 1502 def parse_network_message(self, message): 1503 pos, self.thing = self.get_object(message, str) 1504 self.unpack_recommendations(message, pos) 1505 1506 1507class ItemSimilarUsers(ServerMessage): 1508 """ Server code: 112 """ 1509 """ The server sends us a list of similar users related to a specific item, 1510 which is usually present in the like/dislike list or recommendation list. """ 1511 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1512 1513 def __init__(self, thing=None): 1514 self.thing = thing 1515 self.users = [] 1516 1517 def make_network_message(self): 1518 return self.pack_object(self.thing) 1519 1520 def parse_network_message(self, message): 1521 pos, self.thing = self.get_object(message, str) 1522 pos, num = self.get_object(message, int, pos) 1523 1524 for _ in range(num): 1525 pos, user = self.get_object(message, str, pos) 1526 self.users.append(user) 1527 1528 1529class RoomTickerState(ServerMessage): 1530 """ Server code: 113 """ 1531 """ The server returns a list of tickers in a chat room. 1532 1533 Tickers are customizable, user-specific messages that appear on 1534 chat room walls. """ 1535 1536 def __init__(self): 1537 self.room = None 1538 self.user = None 1539 self.msgs = [] 1540 1541 def parse_network_message(self, message): 1542 pos, self.room = self.get_object(message, str) 1543 pos, num = self.get_object(message, int, pos) 1544 1545 for _ in range(num): 1546 pos, user = self.get_object(message, str, pos) 1547 pos, msg = self.get_object(message, str, pos) 1548 1549 self.msgs.append((user, msg)) 1550 1551 1552class RoomTickerAdd(ServerMessage): 1553 """ Server code: 114 """ 1554 """ The server sends us a new ticker that was added to a chat room. 1555 1556 Tickers are customizable, user-specific messages that appear on 1557 chat room walls. """ 1558 1559 def __init__(self): 1560 self.room = None 1561 self.user = None 1562 self.msg = None 1563 1564 def parse_network_message(self, message): 1565 pos, self.room = self.get_object(message, str) 1566 pos, self.user = self.get_object(message, str, pos) 1567 pos, self.msg = self.get_object(message, str, pos) 1568 1569 1570class RoomTickerRemove(ServerMessage): 1571 """ Server code: 115 """ 1572 """ The server informs us that a ticker was removed from a chat room. 1573 1574 Tickers are customizable, user-specific messages that appear on 1575 chat room walls. """ 1576 1577 def __init__(self): 1578 self.room = None 1579 self.user = None 1580 1581 def parse_network_message(self, message): 1582 pos, self.room = self.get_object(message, str) 1583 pos, self.user = self.get_object(message, str, pos) 1584 1585 1586class RoomTickerSet(ServerMessage): 1587 """ Server code: 116 """ 1588 """ We send this to the server when we change our own ticker in 1589 a chat room. Sending an empty ticker string removes any existing 1590 ticker in the room. 1591 1592 Tickers are customizable, user-specific messages that appear on 1593 chat room walls. """ 1594 1595 def __init__(self, room=None, msg=""): 1596 self.room = room 1597 self.msg = msg 1598 1599 def make_network_message(self): 1600 msg = bytearray() 1601 msg.extend(self.pack_object(self.room)) 1602 msg.extend(self.pack_object(self.msg)) 1603 1604 return msg 1605 1606 1607class AddThingIHate(AddThingILike): 1608 """ Server code: 117 """ 1609 """ We send this to the server when we add an item to our hate list. """ 1610 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1611 1612 1613class RemoveThingIHate(RemoveThingILike): 1614 """ Server code: 118 """ 1615 """ We send this to the server when we remove an item from our hate list. """ 1616 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1617 1618 1619class RoomSearch(ServerMessage): 1620 """ Server code: 120 """ 1621 """ We send this to the server to search files shared by users who have joined 1622 a specific chat room. The ticket/search id is a random number generated by the 1623 client and is used to track the search results. """ 1624 1625 def __init__(self, room=None, requestid=None, text=""): 1626 self.room = room 1627 self.searchid = requestid 1628 self.searchterm = ' '.join([x for x in text.split() if x != '-']) 1629 self.user = None 1630 1631 def make_network_message(self): 1632 msg = bytearray() 1633 msg.extend(self.pack_object(self.room)) 1634 msg.extend(self.pack_object(self.searchid)) 1635 msg.extend(self.pack_object(self.searchterm, latin1=True)) 1636 1637 return msg 1638 1639 # Soulfind support, the official server sends a FileSearch message (code 26) instead 1640 def parse_network_message(self, message): 1641 pos, self.user = self.get_object(message, str) 1642 pos, self.searchid = self.get_object(message, int, pos) 1643 pos, self.searchterm = self.get_object(message, str, pos) 1644 1645 1646class SendUploadSpeed(ServerMessage): 1647 """ Server code: 121 """ 1648 """ We send this after a finished upload to let the server update the speed 1649 statistics for ourselves. """ 1650 1651 def __init__(self, speed=None): 1652 self.speed = speed 1653 1654 def make_network_message(self): 1655 return self.pack_object(self.speed) 1656 1657 1658class UserPrivileged(ServerMessage): 1659 """ Server code: 122 """ 1660 """ We ask the server whether a user is privileged or not. """ 1661 """ DEPRECATED, use AddUser and GetUserStatus server messages """ 1662 1663 def __init__(self, user=None): 1664 self.user = user 1665 self.privileged = None 1666 1667 def make_network_message(self): 1668 return self.pack_object(self.user) 1669 1670 def parse_network_message(self, message): 1671 pos, self.user = self.get_object(message, str, 0) 1672 pos, self.privileged = pos + 1, bool(message[pos]) 1673 1674 1675class GivePrivileges(ServerMessage): 1676 """ Server code: 123 """ 1677 """ We give (part of) our privileges, specified in days, to another 1678 user on the network. """ 1679 1680 def __init__(self, user=None, days=None): 1681 self.user = user 1682 self.days = days 1683 1684 def make_network_message(self): 1685 msg = bytearray() 1686 msg.extend(self.pack_object(self.user)) 1687 msg.extend(self.pack_object(self.days)) 1688 1689 return msg 1690 1691 1692class NotifyPrivileges(ServerMessage): 1693 """ Server code: 124 """ 1694 """ DEPRECATED, no longer used """ 1695 1696 def __init__(self, token=None, user=None): 1697 self.token = token 1698 self.user = user 1699 1700 def parse_network_message(self, message): 1701 pos, self.token = self.get_object(message, int) 1702 pos, self.user = self.get_object(message, str, pos) 1703 1704 def make_network_message(self): 1705 msg = bytearray() 1706 msg.extend(self.pack_object(self.token)) 1707 msg.extend(self.pack_object(self.user)) 1708 1709 return msg 1710 1711 1712class AckNotifyPrivileges(ServerMessage): 1713 """ Server code: 125 """ 1714 """ DEPRECATED, no longer used """ 1715 1716 def __init__(self, token=None): 1717 self.token = token 1718 1719 def parse_network_message(self, message): 1720 _pos, self.token = self.get_object(message, int) 1721 1722 def make_network_message(self): 1723 return self.pack_object(self.token) 1724 1725 1726class BranchLevel(ServerMessage): 1727 """ Server code: 126 """ 1728 """ We tell the server what our position is in our branch (xth generation) 1729 on the distributed network. """ 1730 1731 def __init__(self, value=None): 1732 self.value = value 1733 1734 def make_network_message(self): 1735 return self.pack_object(self.value) 1736 1737 1738class BranchRoot(ServerMessage): 1739 """ Server code: 127 """ 1740 """ We tell the server the username of the root of the branch we’re in on 1741 the distributed network. """ 1742 1743 def __init__(self, user=None): 1744 self.user = user 1745 1746 def make_network_message(self): 1747 return self.pack_object(self.user) 1748 1749 1750class ChildDepth(ServerMessage): 1751 """ Server code: 129 """ 1752 """ We tell the server the maximum number of generation of children we 1753 have on the distributed network. """ 1754 1755 def __init__(self, value=None): 1756 self.value = value 1757 1758 def make_network_message(self): 1759 return self.pack_object(self.value) 1760 1761 1762class ResetDistributed(ServerMessage): 1763 """ Server code: 130 """ 1764 """ The server asks us to reset our distributed parent and children. """ 1765 1766 def parse_network_message(self, message): 1767 # Empty message 1768 pass 1769 1770 1771class PrivateRoomUsers(ServerMessage): 1772 """ Server code: 133 """ 1773 """ The server sends us a list of room users that we can alter 1774 (add operator abilities / dismember). """ 1775 1776 def __init__(self): 1777 self.room = None 1778 self.numusers = None 1779 self.users = [] 1780 1781 def parse_network_message(self, message): 1782 pos, self.room = self.get_object(message, str) 1783 pos, self.numusers = self.get_object(message, int, pos) 1784 1785 for _ in range(self.numusers): 1786 pos, user = self.get_object(message, str, pos) 1787 1788 self.users.append(user) 1789 1790 1791class PrivateRoomAddUser(ServerMessage): 1792 """ Server code: 134 """ 1793 """ We send this to inform the server that we've added a user to a private room. """ 1794 1795 def __init__(self, room=None, user=None): 1796 self.room = room 1797 self.user = user 1798 1799 def make_network_message(self): 1800 msg = bytearray() 1801 msg.extend(self.pack_object(self.room)) 1802 msg.extend(self.pack_object(self.user)) 1803 1804 return msg 1805 1806 def parse_network_message(self, message): 1807 pos, self.room = self.get_object(message, str) 1808 pos, self.user = self.get_object(message, str, pos) 1809 1810 1811class PrivateRoomRemoveUser(PrivateRoomAddUser): 1812 """ Server code: 135 """ 1813 """ We send this to inform the server that we've removed a user from a private room. """ 1814 1815 1816class PrivateRoomDismember(ServerMessage): 1817 """ Server code: 136 """ 1818 """ We send this to the server to remove our own membership of a private room. """ 1819 1820 def __init__(self, room=None): 1821 self.room = room 1822 1823 def make_network_message(self): 1824 return self.pack_object(self.room) 1825 1826 1827class PrivateRoomDisown(ServerMessage): 1828 """ Server code: 137 """ 1829 """ We send this to the server to stop owning a private room. """ 1830 1831 def __init__(self, room=None): 1832 self.room = room 1833 1834 def make_network_message(self): 1835 return self.pack_object(self.room) 1836 1837 1838class PrivateRoomSomething(ServerMessage): 1839 """ Server code: 138 """ 1840 """ OBSOLETE, no longer used """ 1841 1842 def __init__(self, room=None): 1843 self.room = room 1844 1845 def make_network_message(self): 1846 return self.pack_object(self.room) 1847 1848 def parse_network_message(self, message): 1849 _pos, self.room = self.get_object(message, str) 1850 1851 1852class PrivateRoomAdded(ServerMessage): 1853 """ Server code: 139 """ 1854 """ The server sends us this message when we are added to a private room. """ 1855 1856 def __init__(self, room=None): 1857 self.room = room 1858 1859 def parse_network_message(self, message): 1860 _pos, self.room = self.get_object(message, str) 1861 1862 1863class PrivateRoomRemoved(PrivateRoomAdded): 1864 """ Server code: 140 """ 1865 """ The server sends us this message when we are removed from a private room. """ 1866 1867 1868class PrivateRoomToggle(ServerMessage): 1869 """ Server code: 141 """ 1870 """ We send this when we want to enable or disable invitations to private rooms. """ 1871 1872 def __init__(self, enabled=None): 1873 self.enabled = None if enabled is None else int(enabled) 1874 1875 def make_network_message(self): 1876 return bytes([self.enabled]) 1877 1878 def parse_network_message(self, message): 1879 # When this is received, we store it in the config, and disable the appropriate menu item 1880 self.enabled = bool(int(message[0])) 1881 1882 1883class ChangePassword(ServerMessage): 1884 """ Server code: 142 """ 1885 """ We send this to the server to change our password. We receive a 1886 response if our password changes. """ 1887 1888 def __init__(self, password=None): 1889 self.password = password 1890 1891 def make_network_message(self): 1892 return self.pack_object(self.password) 1893 1894 def parse_network_message(self, message): 1895 _pos, self.password = self.get_object(message, str) 1896 1897 1898class PrivateRoomAddOperator(PrivateRoomAddUser): 1899 """ Server code: 143 """ 1900 """ We send this to the server to add private room operator abilities to a user. """ 1901 1902 1903class PrivateRoomRemoveOperator(PrivateRoomAddUser): 1904 """ Server code: 144 """ 1905 """ We send this to the server to remove private room operator abilities from a user. """ 1906 1907 1908class PrivateRoomOperatorAdded(ServerMessage): 1909 """ Server code: 145 """ 1910 """ The server send us this message when we're given operator abilities 1911 in a private room. """ 1912 1913 def __init__(self, room=None): 1914 self.room = room 1915 1916 def parse_network_message(self, message): 1917 _pos, self.room = self.get_object(message, str) 1918 1919 1920class PrivateRoomOperatorRemoved(ServerMessage): 1921 """ Server code: 146 """ 1922 """ The server send us this message when our operator abilities are removed 1923 in a private room. """ 1924 1925 def __init__(self, room=None): 1926 self.room = room 1927 1928 def make_network_message(self): 1929 return self.pack_object(self.room) 1930 1931 def parse_network_message(self, message): 1932 _pos, self.room = self.get_object(message, str) 1933 1934 1935class PrivateRoomOwned(ServerMessage): 1936 """ Server code: 148 """ 1937 """ The server sends us a list of operators in a specific room, that we can 1938 remove operator abilities from. """ 1939 1940 def __init__(self): 1941 self.room = None 1942 self.number = None 1943 self.operators = [] 1944 1945 def parse_network_message(self, message): 1946 pos, self.room = self.get_object(message, str) 1947 pos, self.number = self.get_object(message, int, pos) 1948 1949 for _ in range(self.number): 1950 pos, user = self.get_object(message, str, pos) 1951 1952 self.operators.append(user) 1953 1954 1955class MessageUsers(ServerMessage): 1956 """ Server code: 149 """ 1957 """ Sends a broadcast private message to the given list of users. """ 1958 1959 def __init__(self, users=None, msg=None): 1960 self.users = users 1961 self.msg = msg 1962 1963 def make_network_message(self): 1964 msg = bytearray() 1965 msg.extend(self.pack_object(len(self.users))) 1966 1967 for user in self.users: 1968 msg.extend(self.pack_object(user)) 1969 1970 msg.extend(self.pack_object(self.msg)) 1971 1972 1973class JoinPublicRoom(ServerMessage): 1974 """ Server code: 150 """ 1975 """ We ask the server to send us messages from all public rooms, also 1976 known as public chat. """ 1977 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1978 1979 def make_network_message(self): 1980 return b"" 1981 1982 1983class LeavePublicRoom(ServerMessage): 1984 """ Server code: 151 """ 1985 """ We ask the server to stop sending us messages from all public rooms, 1986 also known as public chat. """ 1987 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1988 1989 def make_network_message(self): 1990 return b"" 1991 1992 1993class PublicRoomMessage(ServerMessage): 1994 """ Server code: 152 """ 1995 """ The server sends this when a new message has been written in a public 1996 room (every single line written in every public room). """ 1997 """ DEPRECATED, used in Soulseek NS but not SoulseekQt """ 1998 1999 def __init__(self): 2000 self.room = None 2001 self.user = None 2002 self.msg = None 2003 2004 def parse_network_message(self, message): 2005 pos, self.room = self.get_object(message, str) 2006 pos, self.user = self.get_object(message, str, pos) 2007 pos, self.msg = self.get_object(message, str, pos) 2008 2009 2010class RelatedSearch(ServerMessage): 2011 """ Server code: 153 """ 2012 """ The server returns a list of related search terms for a search query. """ 2013 """ OBSOLETE, server sends empty list as of 2018 """ 2014 2015 def __init__(self, query=None): 2016 self.query = query 2017 self.terms = [] 2018 2019 def make_network_message(self): 2020 return self.pack_object(self.query) 2021 2022 def parse_network_message(self, message): 2023 pos, self.query = self.get_object(message, str) 2024 pos, num = self.get_object(message, int, pos) 2025 2026 for _ in range(num): 2027 pos, term = self.get_object(message, str, pos) 2028 pos, score = self.get_object(message, int, pos) 2029 2030 self.terms.append((term, score)) 2031 2032 2033class CantConnectToPeer(ServerMessage): 2034 """ Server code: 1001 """ 2035 """ We send this to say we can't connect to peer after it has asked us 2036 to connect. We receive this if we asked peer to connect and it can't do 2037 this. This message means a connection can't be established either way. """ 2038 2039 def __init__(self, token=None, user=None): 2040 self.token = token 2041 self.user = user 2042 2043 def make_network_message(self): 2044 msg = bytearray() 2045 msg.extend(self.pack_object(self.token)) 2046 msg.extend(self.pack_object(self.user)) 2047 2048 return msg 2049 2050 def parse_network_message(self, message): 2051 _pos, self.token = self.get_object(message, int) 2052 2053 2054class CantCreateRoom(ServerMessage): 2055 """ Server code: 1003 """ 2056 """ Server tells us a new room cannot be created. This message only seems 2057 to be sent if you try to create a room with the same name as an existing 2058 private room. In other cases, such as using a room name with leading or 2059 trailing spaces, only a private message containing an error message is sent. """ 2060 2061 def __init__(self): 2062 self.room = None 2063 2064 def parse_network_message(self, message): 2065 _pos, self.room = self.get_object(message, str) 2066 2067 2068""" 2069Peer Init Messages 2070""" 2071 2072 2073class PeerInitMessage(SlskMessage): 2074 pass 2075 2076 2077class PierceFireWall(PeerInitMessage): 2078 """ Peer init code: 0 """ 2079 """ This is the very first message sent by the peer that established a 2080 connection, if it has been asked by the other peer to do so. The token 2081 is taken from the ConnectToPeer server message. """ 2082 2083 def __init__(self, conn=None, token=None): 2084 self.conn = conn 2085 self.token = token 2086 2087 def make_network_message(self): 2088 return self.pack_object(self.token) 2089 2090 def parse_network_message(self, message): 2091 if message: 2092 # A token is not guaranteed to be sent 2093 _pos, self.token = self.get_object(message, int) 2094 2095 2096class PeerInit(PeerInitMessage): 2097 """ Peer init code: 1 """ 2098 """ This message is sent by the peer that initiated a connection, 2099 not necessarily a peer that actually established it. Token apparently 2100 can be anything. Type is 'P' if it's anything but filetransfer, 2101 'F' otherwise. """ 2102 2103 def __init__(self, conn=None, init_user=None, target_user=None, conn_type=None, token=None): 2104 self.conn = conn 2105 self.init_user = init_user # username of peer who initiated the message 2106 self.target_user = target_user # username of peer we're connected to 2107 self.conn_type = conn_type 2108 self.token = token 2109 2110 def make_network_message(self): 2111 msg = bytearray() 2112 msg.extend(self.pack_object(self.init_user)) 2113 msg.extend(self.pack_object(self.conn_type)) 2114 msg.extend(self.pack_object(self.token)) 2115 2116 return msg 2117 2118 def parse_network_message(self, message): 2119 pos, self.init_user = self.get_object(message, str) 2120 pos, self.conn_type = self.get_object(message, str, pos) 2121 2122 if message[pos:]: 2123 # A token is not guaranteed to be sent 2124 pos, self.token = self.get_object(message, int, pos) 2125 2126 if self.target_user is None: 2127 # The user we're connecting to initiated the connection. Set them as target user. 2128 self.target_user = self.init_user 2129 2130 2131""" 2132Peer Messages 2133""" 2134 2135 2136class PeerMessage(SlskMessage): 2137 2138 def parse_file_size(self, message, pos): 2139 2140 if message[pos + INT64_SIZE - 1] == 255: 2141 # Soulseek NS bug: >2 GiB files show up as ~16 EiB when unpacking the size 2142 # as uint64 (8 bytes), due to the first 4 bytes containing the size, and the 2143 # last 4 bytes containing garbage (a value of 4294967295 bytes, integer limit). 2144 # Only unpack the first 4 bytes to work around this issue. 2145 2146 pos, size = self.get_object(message, int, pos) 2147 pos, _garbage = self.get_object(message, int, pos) 2148 2149 else: 2150 # Everything looks fine, parse size as usual 2151 pos, size = self.get_object(message, int, pos, getunsignedlonglong=True) 2152 2153 return pos, size 2154 2155 2156class GetSharedFileList(PeerMessage): 2157 """ Peer code: 4 """ 2158 """ We send this to a peer to ask for a list of shared files. """ 2159 2160 def __init__(self, conn): 2161 self.conn = conn 2162 2163 def make_network_message(self): 2164 return b"" 2165 2166 def parse_network_message(self, message): 2167 # Empty message 2168 pass 2169 2170 2171class SharedFileList(PeerMessage): 2172 """ Peer code: 5 """ 2173 """ A peer responds with a list of shared files when we've sent 2174 a GetSharedFileList. """ 2175 2176 def __init__(self, conn, shares=None): 2177 self.conn = conn 2178 self.list = shares 2179 self.unknown = 0 2180 self.privatelist = [] 2181 self.built = None 2182 2183 def parse_network_message(self, message): 2184 try: 2185 message = memoryview(zlib.decompress(message)) 2186 self._parse_network_message(message) 2187 2188 except Exception as error: 2189 log.add("Exception during parsing %(area)s: %(exception)s", 2190 {'area': 'SharedFileList', 'exception': error}) 2191 self.list = [] 2192 self.privatelist = [] 2193 2194 def _parse_result_list(self, message, pos=0): 2195 pos, ndir = self.get_object(message, int, pos) 2196 2197 shares = [] 2198 for _ in range(ndir): 2199 pos, directory = self.get_object(message, str, pos) 2200 directory = directory.replace('/', '\\') 2201 pos, nfiles = self.get_object(message, int, pos) 2202 2203 files = [] 2204 2205 for _ in range(nfiles): 2206 pos, code = pos + 1, message[pos] 2207 pos, name = self.get_object(message, str, pos) 2208 pos, size = self.parse_file_size(message, pos) 2209 pos, ext = self.get_object(message, str, pos) 2210 pos, numattr = self.get_object(message, int, pos) 2211 2212 attrs = [] 2213 2214 for _ in range(numattr): 2215 pos, _attrnum = self.get_object(message, int, pos) 2216 pos, attr = self.get_object(message, int, pos) 2217 attrs.append(attr) 2218 2219 files.append((code, name, size, ext, attrs)) 2220 2221 shares.append((directory, files)) 2222 2223 return pos, shares 2224 2225 def _parse_network_message(self, message): 2226 pos, self.list = self._parse_result_list(message) 2227 2228 if message[pos:]: 2229 pos, self.unknown = self.get_object(message, int, pos) 2230 2231 if message[pos:]: 2232 pos, self.privatelist = self._parse_result_list(message, pos) 2233 2234 def make_network_message(self): 2235 # Elaborate hack to save CPU 2236 # Store packed message contents in self.built, and use instead of repacking it 2237 if self.built is not None: 2238 return self.built 2239 2240 msg = bytearray() 2241 msg_list = bytearray() 2242 2243 if self.list is None: 2244 # DB is closed 2245 msg_list = self.pack_object(0) 2246 2247 else: 2248 try: 2249 try: 2250 msg_list.extend(self.pack_object(len(self.list))) 2251 2252 except TypeError: 2253 msg_list.extend(self.pack_object(len(list(self.list)))) 2254 2255 for key in self.list: 2256 msg_list.extend(self.pack_object(key.replace('/', '\\'))) 2257 msg_list.extend(self.list[key]) 2258 2259 except Exception as error: 2260 msg_list = self.pack_object(0) 2261 log.add(_("Unable to read shares database. Please rescan your shares. Error: %s"), error) 2262 2263 msg.extend(msg_list) 2264 2265 # Unknown purpose, but official clients always send a value of 0 2266 msg.extend(self.pack_object(self.unknown)) 2267 2268 self.built = zlib.compress(msg) 2269 return self.built 2270 2271 2272class FileSearchRequest(PeerMessage): 2273 """ Peer code: 8 """ 2274 """ We send this to the peer when we search for a file. 2275 Alternatively, the peer sends this to tell us it is 2276 searching for a file. """ 2277 """ OBSOLETE, use UserSearch server message """ 2278 2279 def __init__(self, conn, requestid=None, text=None): 2280 self.conn = conn 2281 self.requestid = requestid 2282 self.text = text 2283 self.searchid = None 2284 self.searchterm = None 2285 2286 def make_network_message(self): 2287 msg = bytearray() 2288 msg.extend(self.pack_object(self.requestid)) 2289 msg.extend(self.pack_object(self.text)) 2290 2291 return msg 2292 2293 def parse_network_message(self, message): 2294 pos, self.searchid = self.get_object(message, int) 2295 pos, self.searchterm = self.get_object(message, str, pos) 2296 2297 2298class FileSearchResult(PeerMessage): 2299 """ Peer code: 9 """ 2300 """ A peer sends this message when it has a file search match. The 2301 token/ticket is taken from original FileSearch, UserSearch or 2302 RoomSearch message. """ 2303 2304 __slots__ = ("conn", "user", "geoip", "token", "list", "privatelist", "freeulslots", 2305 "ulspeed", "inqueue", "fifoqueue") 2306 2307 def __init__(self, conn=None, user=None, token=None, shares=None, freeulslots=None, 2308 ulspeed=None, inqueue=None, fifoqueue=None): 2309 self.conn = conn 2310 self.user = user 2311 self.token = token 2312 self.list = shares 2313 self.privatelist = [] 2314 self.freeulslots = freeulslots 2315 self.ulspeed = ulspeed 2316 self.inqueue = inqueue 2317 self.fifoqueue = fifoqueue 2318 2319 def parse_network_message(self, message): 2320 try: 2321 message = memoryview(zlib.decompress(message)) 2322 self._parse_network_message(message) 2323 2324 except Exception as error: 2325 log.add("Exception during parsing %(area)s: %(exception)s", 2326 {'area': 'FileSearchResult', 'exception': error}) 2327 self.list = [] 2328 self.privatelist = [] 2329 2330 def _parse_result_list(self, message, pos): 2331 pos, nfiles = self.get_object(message, int, pos) 2332 2333 shares = [] 2334 for _ in range(nfiles): 2335 pos, code = pos + 1, message[pos] 2336 pos, name = self.get_object(message, str, pos) 2337 pos, size = self.parse_file_size(message, pos) 2338 pos, ext = self.get_object(message, str, pos) 2339 pos, numattr = self.get_object(message, int, pos) 2340 2341 attrs = [] 2342 if numattr: 2343 for _ in range(numattr): 2344 pos, _attrnum = self.get_object(message, int, pos) 2345 pos, attr = self.get_object(message, int, pos) 2346 attrs.append(attr) 2347 2348 shares.append((code, name.replace('/', '\\'), size, ext, attrs)) 2349 2350 return pos, shares 2351 2352 def _parse_network_message(self, message): 2353 pos, self.user = self.get_object(message, str) 2354 pos, self.token = self.get_object(message, int, pos) 2355 2356 if self.token not in SEARCH_TOKENS_ALLOWED: 2357 # Results are no longer accepted for this search token, stop parsing message 2358 self.list = [] 2359 return 2360 2361 pos, self.list = self._parse_result_list(message, pos) 2362 2363 pos, self.freeulslots = pos + 1, message[pos] 2364 pos, self.ulspeed = self.get_object(message, int, pos) 2365 pos, self.inqueue = self.get_object(message, int, pos, getunsignedlonglong=True) 2366 2367 if message[pos:] and config.sections["searches"]["private_search_results"]: 2368 pos, self.privatelist = self._parse_result_list(message, pos) 2369 2370 def pack_file_info(self, fileinfo): 2371 msg = bytearray() 2372 msg.extend(bytes([1])) 2373 msg.extend(self.pack_object(fileinfo[0].replace('/', '\\'))) 2374 msg.extend(self.pack_object(fileinfo[1], unsignedlonglong=True)) 2375 2376 if fileinfo[2] is None or fileinfo[3] is None: 2377 # No metadata 2378 msg.extend(self.pack_object('')) 2379 msg.extend(self.pack_object(0)) 2380 else: 2381 # FileExtension, NumAttributes 2382 msg.extend(self.pack_object("mp3")) 2383 msg.extend(self.pack_object(3)) 2384 2385 # Length 2386 msg.extend(self.pack_object(0)) 2387 msg.extend(self.pack_object(fileinfo[2][0] or 0)) 2388 2389 # Duration 2390 msg.extend(self.pack_object(1)) 2391 msg.extend(self.pack_object(fileinfo[3] or 0)) 2392 2393 # VBR 2394 msg.extend(self.pack_object(2)) 2395 msg.extend(self.pack_object(fileinfo[2][1] or 0)) 2396 2397 return msg 2398 2399 def make_network_message(self): 2400 msg = bytearray() 2401 msg.extend(self.pack_object(self.user)) 2402 msg.extend(self.pack_object(self.token)) 2403 msg.extend(self.pack_object(len(self.list))) 2404 2405 for fileinfo in self.list: 2406 msg.extend(self.pack_file_info(fileinfo)) 2407 2408 msg.extend(bytes([self.freeulslots])) 2409 msg.extend(self.pack_object(self.ulspeed)) 2410 msg.extend(self.pack_object(self.inqueue, unsignedlonglong=True)) 2411 2412 return zlib.compress(msg) 2413 2414 2415class UserInfoRequest(PeerMessage): 2416 """ Peer code: 15 """ 2417 """ We ask the other peer to send us their user information, picture 2418 and all.""" 2419 2420 def __init__(self, conn): 2421 self.conn = conn 2422 2423 def make_network_message(self): 2424 return b"" 2425 2426 def parse_network_message(self, message): 2427 # Empty message 2428 pass 2429 2430 2431class UserInfoReply(PeerMessage): 2432 """ Peer code: 16 """ 2433 """ A peer responds with this when we've sent a UserInfoRequest. """ 2434 2435 def __init__(self, conn, descr=None, pic=None, totalupl=None, queuesize=None, slotsavail=None, uploadallowed=None): 2436 self.conn = conn 2437 self.descr = descr 2438 self.pic = pic 2439 self.totalupl = totalupl 2440 self.queuesize = queuesize 2441 self.slotsavail = slotsavail 2442 self.uploadallowed = uploadallowed 2443 self.has_pic = None 2444 2445 def parse_network_message(self, message): 2446 pos, self.descr = self.get_object(message, str) 2447 pos, self.has_pic = pos + 1, message[pos] 2448 2449 if self.has_pic: 2450 pos, self.pic = self.get_object(message, bytes, pos) 2451 2452 pos, self.totalupl = self.get_object(message, int, pos) 2453 pos, self.queuesize = self.get_object(message, int, pos) 2454 pos, self.slotsavail = pos + 1, message[pos] 2455 2456 # To prevent errors, ensure that >= 4 bytes are left. Museek+ incorrectly sends 2457 # slotsavail as an integer, resulting in 3 bytes of garbage here. 2458 if len(message[pos:]) >= 4: 2459 pos, self.uploadallowed = self.get_object(message, int, pos) 2460 2461 def make_network_message(self): 2462 msg = bytearray() 2463 msg.extend(self.pack_object(self.descr)) 2464 2465 if self.pic is not None: 2466 msg.extend(bytes([1])) 2467 msg.extend(self.pack_object(self.pic)) 2468 else: 2469 msg.extend(bytes([0])) 2470 2471 msg.extend(self.pack_object(self.totalupl)) 2472 msg.extend(self.pack_object(self.queuesize)) 2473 msg.extend(bytes([self.slotsavail])) 2474 msg.extend(self.pack_object(self.uploadallowed)) 2475 2476 return msg 2477 2478 2479class PMessageUser(PeerMessage): 2480 """ Peer code: 22 """ 2481 """ Chat phrase sent to someone or received by us in private. 2482 This is a Nicotine+ extension to the Soulseek protocol. """ 2483 """ DEPRECATED """ 2484 2485 def __init__(self, conn=None, user=None, msg=None): 2486 self.conn = conn 2487 self.user = user 2488 self.msg = msg 2489 self.msgid = None 2490 self.timestamp = None 2491 2492 def make_network_message(self): 2493 msg = bytearray() 2494 msg.extend(self.pack_object(0)) 2495 msg.extend(self.pack_object(0)) 2496 msg.extend(self.pack_object(self.user)) 2497 msg.extend(self.pack_object(self.msg)) 2498 2499 return msg 2500 2501 def parse_network_message(self, message): 2502 pos, self.msgid = self.get_object(message, int) 2503 pos, self.timestamp = self.get_object(message, int, pos) 2504 pos, self.user = self.get_object(message, str, pos) 2505 pos, self.msg = self.get_object(message, str, pos) 2506 2507 2508class FolderContentsRequest(PeerMessage): 2509 """ Peer code: 36 """ 2510 """ We ask the peer to send us the contents of a single folder. """ 2511 2512 def __init__(self, conn, directory=None): 2513 self.conn = conn 2514 self.dir = directory 2515 self.something = None 2516 2517 def make_network_message(self): 2518 msg = bytearray() 2519 msg.extend(self.pack_object(1)) 2520 msg.extend(self.pack_object(self.dir, latin1=True)) 2521 2522 return msg 2523 2524 def parse_network_message(self, message): 2525 pos, self.something = self.get_object(message, int) 2526 pos, self.dir = self.get_object(message, str, pos) 2527 2528 2529class FolderContentsResponse(PeerMessage): 2530 """ Peer code: 37 """ 2531 """ A peer responds with the contents of a particular folder 2532 (with all subfolders) when we've sent a FolderContentsRequest. """ 2533 2534 def __init__(self, conn, directory=None, shares=None): 2535 self.conn = conn 2536 self.dir = directory 2537 self.list = shares 2538 2539 def parse_network_message(self, message): 2540 try: 2541 message = memoryview(zlib.decompress(message)) 2542 self._parse_network_message(message) 2543 2544 except Exception as error: 2545 log.add("Exception during parsing %(area)s: %(exception)s", 2546 {'area': 'FolderContentsResponse', 'exception': error}) 2547 self.list = {} 2548 2549 def _parse_network_message(self, message): 2550 shares = {} 2551 pos, nfolders = self.get_object(message, int) 2552 2553 for _ in range(nfolders): 2554 pos, folder = self.get_object(message, str, pos) 2555 2556 shares[folder] = {} 2557 2558 pos, ndir = self.get_object(message, int, pos) 2559 2560 for _ in range(ndir): 2561 pos, directory = self.get_object(message, str, pos) 2562 directory = directory.replace('/', '\\') 2563 pos, nfiles = self.get_object(message, int, pos) 2564 2565 shares[folder][directory] = [] 2566 2567 for _ in range(nfiles): 2568 pos, code = pos + 1, message[pos] 2569 pos, name = self.get_object(message, str, pos) 2570 pos, size = self.get_object(message, int, pos, getunsignedlonglong=True) 2571 pos, ext = self.get_object(message, str, pos) 2572 pos, numattr = self.get_object(message, int, pos) 2573 2574 attrs = [] 2575 2576 for _ in range(numattr): 2577 pos, _attrnum = self.get_object(message, int, pos) 2578 pos, attr = self.get_object(message, int, pos) 2579 attrs.append(attr) 2580 2581 shares[folder][directory].append((code, name, size, ext, attrs)) 2582 2583 self.list = shares 2584 2585 def make_network_message(self): 2586 msg = bytearray() 2587 msg.extend(self.pack_object(1)) 2588 msg.extend(self.pack_object(self.dir)) 2589 msg.extend(self.pack_object(1)) 2590 msg.extend(self.pack_object(self.dir)) 2591 2592 if self.list is not None: 2593 # We already saved the folder contents as a bytearray when scanning our shares 2594 msg.extend(self.list) 2595 else: 2596 # No folder contents 2597 msg.extend(self.pack_object(0)) 2598 2599 return zlib.compress(msg) 2600 2601 2602class TransferRequest(PeerMessage): 2603 """ Peer code: 40 """ 2604 """ This message is sent by a peer once they are ready to start uploading a file. 2605 A TransferResponse message is expected from the recipient, either allowing or 2606 rejecting the upload attempt. 2607 2608 This message was formely used to send a download request (direction 0) as well, 2609 but Nicotine+, Museek+ and the official clients use the QueueUpload message for 2610 this purpose today. """ 2611 2612 def __init__(self, conn, direction=None, req=None, file=None, filesize=None, realfile=None): 2613 self.conn = conn 2614 self.direction = direction 2615 self.req = req 2616 self.file = file # virtual file 2617 self.realfile = realfile 2618 self.filesize = filesize 2619 2620 def make_network_message(self): 2621 msg = bytearray() 2622 msg.extend(self.pack_object(self.direction)) 2623 msg.extend(self.pack_object(self.req)) 2624 msg.extend(self.pack_object(self.file)) 2625 2626 if self.filesize is not None and self.direction == 1: 2627 msg.extend(self.pack_object(self.filesize, unsignedlonglong=True)) 2628 2629 return msg 2630 2631 def parse_network_message(self, message): 2632 pos, self.direction = self.get_object(message, int) 2633 pos, self.req = self.get_object(message, int, pos) 2634 pos, self.file = self.get_object(message, str, pos) 2635 2636 if self.direction == 1: 2637 pos, self.filesize = self.get_object(message, int, pos, getunsignedlonglong=True) 2638 2639 2640class TransferResponse(PeerMessage): 2641 """ Peer code: 41 """ 2642 """ Response to TransferRequest - either we (or the other peer) agrees, 2643 or tells the reason for rejecting the file transfer. """ 2644 2645 def __init__(self, conn, allowed=None, reason=None, req=None, filesize=None): 2646 self.conn = conn 2647 self.allowed = allowed 2648 self.req = req 2649 self.reason = reason 2650 self.filesize = filesize 2651 2652 def make_network_message(self): 2653 msg = bytearray() 2654 msg.extend(self.pack_object(self.req)) 2655 msg.extend(bytes([self.allowed])) 2656 2657 if self.reason is not None: 2658 msg.extend(self.pack_object(self.reason)) 2659 2660 if self.filesize is not None: 2661 msg.extend(self.pack_object(self.filesize, unsignedlonglong=True)) 2662 2663 return msg 2664 2665 def parse_network_message(self, message): 2666 pos, self.req = self.get_object(message, int) 2667 pos, self.allowed = pos + 1, message[pos] 2668 2669 if message[pos:]: 2670 if self.allowed: 2671 pos, self.filesize = self.get_object(message, int, pos, getunsignedlonglong=True) 2672 else: 2673 pos, self.reason = self.get_object(message, str, pos) 2674 2675 2676class PlaceholdUpload(PeerMessage): 2677 """ Peer code: 42 """ 2678 """ OBSOLETE, no longer used """ 2679 2680 def __init__(self, conn, file=None): 2681 self.conn = conn 2682 self.file = file 2683 2684 def make_network_message(self): 2685 return self.pack_object(self.file) 2686 2687 def parse_network_message(self, message): 2688 _pos, self.file = self.get_object(message, str) 2689 2690 2691class QueueUpload(PeerMessage): 2692 """ Peer code: 43 """ 2693 """ This message is used to tell a peer that an upload should be queued on their end. 2694 Once the recipient is ready to transfer the requested file, they will send an upload 2695 request. """ 2696 2697 def __init__(self, conn, file=None, legacy_client=False): 2698 self.conn = conn 2699 self.file = file 2700 self.legacy_client = legacy_client 2701 2702 def make_network_message(self): 2703 return self.pack_object(self.file, latin1=self.legacy_client) 2704 2705 def parse_network_message(self, message): 2706 _pos, self.file = self.get_object(message, str) 2707 2708 2709class PlaceInQueue(PeerMessage): 2710 """ Peer code: 44 """ 2711 """ The peer replies with the upload queue placement of the requested file. """ 2712 2713 def __init__(self, conn, filename=None, place=None): 2714 self.conn = conn 2715 self.filename = filename 2716 self.place = place 2717 2718 def make_network_message(self): 2719 msg = bytearray() 2720 msg.extend(self.pack_object(self.filename)) 2721 msg.extend(self.pack_object(self.place)) 2722 2723 return msg 2724 2725 def parse_network_message(self, message): 2726 pos, self.filename = self.get_object(message, str) 2727 pos, self.place = self.get_object(message, int, pos) 2728 2729 2730class UploadFailed(PlaceholdUpload): 2731 """ Peer code: 46 """ 2732 """ This message is sent whenever a file connection of an active upload 2733 closes. Soulseek NS clients can also send this message when a file can 2734 not be read. The recipient either re-queues the upload (download on their 2735 end), or ignores the message if the transfer finished. """ 2736 2737 2738class UploadDenied(PeerMessage): 2739 """ Peer code: 50 """ 2740 """ This message is sent to reject QueueUpload attempts and previously queued 2741 files. The reason for rejection will appear in the transfer list of the recipient. """ 2742 2743 def __init__(self, conn, file=None, reason=None): 2744 self.conn = conn 2745 self.file = file 2746 self.reason = reason 2747 2748 def make_network_message(self): 2749 msg = bytearray() 2750 msg.extend(self.pack_object(self.file)) 2751 msg.extend(self.pack_object(self.reason)) 2752 2753 return msg 2754 2755 def parse_network_message(self, message): 2756 pos, self.file = self.get_object(message, str) 2757 pos, self.reason = self.get_object(message, str, pos) 2758 2759 2760class PlaceInQueueRequest(QueueUpload): 2761 """ Peer code: 51 """ 2762 """ This message is sent when asking for the upload queue placement of a file. """ 2763 2764 2765class UploadQueueNotification(PeerMessage): 2766 """ Peer code: 52 """ 2767 """ This message is sent to inform a peer about an upload attempt initiated by us. """ 2768 """ DEPRECATED, sent by Soulseek NS but not SoulseekQt """ 2769 2770 def __init__(self, conn): 2771 self.conn = conn 2772 2773 def make_network_message(self): 2774 return b"" 2775 2776 def parse_network_message(self, _message): 2777 return b"" 2778 2779 2780class UnknownPeerMessage(PeerMessage): 2781 """ Peer code: 12547 """ 2782 """ UNKNOWN """ 2783 2784 def __init__(self, conn): 2785 self.conn = conn 2786 2787 def parse_network_message(self, message): 2788 # Empty message 2789 pass 2790 2791 2792""" 2793File Messages 2794""" 2795 2796 2797class FileMessage(SlskMessage): 2798 pass 2799 2800 2801class FileRequest(FileMessage): 2802 """ We sent this to a peer via a 'F' connection to tell them that we want to 2803 start uploading a file. The token is the same as the one previously included 2804 in the TransferRequest message. """ 2805 2806 def __init__(self, conn, req=None): 2807 self.conn = conn 2808 self.req = req 2809 2810 def make_network_message(self): 2811 msg = self.pack_object(self.req) 2812 return msg 2813 2814 def parse_network_message(self, message): 2815 _pos, self.req = self.get_object(message, int) 2816 2817 2818class FileOffset(FileMessage): 2819 """ We send this to the uploading peer at the beginning of a 'F' connection, 2820 to tell them how many bytes of the file we've previously downloaded. If none, 2821 the offset is 0. """ 2822 2823 def __init__(self, conn, filesize=None, offset=None): 2824 self.conn = conn 2825 self.filesize = filesize 2826 self.offset = offset 2827 2828 def make_network_message(self): 2829 msg = self.pack_object(self.offset, unsignedlonglong=True) 2830 return msg 2831 2832 def parse_network_message(self, message): 2833 _pos, self.offset = self.get_object(message, int, getunsignedlonglong=True) 2834 2835 2836""" 2837Distributed Messages 2838""" 2839 2840 2841class DistribMessage(SlskMessage): 2842 pass 2843 2844 2845class DistribRequest(InternalMessage): 2846 """ Used to identify a connection attempt to a distributed parent. """ 2847 2848 2849class DistribAlive(DistribMessage): 2850 """ Distrib code: 0 """ 2851 2852 def __init__(self, conn): 2853 self.conn = conn 2854 2855 def make_network_message(self): 2856 return b"" 2857 2858 def parse_network_message(self, message): 2859 # Empty message 2860 pass 2861 2862 2863class DistribSearch(DistribMessage): 2864 """ Distrib code: 3 """ 2865 """ Search request that arrives through the distributed network. 2866 We transmit the search request to our child peers. """ 2867 2868 __slots__ = ("unknown", "conn", "user", "searchid", "searchterm") 2869 2870 def __init__(self, conn): 2871 self.conn = conn 2872 self.unknown = None 2873 self.user = None 2874 self.searchid = None 2875 self.searchterm = None 2876 2877 def parse_network_message(self, message): 2878 try: 2879 self._parse_network_message(message) 2880 2881 except Exception as error: 2882 log.add("Exception during parsing %(area)s: %(exception)s", 2883 {'area': 'DistribSearch', 'exception': error}) 2884 2885 def _parse_network_message(self, message): 2886 pos, self.unknown = self.get_object(message, int) 2887 pos, self.user = self.get_object(message, str, pos) 2888 pos, self.searchid = self.get_object(message, int, pos) 2889 pos, self.searchterm = self.get_object(message, str, pos) 2890 2891 2892class DistribBranchLevel(DistribMessage): 2893 """ Distrib code: 4 """ 2894 """ We tell our distributed children what our position is in our branch (xth 2895 generation) on the distributed network. """ 2896 2897 def __init__(self, conn, value=None): 2898 self.conn = conn 2899 self.value = value 2900 2901 def make_network_message(self): 2902 msg = bytearray() 2903 msg.extend(self.pack_object(self.value, signedint=True)) 2904 2905 return msg 2906 2907 def parse_network_message(self, message): 2908 _pos, self.value = self.get_object(message, int, getsignedint=True) 2909 2910 2911class DistribBranchRoot(DistribMessage): 2912 """ Distrib code: 5 """ 2913 """ We tell our distributed children the username of the root of the branch 2914 we’re in on the distributed network. """ 2915 2916 def __init__(self, conn, user=None): 2917 self.conn = conn 2918 self.user = user 2919 2920 def make_network_message(self): 2921 msg = bytearray() 2922 msg.extend(self.pack_object(self.user)) 2923 2924 return msg 2925 2926 def parse_network_message(self, message): 2927 _pos, self.user = self.get_object(message, str) 2928 2929 2930class DistribChildDepth(DistribMessage): 2931 """ Distrib code: 7 """ 2932 """ We tell our distributed parent the maximum number of generation of children 2933 we have on the distributed network. """ 2934 """ DEPRECATED, sent by Soulseek NS but not SoulseekQt """ 2935 2936 def __init__(self, conn, value=None): 2937 self.conn = conn 2938 self.value = value 2939 2940 def make_network_message(self): 2941 msg = bytearray() 2942 msg.extend(self.pack_object(self.value)) 2943 2944 return msg 2945 2946 def parse_network_message(self, message): 2947 _pos, self.value = self.get_object(message, int) 2948 2949 2950class DistribEmbeddedMessage(DistribMessage): 2951 """ Distrib code: 93 """ 2952 """ A branch root sends us an embedded distributed message. The only type 2953 of distributed message sent at present is DistribSearch (distributed code 3). 2954 We unpack the distributed message and distribute it to our child peers. """ 2955 2956 __slots__ = ("conn", "distrib_code", "distrib_message") 2957 2958 def __init__(self, conn, distrib_code=None, distrib_message=None): 2959 self.conn = conn 2960 self.distrib_code = distrib_code 2961 self.distrib_message = distrib_message 2962 2963 def make_network_message(self): 2964 msg = bytearray() 2965 msg.extend(bytes([self.distrib_code])) 2966 msg.extend(self.distrib_message) 2967 2968 return msg 2969 2970 def parse_network_message(self, message): 2971 self.distrib_code = message[3] 2972 self.distrib_message = message[4:] 2973