initial commit
This commit is contained in:
commit
af17d6fb22
|
@ -0,0 +1,12 @@
|
|||
# editor
|
||||
.vscode/
|
||||
|
||||
# unrelated
|
||||
notes.md
|
||||
|
||||
# builds
|
||||
dist/
|
||||
ryd_client.egg-info/
|
||||
|
||||
# cache
|
||||
__pycache__
|
|
@ -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).
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
# build package
|
||||
|
||||
python -m build
|
||||
|
||||
##
|
||||
exit 0
|
|
@ -0,0 +1,6 @@
|
|||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
|
@ -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
|
|
@ -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",
|
||||
)
|
Loading…
Reference in New Issue