Initial commit

This commit is contained in:
agatha 2023-09-16 14:07:53 -04:00
commit 7c965939d8
7 changed files with 165 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.venv/
.idea/
__pycache__/
*.py[cod]

24
README.md Normal file
View File

@ -0,0 +1,24 @@
# pychan
Python package for interacting with the [4chan JSON API](https://github.com/4chan/4chan-API).
## Usage
```python
from pychan.api import ChanAPI
api = ChanAPI()
# Get list of boards
boards = api.get_boards()
# Get catalog pages for a board
pol_catalog = api.get_catalog('po')
# Get high level stats for threads on a board
pol_threads = api.get_threads('po')
# Get a thread by thread number
thread = api.get_thread('po', 570368)
```
## Notes
Object classes such as Board, Thread, and Post are in development and can be imported from [pychan/models.py](pychan/models.py).

13
main.py Normal file
View File

@ -0,0 +1,13 @@
from pychan.api import ChanAPI
def main():
api = ChanAPI()
boards = api.get_boards()
for board in boards:
catalog = api.get_catalog(board['board'])
print(catalog)
if __name__ == '__main__':
main()

0
pychan/__init__.py Normal file
View File

75
pychan/api.py Normal file
View File

@ -0,0 +1,75 @@
"""
Module for interacting with the 4chan JSON API
"""
import json
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
class InvalidApiResponse(Exception):
pass
class ApiRequestException(Exception):
pass
class ChanAPI:
"""
Wrapper class for interacting with the 4chan JSON API.
Args:
headers (dict, optional): Dictionary containing customer headers to be sent with each request.
proxies (dict, optional): Dictionary containing proxy settings to be used for requests.
Attributes:
base_url (str): The base URL of the API.
session (requests.Session): Persistent HTTP session.
"""
def __init__(self, headers=None, proxies=None):
self.base_url = 'https://a.4cdn.org'
self.session = requests.Session()
retries = Retry(total=3, backoff_factor=1, status_forcelist=[429, 500, 502, 503, 504])
self.session.mount('http://', HTTPAdapter(max_retries=retries))
self.session.mount('https://', HTTPAdapter(max_retries=retries))
if headers:
self.session.headers.update(headers)
if proxies:
self.session.proxies.update(proxies)
def __get(self, endpoint, params=None):
url = self.base_url + endpoint
try:
response = self.session.get(url, params=params)
response.raise_for_status()
except requests.RequestException as e:
raise ApiRequestException(f'Failed to execute GET request: {str(e)}')
try:
return response.json()
except json.JSONDecodeError:
raise InvalidApiResponse('Invalid JSON received')
def get_boards(self):
endpoint = '/boards.json'
response = self.__get(endpoint)
return response['boards']
def get_catalog(self, board):
endpoint = f'/{board}/catalog.json'
response = self.__get(endpoint)
return response
def get_threads(self, board):
endpoint = f'/{board}/threads.json'
response = self.__get(endpoint)
return response
def get_thread(self, board, no):
endpoint = f'/{board}/thread/{no}.json'
response = self.__get(endpoint)
return response

46
pychan/models.py Normal file
View File

@ -0,0 +1,46 @@
import json
from abc import ABC
class ChanObject(ABC):
def __init__(self, data_dict=None):
self.data = {}
if data_dict is not None:
self.load_json(json.dumps(data_dict))
def load_json(self, json_string):
self.data = json.loads(json_string)
def to_json_string(self):
return json.dumps(self.data)
def __getattr__(self, key):
return self.data[key]
def __setattr__(self, key, value):
if key == 'data':
super().__setattr__(key, value)
else:
self.data[key] = value
def __delattr__(self, key):
del self.data[key]
def __repr__(self):
return self.to_json_string()
class Thread(ChanObject):
def __init__(self, data_dict=None):
super().__init__(data_dict)
class Post(ChanObject):
def __init__(self, data_dict=None):
super().__init__(data_dict)
class Board(ChanObject):
def __init__(self, data_dict=None):
super().__init__(data_dict)

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
requests