initial commit

This commit is contained in:
simon 2021-12-24 16:33:34 +07:00
commit af17d6fb22
Signed by: simon
GPG Key ID: 2C15AA5E89985DD4
7 changed files with 364 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
# editor
.vscode/
# unrelated
notes.md
# builds
dist/
ryd_client.egg-info/
# cache
__pycache__

95
README.md Normal file
View File

@ -0,0 +1,95 @@
# RYD Client
Python client library for the **Return YouTube Dislike API**:
- [https://returnyoutubedislike.com/](https://returnyoutubedislike.com/)
- [https://github.com/Anarios/return-youtube-dislike/](https://github.com/Anarios/return-youtube-dislike/)
## Functionality
- Get votes from a list of YouTube video IDs.
- Register your user ID by solving the challenge.
- Cast your vote for a list of YouTube video IDs.
## Usage
Some command example
### Get Votes
Pass a list of YouTube video IDs and get a list of votes.
```python
import ryd_client
ratings = ryd_client.get_votes(["kxOuG8jMIgI", "CaaJyRvvaq8"])
# Returns a list of dictionaries with ratings for every video ID
[{'id': 'kxOuG8jMIgI',
'likes': 27569,
'dislikes': 503144,
'rating': 1.2117898772151874,
'viewCount': 3177346,
'deleted': False,
'status': 200},
{'id': 'CaaJyRvvaq8',
'likes': 502489,
'dislikes': 13270,
'rating': 4.900305046067389,
'viewCount': 3575816,
'deleted': False,
'status': 200}]
```
### Register
To cast a vote, you need to be registered in the API with your user id. Generate a random user id, one per user, store it in your application and reuse for all future votes from this user.
```python
import ryd_client
user_id = ryd_client.generate_user_id()
# Returns a random 36 char string of ascii_letters and digits
'5v3X3mxQOm3fkez8aWsGsEgjpFe0pJNPWIJi'
```
Register your user_id in the api:
```python
import ryd_client
success = ryd_client.register(user_id)
# Returns True on success, False on fail
True
```
### Post Votes
Once your `user_id` is registered, you are allowed to vote. Vote on a list of video IDs. Pass a list of tuples where the first value is the video ID and second value is the vote either as `string` or `int`:
- like: 1
- dislike: -1
- neutral: 0 (aka *undo* your previous vote)
Strings automatically get converted to the matching number, both are valid:
```python
import ryd_client
votes = [
("kxOuG8jMIgI", "dislike"),
("CaaJyRvvaq8", 1),
("CEp5SLT-DJg", 0),
]
response = ryd_client.post_votes(votes, user_id=user_id)
# Returns a list of dictionaries for every vote cast
[{'id': 'kxOuG8jMIgI', 'status': True, 'vote': -1},
{'id': 'CaaJyRvvaq8', 'status': True, 'vote': 1},
{'id': 'CEp5SLT-DJg', 'status': True, 'vote': 0}]
```
## Acknowledgement
If you find this API usefull, please consider donating to the [project](https://returnyoutubedislike.com/donate).

7
build.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
# build package
python -m build
##
exit 0

6
pyproject.toml Normal file
View File

@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools>=42",
"wheel"
]
build-backend = "setuptools.build_meta"

0
ryd-client/__init__.py Normal file
View File

218
ryd-client/ryd_client.py Normal file
View File

@ -0,0 +1,218 @@
"""post votes for YouTube video"""
import random
import string
import base64
import hashlib
import requests
API_URL = "https://returnyoutubedislikeapi.com"
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()
base_url = f"{API_URL}/puzzle/registration"
puzzle = requests.get(f"{base_url}?userId={user_id}").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, 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 Vote:
"""cast your vote"""
def __init__(self, user_id, vote):
self.user_id = user_id
self.video_id = vote[0]
self.vote = self.validate_vote(vote[1])
def post(self):
"""post vote to API"""
puzzle = self._initial_vote()
solution = Puzzle(puzzle).solve()
response = self._confirm_vote(solution)
if not response:
print(f"failed to cast vote for: {self.user_id}, {self.video_id}")
raise ValueError
message = {
"id": self.video_id,
"status": response,
"vote": self.vote
}
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):
"""send initial vote to recieve puzzle"""
data = {
"userId": self.user_id,
"videoId": self.video_id,
"value": self.vote,
}
response = requests.post(f"{API_URL}/interact/vote", json=data)
if not response.ok:
print("failed")
raise ValueError
puzzle = response.json()
return puzzle
def _confirm_vote(self, solution):
"""send second confirmation with solved puzzle"""
data = {
"userId": self.user_id,
"videoId": self.video_id,
"solution": solution["solution"]
}
response = requests.post(f"{API_URL}/interact/confirmVote", json=data)
if response.ok:
return response.text == "true"
return False
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_votes(youtube_ids):
"""get votes from list of youtube_ids"""
all_votes = []
for youtube_id in youtube_ids:
votes = requests.get(f"{API_URL}/votes?videoId={youtube_id}")
if votes.ok:
parsed = votes.json()
parsed["status"] = votes.status_code
del parsed["dateCreated"]
elif votes.status_code in [400, 404]:
parsed = {
"id": youtube_id,
"status": votes.status_code,
}
elif votes.status_code == 429:
print("ratelimiting reached, cancle")
break
all_votes.append(parsed)
return all_votes
def post_votes(votes, user_id):
"""post votes"""
all_votes = []
for vote_pair in votes:
vote_handler = Vote(user_id, vote_pair)
message = vote_handler.post()
all_votes.append(message)
return all_votes

26
setup.py Normal file
View File

@ -0,0 +1,26 @@
import setuptools
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setuptools.setup(
name="ryd-client",
version="0.0.1",
author="Simon",
author_email="simobilleter@gmail.com",
description="api client for returnyoutubedislike.com",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/bbilly1/ryd-client",
project_urls={
"Bug Tracker": "https://github.com/bbilly1/ryd-client/issues",
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
package_dir={"ryd-client": "ryd-client"},
packages=setuptools.find_packages(where="ryd_client"),
python_requires=">=3.6",
)