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