commit 81f00caa7ea2bb94ee3149ad08755d32ba44d86d Author: retoor Date: Mon Dec 2 17:41:02 2024 +0100 Initial commit. diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 0000000..88bede9 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c44fc3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv +.history +__pycache__ +*.pyc diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24d33c9 --- /dev/null +++ b/Makefile @@ -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 + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..1a885be --- /dev/null +++ b/README.md @@ -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. + diff --git a/dist/Retoorded-1.3.37-py3-none-any.whl b/dist/Retoorded-1.3.37-py3-none-any.whl new file mode 100644 index 0000000..0db55fc Binary files /dev/null and b/dist/Retoorded-1.3.37-py3-none-any.whl differ diff --git a/dist/devranta-1.0.0-py3-none-any.whl b/dist/devranta-1.0.0-py3-none-any.whl new file mode 100644 index 0000000..decff32 Binary files /dev/null and b/dist/devranta-1.0.0-py3-none-any.whl differ diff --git a/dist/devranta-1.0.0.tar.gz b/dist/devranta-1.0.0.tar.gz new file mode 100644 index 0000000..58ae5f9 Binary files /dev/null and b/dist/devranta-1.0.0.tar.gz differ diff --git a/dist/retoorded-1.3.37.tar.gz b/dist/retoorded-1.3.37.tar.gz new file mode 100644 index 0000000..ef40e94 Binary files /dev/null and b/dist/retoorded-1.3.37.tar.gz differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07de284 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..f2a2726 --- /dev/null +++ b/setup.cfg @@ -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 diff --git a/src/Retoorded.egg-info/PKG-INFO b/src/Retoorded.egg-info/PKG-INFO new file mode 100644 index 0000000..38f8448 --- /dev/null +++ b/src/Retoorded.egg-info/PKG-INFO @@ -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 diff --git a/src/Retoorded.egg-info/SOURCES.txt b/src/Retoorded.egg-info/SOURCES.txt new file mode 100644 index 0000000..11f1114 --- /dev/null +++ b/src/Retoorded.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/src/Retoorded.egg-info/dependency_links.txt b/src/Retoorded.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Retoorded.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/Retoorded.egg-info/entry_points.txt b/src/Retoorded.egg-info/entry_points.txt new file mode 100644 index 0000000..7a5037b --- /dev/null +++ b/src/Retoorded.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +retoorded.service = retoorded.bot:run diff --git a/src/Retoorded.egg-info/requires.txt b/src/Retoorded.egg-info/requires.txt new file mode 100644 index 0000000..aaad898 --- /dev/null +++ b/src/Retoorded.egg-info/requires.txt @@ -0,0 +1,3 @@ +requests +aiohttp +dataset diff --git a/src/Retoorded.egg-info/top_level.txt b/src/Retoorded.egg-info/top_level.txt new file mode 100644 index 0000000..56059c8 --- /dev/null +++ b/src/Retoorded.egg-info/top_level.txt @@ -0,0 +1 @@ +devranta diff --git a/src/devranta.egg-info/PKG-INFO b/src/devranta.egg-info/PKG-INFO new file mode 100644 index 0000000..06c226e --- /dev/null +++ b/src/devranta.egg-info/PKG-INFO @@ -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 diff --git a/src/devranta.egg-info/SOURCES.txt b/src/devranta.egg-info/SOURCES.txt new file mode 100644 index 0000000..32eecdc --- /dev/null +++ b/src/devranta.egg-info/SOURCES.txt @@ -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 \ No newline at end of file diff --git a/src/devranta.egg-info/dependency_links.txt b/src/devranta.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/devranta.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/devranta.egg-info/entry_points.txt b/src/devranta.egg-info/entry_points.txt new file mode 100644 index 0000000..fd6d778 --- /dev/null +++ b/src/devranta.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +devranta = devranta.__main__:main diff --git a/src/devranta.egg-info/requires.txt b/src/devranta.egg-info/requires.txt new file mode 100644 index 0000000..aaad898 --- /dev/null +++ b/src/devranta.egg-info/requires.txt @@ -0,0 +1,3 @@ +requests +aiohttp +dataset diff --git a/src/devranta.egg-info/top_level.txt b/src/devranta.egg-info/top_level.txt new file mode 100644 index 0000000..56059c8 --- /dev/null +++ b/src/devranta.egg-info/top_level.txt @@ -0,0 +1 @@ +devranta diff --git a/src/devranta/__init__.py b/src/devranta/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/devranta/__main__.py b/src/devranta/__main__.py new file mode 100644 index 0000000..579435f --- /dev/null +++ b/src/devranta/__main__.py @@ -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() diff --git a/src/devranta/api.py b/src/devranta/api.py new file mode 100644 index 0000000..8e529c3 --- /dev/null +++ b/src/devranta/api.py @@ -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 diff --git a/src/devranta/tests.py b/src/devranta/tests.py new file mode 100644 index 0000000..e8ec7fb --- /dev/null +++ b/src/devranta/tests.py @@ -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))