From 5ab815dc2241b494416afdae38abb36bc4f6d112 Mon Sep 17 00:00:00 2001 From: Yahya Al-Shamali Date: Sun, 10 Sep 2023 16:43:55 -0600 Subject: [PATCH 1/3] Require device_id for auto login --- kik_unofficial/client.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/kik_unofficial/client.py b/kik_unofficial/client.py index c39c703..7079e0b 100644 --- a/kik_unofficial/client.py +++ b/kik_unofficial/client.py @@ -31,9 +31,8 @@ class KikClient: """ The main kik class with which you're managing a kik connection and sending commands """ - - def __init__(self, callback: callbacks.KikClientCallback, kik_username, kik_password, - kik_node=None, device_id=random_device_id(), android_id=random_android_id(), logging=False): + def __init__(self, callback: callbacks.KikClientCallback, kik_username: str, kik_password: str, + kik_node: str = None, device_id: str = None, android_id: str = random_android_id(), logging: bool = False) -> None: """ Initializes a connection to Kik servers. If you want to automatically login too, use the username and password parameters. @@ -45,7 +44,7 @@ def __init__(self, callback: callbacks.KikClientCallback, kik_username, kik_pass :param kik_password: the kik password to log in with. :param kik_node: the username plus 3 letters after the "_" and before the "@" in the JID. If you know it, authentication will happen faster and without a login. otherwise supply None. - :param device_id: a unique device ID. If you don't supply one, a random one will be generated. + :param device_id: a unique device ID. If you don't supply one, a random one will be generated. (generated at _on_connection_made) :param android_id: a unique android ID. If you don't supply one, a random one will be generated. :param logging: If true, turns on logging to stdout (default: False) """ @@ -97,12 +96,14 @@ def _on_connection_made(self): Gets called when the TCP connection to kik's servers is done and we are connected. Now we might initiate a login request or an auth request. """ - if self.username is not None and self.password is not None and self.kik_node is not None: + if self.username and self.password and self.kik_node and self.device_id: # we have all required credentials, we can authenticate - log.info(f"[+] Establishing authenticated connection using kik node '{self.kik_node}'...") + log.info(f"[+] Establishing authenticated connection using kik node '{self.kik_node}', device id '{self.device_id}' and android id '{self.android_id}'...") message = login.EstablishAuthenticatedSessionRequest(self.kik_node, self.username, self.password, self.device_id) else: + # if device id is not known, we generate a random one + self.device_id = random_device_id() message = login.MakeAnonymousStreamInitTag(self.device_id, n = 1) self.initial_connection_payload = message.serialize() self.connection.send_raw_data(self.initial_connection_payload) From b500cf62e467ee3905b31df0fe43076d6ba7c03e Mon Sep 17 00:00:00 2001 From: Yahya Al-Shamali Date: Sun, 10 Sep 2023 17:07:38 -0600 Subject: [PATCH 2/3] Refactor datatypes --- kik_unofficial/datatypes/xmpp/history.py | 3 +- kik_unofficial/datatypes/xmpp/login.py | 30 +++++------ kik_unofficial/datatypes/xmpp/roster.py | 64 +++++++++++------------- 3 files changed, 43 insertions(+), 54 deletions(-) diff --git a/kik_unofficial/datatypes/xmpp/history.py b/kik_unofficial/datatypes/xmpp/history.py index b4fb3da..3292390 100644 --- a/kik_unofficial/datatypes/xmpp/history.py +++ b/kik_unofficial/datatypes/xmpp/history.py @@ -56,12 +56,13 @@ def serialize(self): data = (f'' '' - f'' + '' '' '' '' ) return data.encode() + class HistoryResponse(XMPPResponse): """ diff --git a/kik_unofficial/datatypes/xmpp/login.py b/kik_unofficial/datatypes/xmpp/login.py index 4625a11..a9d6401 100644 --- a/kik_unofficial/datatypes/xmpp/login.py +++ b/kik_unofficial/datatypes/xmpp/login.py @@ -87,15 +87,15 @@ def __init__(self, device_id=None, n=1): def serialize(self): can = 'CAN' # XmppSocketV2.java line 180, - + device = self.device_id timestamp = str(CryptographicUtils.make_kik_timestamp()) sid = CryptographicUtils.make_kik_uuid() - - signature = rsa.sign("{}:{}:{}:{}".format(can + device, kik_version, timestamp, sid).encode(), private_key, 'SHA-256') + + signature = rsa.sign(f"{can + device}:{kik_version}:{timestamp}:{sid}".encode(), private_key,'SHA-256') signature = base64.b64encode(signature, '-_'.encode()).decode().rstrip('=') - hmac_data = timestamp + ":" + can + device + hmac_data = f"{timestamp}:{can}{device}" hmac_secret_key = CryptographicUtils.build_hmac_key() cv = binascii.hexlify(hmac.new(hmac_secret_key, hmac_data.encode(), hashlib.sha1).digest()).decode() @@ -110,7 +110,7 @@ def serialize(self): 'conn': 'WIFI', 'dev': can+device, } - + # Test data to confirm the sort_kik_map function returns the correct result. # the_map = { # 'signed': 'signature', @@ -126,7 +126,7 @@ def serialize(self): if self.n > 0: the_map['n'] = self.n - + packet = CryptographicUtils.make_connection_payload(*CryptographicUtils.sort_kik_map(the_map)) return packet.encode() @@ -143,16 +143,16 @@ def __init__(self, node, username, password, device_id=None): self.device_id = device_id def serialize(self): - jid = self.node + "@talk.kik.com" - jid_with_resource = jid + "/CAN" + (self.device_id) + jid = f"{self.node}@talk.kik.com" + jid_with_resource = f"{jid}/CAN{self.device_id}" timestamp = str(CryptographicUtils.make_kik_timestamp()) sid = CryptographicUtils.make_kik_uuid() # some super secret cryptographic stuff - - signature = rsa.sign("{}:{}:{}:{}".format(jid, kik_version, timestamp, sid).encode(), private_key, 'SHA-256') + + signature = rsa.sign(f"{jid}:{kik_version}:{timestamp}:{sid}".encode(), private_key,'SHA-256') signature = base64.b64encode(signature, '-_'.encode()).decode().rstrip('=') - hmac_data = timestamp + ":" + jid + hmac_data = f"{timestamp}:{jid}" hmac_secret_key = CryptographicUtils.build_hmac_key() cv = binascii.hexlify(hmac.new(hmac_secret_key, hmac_data.encode(), hashlib.sha1).digest()).decode() @@ -176,7 +176,7 @@ class CaptchaElement: """ def __init__(self, data: BeautifulSoup): self.type = data.stp['type'] - self.captcha_url = data.stp.text + "&callback_url=https://kik.com/captcha-url" + self.captcha_url = f"{data.stp.text}&callback_url=https://kik.com/captcha-url" self.stc_id = data['id'] @@ -190,9 +190,5 @@ def __init__(self, stc_id: str, captcha_result: str): self.stc_id = stc_id def serialize(self) -> bytes: - data = ( - '' - '{}' - '' - ).format(self.stc_id, self.captcha_result) + data = f'{self.captcha_result}' return data.encode() diff --git a/kik_unofficial/datatypes/xmpp/roster.py b/kik_unofficial/datatypes/xmpp/roster.py index e3ef03a..5748b2c 100644 --- a/kik_unofficial/datatypes/xmpp/roster.py +++ b/kik_unofficial/datatypes/xmpp/roster.py @@ -39,17 +39,14 @@ def __init__(self, data: BeautifulSoup): @staticmethod def parse_peer(element): - if element.name == "g": + if element.name in ('g', "remove-group"): return Group(element) - elif element.name == "item": + elif element.name in ('item', "remove"): + # remove deletes accounts / accounts no longer in the roster return User(element) - elif element.name == "remove": - # deleted accounts / accounts no longer in the roster - return User(element) - elif element.name == "remove-group": - return Group(element) else: - raise KikParsingException("Unsupported peer element tag: {}".format(element.name)) + raise KikParsingException(f"Unsupported peer element tag: {element.name}") + class QueryUsersInfoRequest(XMPPElement): @@ -58,27 +55,24 @@ class QueryUsersInfoRequest(XMPPElement): """ def __init__(self, peer_jids: Union[str, List[str]]): super().__init__() - if isinstance(peer_jids, List): - self.peer_jids = peer_jids - else: - self.peer_jids = [peer_jids] + self.peer_jids = peer_jids if isinstance(peer_jids, List) else [peer_jids] def serialize(self) -> bytes: items = [] for jid in self.peer_jids: if "@" in jid: - items.append(''.format(jid)) + items.append(f'') else: # this is in fact not a JID, but a username - items.append(''.format(jid)) + items.append(f'') xmlns = 'kik:iq:friend:batch' if len(items) > 1 else "kik:iq:friend" - data = ('' - '' - '{}' + data = (f'' + f'' + f'{"".join(items)}' '' - '').format(self.message_id, xmlns, "".join(items)) + '') return data.encode() @@ -101,11 +95,11 @@ def __init__(self, peer_jid): self.peer_jid = peer_jid def serialize(self): - data = '' \ + data = f'' \ '' \ - '' \ + f'' \ '' \ - ''.format(self.message_id, self.peer_jid) + '' return data.encode() @@ -119,12 +113,12 @@ def __init__(self, peer_jid): def serialize(self): data = ( - '' + f'' '' - '' + f'' '' '' - ).format(self.message_id, self.peer_jid) + ) return data.encode() @@ -145,9 +139,9 @@ def serialize(self): if encoded_search_query.endswith("="): encoded_search_query = encoded_search_query[:encoded_search_query.index("=")] - data = ('' + data = (f'' '' - '{}').format(self.message_id, encoded_search_query) + f'{encoded_search_query}') return data.encode() @@ -162,15 +156,14 @@ def __init__(self, data: BeautifulSoup): results = group_search_service_pb2.FindGroupsResponse() results.ParseFromString(encoded_results) self.groups = [] # type: List[self.GroupSearchEntry] - for result in results.match: - self.groups.append(self.GroupSearchEntry(result)) + self.groups.extend(self.GroupSearchEntry(result) for result in results.match) class GroupSearchEntry: """ Represents a group entry that was found in the search results """ def __init__(self, result): - self.jid = result.jid.local_part + "@groups.kik.com" + self.jid = f"{result.jid.local_part}@groups.kik.com" self.hashtag = result.display_data.hashtag self.display_name = result.display_data.display_name self.picture_url = result.display_data.display_pic_base_url @@ -178,7 +171,7 @@ def __init__(self, result): self.group_join_token = result.group_join_token.token def __repr__(self): - return "GroupSearchEntry(jid={}, hashtag={}, name={}, members={})".format(self.jid, self.hashtag, self.display_name, self.member_count) + return f"GroupSearchEntry(jid={self.jid}, hashtag={self.hashtag}, name={self.display_name}, members={self.member_count})" class GroupJoinRequest(XMPPElement): @@ -196,11 +189,10 @@ def serialize(self) -> bytes: join_token = base64.b64encode(self.join_token, b"-_").decode() if join_token.endswith("="): join_token = join_token[:join_token.index("=")] - data = ('' + data = (f'' '' - '' - '{}' - '{}' - '') \ - .format(self.message_id, self.group_jid, self.group_hashtag, join_token) + f'' + f'{self.group_hashtag}' + f'{join_token}' + '') return data.encode() From 708a1b29a70830afaa1e9b78ca7608970a71f429 Mon Sep 17 00:00:00 2001 From: Yahya Al-Shamali Date: Sun, 10 Sep 2023 17:23:42 -0600 Subject: [PATCH 3/3] Make the API_key mandatory Made the API key mandatory --- kik_unofficial/client.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/kik_unofficial/client.py b/kik_unofficial/client.py index 7079e0b..762fd78 100644 --- a/kik_unofficial/client.py +++ b/kik_unofficial/client.py @@ -122,7 +122,7 @@ def _establish_authenticated_session(self, kik_node): self.disconnect() self._connect() - def login(self, username, password, captcha_result=None): + def login(self, username: str, password: str, captcha_result: str = None): """ Sends a login request with the given kik username and password @@ -138,7 +138,7 @@ def login(self, username, password, captcha_result=None): log.info(f"[+] Logging in with {login_type} '{username}' and a given password {'*' * len(password)}...") return self._send_xmpp_element(login_request) - def register(self, email, username, password, first_name, last_name, birthday="1974-11-20", captcha_result=None): + def register(self, email, username, password, first_name, last_name, birthday="1974-11-20", captcha_result: str = None): """ Sends a register request to sign up a new user to kik with the given details. """ @@ -237,8 +237,9 @@ def send_is_typing(self, peer_jid: str, is_typing: bool): else: return self._send_xmpp_element(chatting.OutgoingIsTypingEvent(peer_jid, is_typing)) - # If you can set your API key here by replacing the None value with your API key - def send_gif_image(self, peer_jid: str, search_term, API_key=None): + # Uncomment if you want to set your api key here + # def send_gif_image(self, peer_jid, search_term, API_key = "YOUR_API_KEY"): + def send_gif_image(self, peer_jid: str, search_term: str, API_key: str): """ Sends a GIF image to another person or a group with the given JID/username. The GIF is taken from tendor.com, based on search keywords.