Initial commit
This commit is contained in:
commit
7c965939d8
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.venv/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
*.py[cod]
|
24
README.md
Normal file
24
README.md
Normal 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
13
main.py
Normal 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
0
pychan/__init__.py
Normal file
75
pychan/api.py
Normal file
75
pychan/api.py
Normal 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
46
pychan/models.py
Normal 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
1
requirements.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
requests
|
Loading…
Reference in New Issue
Block a user