278 lines
7.5 KiB
Python
278 lines
7.5 KiB
Python
"""api client for returnyoutubedislike.com"""
|
|
|
|
import base64
|
|
import hashlib
|
|
import random
|
|
import string
|
|
from time import sleep
|
|
|
|
import requests
|
|
|
|
API_URL = "https://returnyoutubedislikeapi.com"
|
|
HEADERS = {"User-Agent": "https://github.com/bbilly1/ryd-client v0.0.6"}
|
|
|
|
|
|
class Login:
|
|
"""handle user registation"""
|
|
|
|
def __init__(self, user_id=False):
|
|
self.user_id = user_id
|
|
|
|
def generate_user_id(self):
|
|
"""get random 36 int user id"""
|
|
choice = string.ascii_letters + string.digits
|
|
new_user_id = str()
|
|
for _ in range(36):
|
|
letter = random.SystemRandom().choice(choice)
|
|
new_user_id = new_user_id + letter
|
|
|
|
self.user_id = new_user_id
|
|
|
|
return new_user_id
|
|
|
|
def get_puzzle(self):
|
|
"""get puzzle"""
|
|
user_id = self.user_id or self.generate_user_id()
|
|
url = f"{API_URL}/puzzle/registration?userId={user_id}"
|
|
puzzle = requests.get(url, headers=HEADERS).json()
|
|
puzzle["user_id"] = user_id
|
|
|
|
return puzzle
|
|
|
|
def post_puzzle(self, solution):
|
|
"""post solved puzzle to confirm registration"""
|
|
url = f"{API_URL}/puzzle/registration?userId={self.user_id}"
|
|
response = requests.post(url, headers=HEADERS, json=solution)
|
|
if response.ok:
|
|
print(f"successfully registered with user id {self.user_id}")
|
|
return response.text == "true"
|
|
|
|
return False
|
|
|
|
|
|
class Puzzle:
|
|
"""solve your puzzle"""
|
|
|
|
def __init__(self, puzzle):
|
|
self.puzzle = puzzle
|
|
|
|
@staticmethod
|
|
def count_leading_zeros(to_check):
|
|
"""return leading binary zeroes"""
|
|
zeros = 0
|
|
for i in to_check:
|
|
if i == 0:
|
|
zeros = zeros + 8
|
|
else:
|
|
zeros = zeros + f"{i:08b}".index("1")
|
|
break
|
|
|
|
return zeros
|
|
|
|
def solve(self):
|
|
"""get puzzle solution"""
|
|
challenge = list(base64.b64decode(self.puzzle["challenge"]))
|
|
max_count = 2 ** self.puzzle["difficulty"] * 5
|
|
# fill buffer
|
|
buffer = bytearray(20)
|
|
for i in range(4, 20):
|
|
buffer[i] = challenge[i - 4]
|
|
# keep hashing until leading zeros are matched
|
|
for i in range(max_count):
|
|
new_buffer = (i).to_bytes(4, byteorder="little") + buffer[4:20]
|
|
to_check = list(hashlib.sha512(new_buffer).digest())
|
|
zeros = self.count_leading_zeros(to_check)
|
|
if zeros >= self.puzzle["difficulty"]:
|
|
solution = base64.b64encode(new_buffer[0:4]).decode()
|
|
return {"solution": solution}
|
|
|
|
return False
|
|
|
|
|
|
class VotePost:
|
|
"""cast your vote"""
|
|
|
|
def __init__(self, votes, user_id):
|
|
self.votes = votes
|
|
self.user_id = user_id
|
|
|
|
def process(self):
|
|
"""process the input"""
|
|
if isinstance(self.votes, list):
|
|
to_return = self._process_list()
|
|
elif isinstance(self.votes, tuple):
|
|
youtube_id, vote = self.votes
|
|
validated = self.validate_vote(vote)
|
|
to_return = self.post((youtube_id, validated))
|
|
else:
|
|
raise ValueError
|
|
|
|
return to_return
|
|
|
|
def _process_list(self):
|
|
"""process a list of votes"""
|
|
validated = [(i[0], self.validate_vote(i[1])) for i in self.votes]
|
|
|
|
all_messages = []
|
|
for vote in validated:
|
|
message = self.post(vote)
|
|
all_messages.append(message)
|
|
|
|
return all_messages
|
|
|
|
def post(self, vote):
|
|
"""post vote to API"""
|
|
puzzle = self._initial_vote(vote)
|
|
solution = Puzzle(puzzle).solve()
|
|
response = self._confirm_vote(solution, vote[0])
|
|
if not response:
|
|
print(f"failed to cast vote for: {self.user_id}, {vote}")
|
|
raise ValueError
|
|
|
|
message = {
|
|
"id": vote[0],
|
|
"status": response,
|
|
"vote": vote[1],
|
|
}
|
|
|
|
return message
|
|
|
|
@staticmethod
|
|
def validate_vote(vote):
|
|
"""convert vote"""
|
|
vote_map = {
|
|
"like": 1,
|
|
"dislike": -1,
|
|
"neutral": 0,
|
|
}
|
|
if isinstance(vote, str):
|
|
try:
|
|
return vote_map[vote]
|
|
except KeyError:
|
|
print(f"invalid vote: {vote}")
|
|
raise
|
|
elif isinstance(vote, int):
|
|
if vote in vote_map.values():
|
|
return vote
|
|
raise ValueError(f"invalid vote cast: {vote}")
|
|
|
|
return False
|
|
|
|
def _initial_vote(self, vote):
|
|
"""send initial vote to receive puzzle"""
|
|
data = {
|
|
"userId": self.user_id,
|
|
"videoId": vote[0],
|
|
"value": vote[1],
|
|
}
|
|
url = f"{API_URL}/interact/vote"
|
|
response = requests.post(url, headers=HEADERS, json=data)
|
|
if not response.ok:
|
|
print("failed")
|
|
raise ValueError
|
|
puzzle = response.json()
|
|
|
|
return puzzle
|
|
|
|
def _confirm_vote(self, solution, video_id):
|
|
"""send second confirmation with solved puzzle"""
|
|
data = {
|
|
"userId": self.user_id,
|
|
"videoId": video_id,
|
|
"solution": solution["solution"],
|
|
}
|
|
url = f"{API_URL}/interact/confirmVote"
|
|
response = requests.post(url, headers=HEADERS, json=data)
|
|
if response.ok:
|
|
return response.text == "true"
|
|
|
|
return False
|
|
|
|
|
|
class VoteGet:
|
|
"""get single vote or list of votes"""
|
|
|
|
def __init__(self, youtube_ids):
|
|
self.youtube_ids = youtube_ids
|
|
|
|
def process(self):
|
|
"""process youtube_ids build list or string"""
|
|
if isinstance(self.youtube_ids, list):
|
|
to_return = self._process_list()
|
|
elif isinstance(self.youtube_ids, str):
|
|
to_return = self._get_vote(self.youtube_ids)
|
|
else:
|
|
raise ValueError
|
|
|
|
return to_return
|
|
|
|
def _process_list(self):
|
|
"""process list"""
|
|
all_votes = []
|
|
for youtube_id in self.youtube_ids:
|
|
parsed = self._get_vote(youtube_id)
|
|
all_votes.append(parsed)
|
|
return all_votes
|
|
|
|
@staticmethod
|
|
def _get_vote(youtube_id):
|
|
"""get vote from a single video"""
|
|
url = f"{API_URL}/votes?videoId={youtube_id}"
|
|
votes = None
|
|
try:
|
|
votes = requests.get(url, headers=HEADERS, timeout=3)
|
|
except requests.exceptions.RequestException:
|
|
sleep(5)
|
|
votes = requests.get(url, headers=HEADERS, timeout=5)
|
|
|
|
if votes is None:
|
|
raise ConnectionError("failed to connect to API")
|
|
|
|
if votes.ok:
|
|
parsed = votes.json()
|
|
parsed["status"] = votes.status_code
|
|
del parsed["dateCreated"]
|
|
else:
|
|
print(f"{youtube_id}: RYD returns error code {votes.status_code}.")
|
|
parsed = {
|
|
"id": youtube_id,
|
|
"status": votes.status_code,
|
|
}
|
|
|
|
return parsed
|
|
|
|
|
|
def generate_user_id():
|
|
"""short hand to generate user id"""
|
|
user_id = Login().generate_user_id()
|
|
return user_id
|
|
|
|
|
|
def register(user_id):
|
|
"""register your user id"""
|
|
login_handler = Login(user_id)
|
|
puzzle = login_handler.get_puzzle()
|
|
solution = Puzzle(puzzle).solve()
|
|
response = login_handler.post_puzzle(solution)
|
|
if not response:
|
|
print(f"failed to register with user id {user_id}")
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def get(youtube_ids):
|
|
"""get votes from list of youtube_ids"""
|
|
|
|
result = VoteGet(youtube_ids).process()
|
|
|
|
return result
|
|
|
|
|
|
def post(votes, user_id):
|
|
"""post votes"""
|
|
|
|
result = VotePost(votes, user_id).process()
|
|
|
|
return result
|