This commit is contained in:
commit
81f00caa7e
16
.gitea/workflows/build.yaml
Normal file
16
.gitea/workflows/build.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
name: devranta build
|
||||||
|
run-name: devranta async devRant api client build and test
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: List files in the repository
|
||||||
|
run: |
|
||||||
|
ls ${{ gitea.workspace }}
|
||||||
|
- run: make build
|
||||||
|
- run: make run
|
||||||
|
- run: make test
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.venv
|
||||||
|
.history
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
30
Makefile
Normal file
30
Makefile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
PYTHON=./.venv/bin/python
|
||||||
|
PIP=./.venv/bin/pip
|
||||||
|
BIN=./.venv/bin
|
||||||
|
|
||||||
|
all: format install build run test
|
||||||
|
|
||||||
|
ensure_env:
|
||||||
|
-@python3 -m venv .venv
|
||||||
|
|
||||||
|
install: ensure_env
|
||||||
|
$(PIP) install -e .
|
||||||
|
|
||||||
|
build: ensure_env
|
||||||
|
$(PIP) install build
|
||||||
|
$(PYTHON) -m build .
|
||||||
|
|
||||||
|
format: ensure_env
|
||||||
|
$(PIP) install shed
|
||||||
|
$(BIN)/shed src/devranta/*.py
|
||||||
|
|
||||||
|
test: ensure_env
|
||||||
|
$(PYTHON) -m unittest devranta.tests
|
||||||
|
|
||||||
|
run: ensure_env
|
||||||
|
$(BIN)/devranta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# devRanta
|
||||||
|
|
||||||
|
## About
|
||||||
|
devRanta is an async devrant client written in and for Python.
|
||||||
|
Authentication is only needed for half of the functionality and thus username and password are optional parameters by constructing the main class of this package (Api).
|
||||||
|
|
||||||
|
## Running
|
||||||
|
```
|
||||||
|
make run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
Tests are only made for methods not requireing authentication.
|
||||||
|
I do not see value in mocking requests.
|
||||||
|
```
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
Implementation:
|
||||||
|
```
|
||||||
|
from devranta.api import Api
|
||||||
|
|
||||||
|
api = Api(username="optional!", password="optional!")
|
||||||
|
|
||||||
|
async def list_rants():
|
||||||
|
async for rant in api.get_rants():
|
||||||
|
print(rant["user_username"], ":", rant["text"])
|
||||||
|
```
|
||||||
|
See [tests](src/devranta/tests.py) for [examples](src/devranta/tests.py) on how to use.
|
||||||
|
|
BIN
dist/Retoorded-1.3.37-py3-none-any.whl
vendored
Normal file
BIN
dist/Retoorded-1.3.37-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
dist/devranta-1.0.0-py3-none-any.whl
vendored
Normal file
BIN
dist/devranta-1.0.0-py3-none-any.whl
vendored
Normal file
Binary file not shown.
BIN
dist/devranta-1.0.0.tar.gz
vendored
Normal file
BIN
dist/devranta-1.0.0.tar.gz
vendored
Normal file
Binary file not shown.
BIN
dist/retoorded-1.3.37.tar.gz
vendored
Normal file
BIN
dist/retoorded-1.3.37.tar.gz
vendored
Normal file
Binary file not shown.
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
25
setup.cfg
Normal file
25
setup.cfg
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
[metadata]
|
||||||
|
name = devranta
|
||||||
|
version = 1.0.0
|
||||||
|
description = Async devRant API client made with aiohttp.
|
||||||
|
author = retoor
|
||||||
|
author_email = retoor@molodetz.nl
|
||||||
|
license = MIT
|
||||||
|
long_description = file: README.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
package_dir =
|
||||||
|
= src
|
||||||
|
python_requires = >=3.7
|
||||||
|
install_requires =
|
||||||
|
requests
|
||||||
|
aiohttp
|
||||||
|
dataset
|
||||||
|
[options.packages.find]
|
||||||
|
where = src
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
devranta = devranta.__main__:main
|
12
src/Retoorded.egg-info/PKG-INFO
Normal file
12
src/Retoorded.egg-info/PKG-INFO
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Retoorded
|
||||||
|
Version: 1.3.37
|
||||||
|
Summary: A LLM to devRant mapper
|
||||||
|
Author: Retoor
|
||||||
|
Author-email: retoor@molodetz.nl
|
||||||
|
License: MIT
|
||||||
|
Requires-Python: >=3.7
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: requests
|
||||||
|
Requires-Dist: aiohttp
|
||||||
|
Requires-Dist: dataset
|
10
src/Retoorded.egg-info/SOURCES.txt
Normal file
10
src/Retoorded.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
pyproject.toml
|
||||||
|
setup.cfg
|
||||||
|
src/Retoorded.egg-info/PKG-INFO
|
||||||
|
src/Retoorded.egg-info/SOURCES.txt
|
||||||
|
src/Retoorded.egg-info/dependency_links.txt
|
||||||
|
src/Retoorded.egg-info/entry_points.txt
|
||||||
|
src/Retoorded.egg-info/requires.txt
|
||||||
|
src/Retoorded.egg-info/top_level.txt
|
||||||
|
src/devranta/__init__.py
|
||||||
|
src/devranta/__main__.py
|
1
src/Retoorded.egg-info/dependency_links.txt
Normal file
1
src/Retoorded.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
2
src/Retoorded.egg-info/entry_points.txt
Normal file
2
src/Retoorded.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
retoorded.service = retoorded.bot:run
|
3
src/Retoorded.egg-info/requires.txt
Normal file
3
src/Retoorded.egg-info/requires.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
requests
|
||||||
|
aiohttp
|
||||||
|
dataset
|
1
src/Retoorded.egg-info/top_level.txt
Normal file
1
src/Retoorded.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
devranta
|
12
src/devranta.egg-info/PKG-INFO
Normal file
12
src/devranta.egg-info/PKG-INFO
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: devranta
|
||||||
|
Version: 1.0.0
|
||||||
|
Summary: Async devRant API client made with aiohttp.
|
||||||
|
Author: retoor
|
||||||
|
Author-email: retoor@molodetz.nl
|
||||||
|
License: MIT
|
||||||
|
Requires-Python: >=3.7
|
||||||
|
Description-Content-Type: text/markdown
|
||||||
|
Requires-Dist: requests
|
||||||
|
Requires-Dist: aiohttp
|
||||||
|
Requires-Dist: dataset
|
12
src/devranta.egg-info/SOURCES.txt
Normal file
12
src/devranta.egg-info/SOURCES.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pyproject.toml
|
||||||
|
setup.cfg
|
||||||
|
src/devranta/__init__.py
|
||||||
|
src/devranta/__main__.py
|
||||||
|
src/devranta/api.py
|
||||||
|
src/devranta/tests.py
|
||||||
|
src/devranta.egg-info/PKG-INFO
|
||||||
|
src/devranta.egg-info/SOURCES.txt
|
||||||
|
src/devranta.egg-info/dependency_links.txt
|
||||||
|
src/devranta.egg-info/entry_points.txt
|
||||||
|
src/devranta.egg-info/requires.txt
|
||||||
|
src/devranta.egg-info/top_level.txt
|
1
src/devranta.egg-info/dependency_links.txt
Normal file
1
src/devranta.egg-info/dependency_links.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
2
src/devranta.egg-info/entry_points.txt
Normal file
2
src/devranta.egg-info/entry_points.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[console_scripts]
|
||||||
|
devranta = devranta.__main__:main
|
3
src/devranta.egg-info/requires.txt
Normal file
3
src/devranta.egg-info/requires.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
requests
|
||||||
|
aiohttp
|
||||||
|
dataset
|
1
src/devranta.egg-info/top_level.txt
Normal file
1
src/devranta.egg-info/top_level.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
devranta
|
0
src/devranta/__init__.py
Normal file
0
src/devranta/__init__.py
Normal file
21
src/devranta/__main__.py
Normal file
21
src/devranta/__main__.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from devranta.api import Api
|
||||||
|
|
||||||
|
|
||||||
|
async def main_async():
|
||||||
|
api = Api()
|
||||||
|
async for rant in api.get_rants():
|
||||||
|
print(
|
||||||
|
"{}({}): {}".format(
|
||||||
|
rant["user_username"], rant["user_score"], rant["text"].split(".")[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
asyncio.run(main_async())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
167
src/devranta/api.py
Normal file
167
src/devranta/api.py
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import aiohttp
|
||||||
|
|
||||||
|
|
||||||
|
class Api:
|
||||||
|
|
||||||
|
base_url = "https://www.devrant.io/api/"
|
||||||
|
|
||||||
|
def __init__(self, username=None, password=None):
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.auth = None
|
||||||
|
self.app_id = 3
|
||||||
|
self.user_id = None
|
||||||
|
self.token_id = None
|
||||||
|
self.token_Key = None
|
||||||
|
self.session = None
|
||||||
|
|
||||||
|
def patch_auth(self, request_dict=None):
|
||||||
|
auth_dict = {"app": self.app_id}
|
||||||
|
if self.auth:
|
||||||
|
auth_dict.update(
|
||||||
|
user_id=self.user_id, token_id=self.token_id, token_key=self.token_key
|
||||||
|
)
|
||||||
|
if not request_dict:
|
||||||
|
return auth_dict
|
||||||
|
request_dict.update(auth_dict)
|
||||||
|
return request_dict
|
||||||
|
|
||||||
|
def patch_url(self, url: str):
|
||||||
|
return self.base_url.rstrip("/") + "/" + url.lstrip("/")
|
||||||
|
|
||||||
|
async def login(self):
|
||||||
|
if not self.username or not self.password:
|
||||||
|
raise Exception("No authentication defails supplied.")
|
||||||
|
async with self as session:
|
||||||
|
response = await session.post(
|
||||||
|
url=self.patch_url("users/auth-token"),
|
||||||
|
data={
|
||||||
|
"username": self.username,
|
||||||
|
"password": self.password,
|
||||||
|
"app": self.app_id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
if not obj.get("success"):
|
||||||
|
return False
|
||||||
|
self.auth = obj.get("auth_token")
|
||||||
|
if not self.auth:
|
||||||
|
return False
|
||||||
|
self.user_id = self.auth.get("user_id")
|
||||||
|
self.token_id = self.auth.get("id")
|
||||||
|
self.token_key = self.auth.get("key")
|
||||||
|
return self.auth and True or False
|
||||||
|
|
||||||
|
async def ensure_login(self):
|
||||||
|
if not self.auth:
|
||||||
|
return await self.login()
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def __aenter__(self):
|
||||||
|
self.session = aiohttp.ClientSession()
|
||||||
|
return self.session
|
||||||
|
|
||||||
|
async def __aexit__(self, *args, **kwargs):
|
||||||
|
await self.session.close()
|
||||||
|
self.session = None
|
||||||
|
|
||||||
|
async def post_comment(self, rant_id, comment):
|
||||||
|
response = None
|
||||||
|
if not await self.ensure_login():
|
||||||
|
return False
|
||||||
|
async with self as session:
|
||||||
|
response = await session.post(
|
||||||
|
url=self.patch_url(f"devrant/rants/{rant_id}/comments"),
|
||||||
|
data=self.patch_auth({"comment": comment, "plat": 2}),
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
return obj.get("success", False)
|
||||||
|
|
||||||
|
async def get_comment(self, id_):
|
||||||
|
response = None
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url("comments/" + str(id_)), params=self.patch_auth()
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
|
||||||
|
print(obj)
|
||||||
|
if not obj.get("success"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return obj.get("comment")
|
||||||
|
|
||||||
|
async def get_profile(self, id_):
|
||||||
|
response = None
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url(f"users/{id_}"), params=self.patch_auth()
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
if not obj.get("success"):
|
||||||
|
return None
|
||||||
|
return obj.get("profile")
|
||||||
|
|
||||||
|
async def search(self, term):
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url("devrant/search"),
|
||||||
|
params=self.patch_auth({"term": term}),
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
if not obj.get("success"):
|
||||||
|
return
|
||||||
|
for result in obj.get("results", []):
|
||||||
|
yield result
|
||||||
|
|
||||||
|
async def get_rant(self, id):
|
||||||
|
response = None
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
self.patch_url(f"devrant/rants/{id}"),
|
||||||
|
params=self.patch_auth(),
|
||||||
|
)
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def get_rants(self, sort="recent", limit=20, skip=0):
|
||||||
|
response = None
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url("devrant/rants"),
|
||||||
|
params=self.patch_auth({"sort": sort, "limit": limit, "skip": skip}),
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
if not obj.get("success"):
|
||||||
|
return
|
||||||
|
for rant in obj.get("rants", []):
|
||||||
|
yield rant
|
||||||
|
|
||||||
|
async def get_user_id(self, username):
|
||||||
|
response = None
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url("get-user-id"),
|
||||||
|
params=self.patch_auth({"username": username}),
|
||||||
|
)
|
||||||
|
obj = await response.json()
|
||||||
|
if not obj.get("success"):
|
||||||
|
return None
|
||||||
|
return obj.get("user_id")
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def mentions(self):
|
||||||
|
async for notif in self.notifs:
|
||||||
|
if notif["type"] == "comment_mention":
|
||||||
|
yield notif
|
||||||
|
|
||||||
|
@property
|
||||||
|
async def notifs(self):
|
||||||
|
response = None
|
||||||
|
if not await self.ensure_login():
|
||||||
|
return
|
||||||
|
async with self as session:
|
||||||
|
response = await session.get(
|
||||||
|
url=self.patch_url("users/me/notif-feed"), params=self.patch_auth()
|
||||||
|
)
|
||||||
|
for item in (await response.json()).get("data", {}).get("items", []):
|
||||||
|
yield item
|
31
src/devranta/tests.py
Normal file
31
src/devranta/tests.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from devranta.api import Api
|
||||||
|
|
||||||
|
|
||||||
|
class ApiTestCase(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.api = Api()
|
||||||
|
|
||||||
|
async def async_list(self, generator):
|
||||||
|
list_ = []
|
||||||
|
async for v in generator:
|
||||||
|
list_.append(v)
|
||||||
|
return list_
|
||||||
|
|
||||||
|
async def async_len(self, generator):
|
||||||
|
return len(await self.async_list(generator))
|
||||||
|
|
||||||
|
async def test_get_rants(self):
|
||||||
|
self.assertTrue(await self.async_len(self.api.get_rants()))
|
||||||
|
|
||||||
|
async def test_search(self):
|
||||||
|
self.assertTrue(await self.async_len(self.api.search("retoor")))
|
||||||
|
|
||||||
|
async def test_get_user_id(self):
|
||||||
|
self.assertTrue(await self.api.get_user_id("retoor"))
|
||||||
|
|
||||||
|
async def test_get_profile(self):
|
||||||
|
user_id = await self.api.get_user_id("retoor")
|
||||||
|
self.assertTrue(await self.api.get_profile(user_id))
|
Loading…
Reference in New Issue
Block a user