Skip to content

Commit

Permalink
Merge pull request #233 from PointlessUser/new
Browse files Browse the repository at this point in the history
Require device_id for auto login
  • Loading branch information
tomer8007 authored Sep 18, 2023
2 parents c860cfb + 708a1b2 commit 4e4aa06
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 64 deletions.
22 changes: 12 additions & 10 deletions kik_unofficial/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
"""
Expand Down Expand Up @@ -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)
Expand All @@ -121,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
Expand All @@ -137,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.
"""
Expand Down Expand Up @@ -236,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.
Expand Down
3 changes: 2 additions & 1 deletion kik_unofficial/datatypes/xmpp/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ def serialize(self):

data = (f'<iq type="set" id="{self.message_id}" cts="{timestamp}">'
'<query xmlns="kik:iq:QoS">'
f'<msg-acks />'
'<msg-acks />'
'<history attach="true" />'
'</query>'
'</iq>'
)
return data.encode()


class HistoryResponse(XMPPResponse):
"""
Expand Down
30 changes: 13 additions & 17 deletions kik_unofficial/datatypes/xmpp/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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',
Expand All @@ -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()

Expand All @@ -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()

Expand All @@ -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']


Expand All @@ -190,9 +190,5 @@ def __init__(self, stc_id: str, captcha_result: str):
self.stc_id = stc_id

def serialize(self) -> bytes:
data = (
'<stc id="{}">'
'<sts>{}</sts>'
'</stc>'
).format(self.stc_id, self.captcha_result)
data = f'<stc id="{self.stc_id}"><sts>{self.captcha_result}</sts></stc>'
return data.encode()
64 changes: 28 additions & 36 deletions kik_unofficial/datatypes/xmpp/roster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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('<item jid="{}" />'.format(jid))
items.append(f'<item jid="{jid}" />')
else:
# this is in fact not a JID, but a username
items.append('<item username="{}" />'.format(jid))
items.append(f'<item username="{jid}" />')

xmlns = 'kik:iq:friend:batch' if len(items) > 1 else "kik:iq:friend"

data = ('<iq type="get" id="{}">'
'<query xmlns="{}">'
'{}'
data = (f'<iq type="get" id="{self.message_id}">'
f'<query xmlns="{xmlns}">'
f'{"".join(items)}'
'</query>'
'</iq>').format(self.message_id, xmlns, "".join(items))
'</iq>')

return data.encode()

Expand All @@ -101,11 +95,11 @@ def __init__(self, peer_jid):
self.peer_jid = peer_jid

def serialize(self):
data = '<iq type="set" id="{}">' \
data = f'<iq type="set" id="{self.message_id}">' \
'<query xmlns="kik:iq:friend">' \
'<add jid="{}" />' \
f'<add jid="{self.peer_jid}" />' \
'</query>' \
'</iq>'.format(self.message_id, self.peer_jid)
'</iq>'
return data.encode()


Expand All @@ -119,12 +113,12 @@ def __init__(self, peer_jid):

def serialize(self):
data = (
'<iq type="set" id="{}">'
f'<iq type="set" id="{self.message_id}">'
'<query xmlns="kik:iq:friend">'
'<remove jid="{}" />'
f'<remove jid="{self.peer_jid}" />'
'</query>'
'</iq>'
).format(self.message_id, self.peer_jid)
)
return data.encode()


Expand All @@ -145,9 +139,9 @@ def serialize(self):
if encoded_search_query.endswith("="):
encoded_search_query = encoded_search_query[:encoded_search_query.index("=")]

data = ('<iq type="set" id="{}">'
data = (f'<iq type="set" id="{self.message_id}">'
'<query xmlns="kik:iq:xiphias:bridge" service="mobile.groups.v1.GroupSearch" method="FindGroups">'
'<body>{}</body></query></iq>').format(self.message_id, encoded_search_query)
f'<body>{encoded_search_query}</body></query></iq>')
return data.encode()


Expand All @@ -162,23 +156,22 @@ 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
self.member_count = result.member_count
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):
Expand All @@ -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 = ('<iq type="set" id="{}">'
data = (f'<iq type="set" id="{self.message_id}">'
'<query xmlns="kik:groups:admin">'
'<g jid="{}" action="join">'
'<code>{}</code>'
'<token>{}</token>'
'</g></query></iq>') \
.format(self.message_id, self.group_jid, self.group_hashtag, join_token)
f'<g jid="{self.group_jid}" action="join">'
f'<code>{self.group_hashtag}</code>'
f'<token>{join_token}</token>'
'</g></query></iq>')
return data.encode()

0 comments on commit 4e4aa06

Please sign in to comment.