diff --git a/.gitignore b/.gitignore index 75adbe0..a5afc51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,13 @@ # Ignore files .pyc *.pyc +*.pyo +*.pyd *.egg-info __pycache__ # ignore test file pro.py +test.json # build/dist files build/ diff --git a/.pylintrc b/.pylintrc index 9c2648e..2679d1a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,4 +1,7 @@ # Pylint config file +[TYPECHECK] +generated-members=objects + [REPORTS] -msg-template="{path}:{line}:{column} - [{msg_id} in {obj}]: ({symbol}), {msg} +msg-template={path}:{line}:{column} - [{msg_id} in {obj}]: ({symbol}), {msg} diff --git a/.travis.yml b/.travis.yml index bfda3cc..13879e9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,26 +9,28 @@ language: python python: - "2.6" - "2.7" - - "3.2" - "3.3" - "3.4" + - "3.5" branches: - develop - master install: + # Install requests + - pip install -r requirements.txt + # Install pylint - sudo apt-get install pylint - # Install requests - - pip install requests before_script: - # Remove python binari files + # Remove python binary files - rm --recursive --force --verbose *.py[cod] + - rm --recursive --force --verbose pybooru/__pycache__ + # Run pylint + - pylint --errors-only --rcfile=.pylintrc pybooru/ script: - # Run pylint - - pylint --errors-only --rcfile=".pylintrc" pybooru/ # Run test script, but at the moment is a provisional test - python provisional_test.py diff --git a/LICENSE b/LICENSE index 66f504b..86ae240 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2012 - 2015 - Daniel Luque +Copyright (c) 2012 - 2016 - Daniel Luque Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..22fad36 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md changelog.md LICENSE requirements.txt diff --git a/README.md b/README.md index 210b291..45ca0ac 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# Pybooru - Library for Danbooru/Moebooru API. -[![Build Status](https://travis-ci.org/LuqueDaniel/pybooru.svg?branch=master)](https://travis-ci.org/LuqueDaniel/pybooru) +# Pybooru - Package for Danbooru/Moebooru API. +[![PyPI](https://img.shields.io/pypi/v/Pybooru.svg?style=flat-square)](https://pypi.python.org/pypi/Pybooru/) +[![Build Status](https://travis-ci.org/LuqueDaniel/pybooru.svg?branch=master)](https://travis-ci.org/LuqueDaniel/pybooru) [![PyPI](https://img.shields.io/pypi/status/Pybooru.svg?style=flat-square)](https://pypi.python.org/pypi/Pybooru/) +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/LuqueDaniel/pybooru/master/LICENSE) +[![Documentation Status](https://readthedocs.org/projects/pybooru/badge/?version=stable)](http://pybooru.readthedocs.io/en/stable/?badge=stable) -**Pybooru** is a Python library to access API of Danbooru/Moebooru based sites. +**Pybooru** is a Python package to access to the API of Danbooru/Moebooru based sites. -- Version: **3.0.1** +- Version: **4.0.0** - Licensed under: **MIT License** ## Dependencies. -- Python: >= 2.6 or Python: >= 3 -- [requests.](http://docs.python-requests.org/en/latest/) +- Python: >= 2.6 or Python: >= 3.3 +- [requests](http://docs.python-requests.org/en/latest/) ## Installation ### from Python Package Index (Pypi) @@ -16,54 +19,71 @@ Pypi - Python Package Index: [Pybooru on Pypi.](https://pypi.python.org/pypi/Pybooru/) ```bash -sudo pip install Pybooru -``` -or -```bash -sudo easy_install Pybooru +pip install --user Pybooru ``` ### Manual installation ```bash git clone git://github.com/luquedaniel/pybooru.git cd pybooru +pip install --user -r requirements.txt sudo python setup.py build -sudo python setup.py install +python setup.py install ``` ## Examples of use -```python -from pybooru import Pybooru +See [More examples.](https://github.com/LuqueDaniel/pybooru/tree/master/examples) -client = Pybooru('Konachan') +### Danbooru +```python +from pybooru import Danbooru -artists = client.artists('ma') +client = Danbooru('danbooru') +artists = client.artist_list('ma') for artist in artists: print("Name: {0}".format(artist['name'])) ``` -### Login example -#### Default sites +#### Login example ```python -from pybooru import Pybooru +from pybooru import Danbooru -client = Pybooru('Konachan', username='your-username', password='your-password') +client = Danbooru('danbooru', username='your-username', api_key='your-apikey') +client.comment_create(post_id=id, body='Comment content') +``` + +### Moebooru +```python +from pybooru import Moebooru -client.comments_create(post_id=id, comment_body='Comment content') +client = Moebooru('konachan') +artists = client.artist_list(name='neko') + +for artist in artists: + print("Name: {0}".format(artist['name'])) ``` -#### Other sites +#### Login example +##### Default sites ```python -from pybooru import Pybooru +from pybooru import Moebooru -client = Pybooru('konachan.com', username='your-username', password='your-password', - hashString='So-I-Heard-You-Like-Mupkids-?--{0}--') +client = Moebooru('konachan', username='your-username', password='your-password') +client.comment_create(post_id=id, comment_body='Comment content') +``` + +##### Not default sites +```python +from pybooru import Moebooru -client.comments_create(post_id=id, comment_body='Comment content') +client = Moebooru('konachan.com', username='your-username', password='your-password', + hash_string='So-I-Heard-You-Like-Mupkids-?--{0}--') +client.comment_create(post_id=id, comment_body='Comment content') ``` -[More examples.](https://github.com/LuqueDaniel/pybooru/tree/master/examples) +## Documentation +You can consult the documentation on **[Read the Docs](http://pybooru.readthedocs.io/en/stable/)** ## CI Report - https://travis-ci.org/LuqueDaniel/pybooru diff --git a/README.rst b/README.rst index 6400917..6436784 100644 --- a/README.rst +++ b/README.rst @@ -1,27 +1,31 @@ -Pybooru - Library for Danbooru/Moebooru API. +Pybooru - Package for Danbooru/Moebooru API. ============================================ -.. image:: https://travis-ci.org/LuqueDaniel/pybooru.svg?branch=master - :target: https://travis-ci.org/LuqueDaniel/pybooru +.. image:: https://img.shields.io/pypi/v/Pybooru.svg?style=flat-square :target: +.. image:: https://img.shields.io/pypi/status/Pybooru.svg?style=flat-square :target: +.. image:: https://img.shields.io/pypi/l/Pybooru.svg?style=flat-square :target: https://raw.githubusercontent.com/LuqueDaniel/pybooru/master/LICENSE +.. image:: https://img.shields.io/pypi/wheel/Pybooru.svg?style=flat-square :target: +.. image:: https://img.shields.io/pypi/format/Pybooru.svg?style=flat-square :target: -Pybooru is a Python library to access API of Danbooru/Moebooru based sites. Licensed under: **MIT License**. Examples of use --------------- .. code-block:: python + from pybooru import Danbooru - from pybooru import Pybooru - - client = Pybooru('Konachan') - - artists = client.artists('ma') + client = Danbooru('danbooru') + artists = client.artist_list('ma') for artist in artists: print("Name: {0}".format(artist['name'])) - .. +.. + +See more examples of `Danbooru `_ and `Moebooru `_. -See more examples: https://github.com/LuqueDaniel/pybooru/tree/develop/examples +Documentation +------------- +You can consult the documentation on `Read the Docs `_ Changelog --------- diff --git a/changelog.md b/changelog.md index 4b9611b..2c2f7bc 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,20 @@ # Pybooru - Changelog +## Pybooru 4.0.0 - (12/09/2016) +- Added support to Danbooru. +- Now Danbooru and Moebooru are two separed classes. +- Pybooru has been refactored. +- Moebooru (only): added support for API versioning. +- Added PybooruAPIError exception. +- Added **last_call** attribute to Danbooru and Moebooru to store last request information. +- Examples has been updated. +- Added **[documentation](http://pybooru.readthedocs.io/en/stable/)** to Pybooru (with Sphinx). +- Added some tools for Pybooru (tools folder) +- Refactored setup.py. +- End of Python 3.2.x support. +- Fixed parameter comparison (python 2.X only) +- In this version there's a nice amount of improvements. + ## Pybooru 3.0.1 - (01/13/2015) - Minors changes diff --git a/clean_pycs.sh b/clean_pycs.sh deleted file mode 100755 index fb6ed8d..0000000 --- a/clean_pycs.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -find . -iname '*.pyc' -delete diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..0b3ab3a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = Pybooru +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..4d28340 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=Pybooru + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/api_danbooru.rst b/docs/source/api_danbooru.rst new file mode 100644 index 0000000..20db4b0 --- /dev/null +++ b/docs/source/api_danbooru.rst @@ -0,0 +1,6 @@ +Danbooru API Reference +====================== + +.. automodule:: pybooru.api_danbooru + :show-inheritance: + :members: diff --git a/docs/source/api_moebooru.rst b/docs/source/api_moebooru.rst new file mode 100644 index 0000000..4a95f60 --- /dev/null +++ b/docs/source/api_moebooru.rst @@ -0,0 +1,6 @@ +Moebooru API Reference +====================== + +.. automodule:: pybooru.api_moebooru + :show-inheritance: + :members: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..82d1071 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Pybooru documentation build configuration file, created by +# sphinx-quickstart on Wed Dec 7 14:53:54 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', + 'sphinx.ext.mathjax'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Pybooru' +copyright = '2016, Daniel Luque' +author = 'Daniel Luque' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +# Use Read the Docs theme +import sphinx_rtd_theme +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Pyboorudoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'Pybooru.tex', 'Pybooru Documentation', + 'Daniel Luque', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'pybooru', 'Pybooru Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Pybooru', 'Pybooru Documentation', + author, 'Pybooru', 'One line description of project.', + 'Miscellaneous'), +] + +# Order autodoc by source +autodoc_member_order = 'bysource' diff --git a/docs/source/danbooru.rst b/docs/source/danbooru.rst new file mode 100644 index 0000000..fd22c2d --- /dev/null +++ b/docs/source/danbooru.rst @@ -0,0 +1,8 @@ +Danbooru Reference +================== + +.. automodule:: pybooru.danbooru + :show-inheritance: + :members: + :private-members: + :special-members: diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..01c9182 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,35 @@ +.. Pybooru documentation master file, created by + sphinx-quickstart on Wed Dec 7 14:53:54 2016. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Pybooru's documentation! +=================================== + +**Pybooru** is a Python package to access to the API of Danbooru/Moebooru based sites. + +- Version: **4.0.0** +- Licensed under: `MIT License `_ +- Python: >= 2.6 or Python: >= 3.3 + +Dependencies +------------ + +- `requests `_ + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + quick_start_guide + danbooru + api_danbooru + moebooru + api_moebooru + pybooru + +Changelog +--------- + +`Changelog `_ diff --git a/docs/source/moebooru.rst b/docs/source/moebooru.rst new file mode 100644 index 0000000..cbc4ffb --- /dev/null +++ b/docs/source/moebooru.rst @@ -0,0 +1,8 @@ +Moebooru Reference +================== + +.. automodule:: pybooru.moebooru + :show-inheritance: + :members: + :private-members: + :special-members: diff --git a/docs/source/pybooru.rst b/docs/source/pybooru.rst new file mode 100644 index 0000000..63f855d --- /dev/null +++ b/docs/source/pybooru.rst @@ -0,0 +1,29 @@ +Pybooru Reference +====================== + +Pybooru +------- + +.. automodule:: pybooru.pybooru + :show-inheritance: + :members: + :private-members: + :special-members: + +Exceptions +---------- + +.. automodule:: pybooru.exceptions + :show-inheritance: + :members: + :private-members: + :special-members: + +Resources +--------- + +.. automodule:: pybooru.resources + :show-inheritance: + :members: + :private-members: + :special-members: diff --git a/docs/source/quick_start_guide.rst b/docs/source/quick_start_guide.rst new file mode 100644 index 0000000..5423492 --- /dev/null +++ b/docs/source/quick_start_guide.rst @@ -0,0 +1,114 @@ +Quick Start Guide +================= + +Features +-------- + +- Support Danbooru API (version: 2.105.0 - 77e06b6). +- Support Moebooru API (version: 1.13.0+update.3). +- Defult site list. +- JSON responses. +- Custom user-agent. + +Installation +------------ + +You can download and install Pybooru from `Pypi `_ + +.. code-block:: bash + + pip install --user Pybooru +.. + +Install from source +------------------- + +.. code-block:: bash + + git clone git://github.com/luquedaniel/pybooru.git + cd pybooru + pip install --user -r requirements.txt + sudo python setup.py build + python setup.py install +.. + +Example of use with Danbooru +---------------------------- + +.. code-block:: python + + from pybooru import Danbooru + + client = Danbooru('danbooru') + artists = client.artist_list('ma') + + for artist in artists: + print("Name: {0}".format(artist['name'])) +.. + +login (only for functions that **requires login**): + +You can pass two parameters to authenticate: "username" and "api_key". You can see your API key in your profile. + +Example: + +.. code-block:: python + + from pybooru import Danbooru + + client = Danbooru('danbooru', username='your-username', api_key='your-apikey') + client.comment_create(post_id=id, body='Comment content') +.. + +Example of use with Moebooru +---------------------------- + +.. code-block:: python + + from pybooru import Moebooru + + client = Moebooru('konachan') + artists = client.artist_list(name='ma') + + for artist in artists: + print("Name: {0}".format(artist['name'])) +.. + +Some functions may require you to authenticate: + +- **username**: your site username. +- **password**: your password in plain text. +- **hash_string** (requires only for sites that isn't in default site list): a string to be hashed with your password. + +Example using default sites: + +.. code-block:: python + + from pybooru import Moebooru + + client = Moebooru('konachan', username='your-username', password='your-password') + client.comment_create(post_id=id, comment_body='Comment content') +.. + +Example using not default sites: + +.. code-block:: python + + from pybooru import Moebooru + + client = Moebooru('konachan.com', username='your-username', password='your-password', + hash_string='So-I-Heard-You-Like-Mupkids-?--{0}--') + client.comment_create(post_id=id, comment_body='Comment content') +.. + + +See more examples of `Danbooru `_ and `Moebooru `_. + +Default sites list +------------------ + +Pybooru has a list of default sites that allow you to use Pybooru without "site_url" argument: + +- konchan (`Konachan `_) +- yandere (`Yande.re `_) +- danbooru (`Danbooru `_) diff --git a/examples/danbooru/list_tags.py b/examples/danbooru/list_tags.py new file mode 100644 index 0000000..de62d03 --- /dev/null +++ b/examples/danbooru/list_tags.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Danbooru + +client = Danbooru('danbooru') +tags = client.tag_list(order='date') + +for tag in tags: + print("Tag: {0} ----- {1}".format(tag['name'], tag['category'])) diff --git a/examples/danbooru/login_example.py b/examples/danbooru/login_example.py new file mode 100644 index 0000000..fd5f6b7 --- /dev/null +++ b/examples/danbooru/login_example.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Danbooru + +client = Danbooru('danbooru', username='your-username', api_key='yout-api_key') +response = client.comment_create(post_id=id, body='your comment') + +print(client.last_call) diff --git a/examples/danbooru/post_img_path.py b/examples/danbooru/post_img_path.py new file mode 100644 index 0000000..5fb45d3 --- /dev/null +++ b/examples/danbooru/post_img_path.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Danbooru + +client = Danbooru('danbooru') +posts = client.post_list(tags='blue_eyes', limit=5) + +for post in posts: + print("Image path: {0}".format(post['file_url'])) diff --git a/examples/danbooru/wiki_msg.py b/examples/danbooru/wiki_msg.py new file mode 100644 index 0000000..e9564f6 --- /dev/null +++ b/examples/danbooru/wiki_msg.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +client = Moebooru('yandere') +wiki = client.wiki_list(body_matches='great', order='date') + +for page in wiki: + print("Message: {0}".format(page['body'])) diff --git a/examples/list_tags.py b/examples/list_tags.py deleted file mode 100644 index 84bf560..0000000 --- a/examples/list_tags.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from pybooru import Pybooru - -client = Pybooru('Konachan') - -tags = client.tags_list(None, None, 100, 0, 'date') - -for tag in tags: - print("Nombre: {0} ----- {1}".format(tag['name'], tag['type'])) diff --git a/examples/login_example.py b/examples/login_example.py deleted file mode 100644 index f86862b..0000000 --- a/examples/login_example.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from pybooru import Pybooru - -client = Pybooru('Konachan', username='your-username', password='your-password') - -client.comments_create(post_id=id, comment_body='Comment content') diff --git a/examples/moebooru/list_tags.py b/examples/moebooru/list_tags.py new file mode 100644 index 0000000..2d8a0c7 --- /dev/null +++ b/examples/moebooru/list_tags.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +client = Moebooru('Konachan') +tags = client.tag_list(order='name') + +for tag in tags: + print("Tag: {0} ----- {1}".format(tag['name'], tag['type'])) diff --git a/examples/moebooru/login_example.py b/examples/moebooru/login_example.py new file mode 100644 index 0000000..3add5c6 --- /dev/null +++ b/examples/moebooru/login_example.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +# replace login information +client = Moebooru('Konachan', username='your-username', password='your-password') +client.comment_create(post_id=id, comment_body='Comment content') + +print(client.last_call) diff --git a/examples/moebooru/more_examples.py b/examples/moebooru/more_examples.py new file mode 100644 index 0000000..43ee49c --- /dev/null +++ b/examples/moebooru/more_examples.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +client = Moebooru(site_name='Konachan') + +# notes = client.note_list() +# print(notes) + +# wiki = client.wiki_list(query='nice', order='date') +# for msg in wiki: +# print("Message: {0}".format(msg['body'])) + +# posts = client.post_list(tags='blue_eyes', limit=2) +# for post in posts: +# print("Image URL: {0}".format(post['file_url'])) + +# tags = client.tag_list() +# for tag in tags: +# print("Tag: {0} ----- {1}".format(tag['name'], tag['type'])) diff --git a/examples/moebooru/posts_img_url.py b/examples/moebooru/posts_img_url.py new file mode 100644 index 0000000..d708c35 --- /dev/null +++ b/examples/moebooru/posts_img_url.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +client = Moebooru('Konachan') +posts = client.post_list(tags='blue_eyes', limit=10) + +for post in posts: + print("URL image: {0}".format(post['file_url'])) diff --git a/examples/moebooru/wiki_msg.py b/examples/moebooru/wiki_msg.py new file mode 100644 index 0000000..8ea1887 --- /dev/null +++ b/examples/moebooru/wiki_msg.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +from pybooru import Moebooru + +client = Moebooru('yandere') +wiki = client.wiki_list(query='nice', order='date', limit=2, page=1) + +for msg in wiki: + print("Message: {0}".format(msg['body'])) diff --git a/examples/more_examples.py b/examples/more_examples.py deleted file mode 100644 index 4ea29f1..0000000 --- a/examples/more_examples.py +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from pybooru import Pybooru - -client = Pybooru(siteName='Konachan') - -#notes = client.notes_list() - -#print(notes) - -#wiki = client.wiki_list('nice', 'date', 2, 1) - -#for msg in wiki: -# print("Mensaje: {0}".format(msg['body'])) - -#posts = client.posts_list('blue_eyes', 2, 0) - -#for post in posts: -# print("URL imagen: {0}".format(post['file_url'])) - -#tags = client.tags_list(None, None, 100, 0, 'date') - -#print tags -#for tag in tags: -# print("Nombre: {0} ----- {1}".format(tag['name'], tag['type'])) diff --git a/examples/posts_img_url.py b/examples/posts_img_url.py deleted file mode 100644 index 7e8e92a..0000000 --- a/examples/posts_img_url.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from pybooru import Pybooru - -client = Pybooru('Konachan') - -posts = client.posts_list('blue_eyes', 10) - -for post in posts: - print("URL imagen: {0}".format(post['file_url'])) diff --git a/examples/wiki_msg.py b/examples/wiki_msg.py deleted file mode 100644 index 956c667..0000000 --- a/examples/wiki_msg.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals -from pybooru import Pybooru - -client = Pybooru('yandere') - -wiki = client.wiki_list('nice', 'date', 2, 1) - -for msg in wiki: - print("Mensaje: {0}".format(msg['body'])) diff --git a/provisional_test.py b/provisional_test.py index b1e70f4..73275af 100644 --- a/provisional_test.py +++ b/provisional_test.py @@ -1,7 +1,18 @@ # encoding: utf-8 -from pybooru import Pybooru +from __future__ import print_function +from pybooru import Danbooru +from pybooru import Moebooru -# client = Pybooru(site_url='konachan.com') -client = Pybooru(site_url="http://www.konachan.com") +konachan = Moebooru("konachan") -tags = client.tags_list(None, None, 100, 0, 'date') +kona_tags = konachan.tag_list(order='date') +print(konachan.last_call) +kona_post = konachan.post_list() +print(konachan.last_call) + +danbooru = Danbooru('danbooru') + +dan_tags = danbooru.tag_list(order='name') +print(danbooru.last_call) +dan_post = danbooru.post_list(tags="computer") +print(danbooru.last_call) diff --git a/pybooru/__init__.py b/pybooru/__init__.py index ffc65df..5790ca8 100644 --- a/pybooru/__init__.py +++ b/pybooru/__init__.py @@ -1,18 +1,30 @@ # -*- coding: utf-8 -*- """ -Pybooru. --------- - -Pybooru is a Python library to access API of Danbooru/Moebooru based sites. +Pybooru +------- +Pybooru is a API client written in Python for Danbooru and Moebooru based sites. Under MIT license. + +Pybooru requires "requests" package to work. + +Pybooru modules: + pybooru -- Main module of Pybooru, contains Pybooru class. + moebooru -- Contains Moebooru main class. + danbooru -- Contains Danbooru main class. + api_moebooru -- Contains all Moebooru API functions. + api_danbooru -- Contains all Danbooru API functions. + exceptions -- Manages and builds Pybooru errors messages. + resources -- Contains all resources for Pybooru. """ -__author__ = "Daniel Luque " -__version__ = "3.0.1" -__url__ = "http://github.com/LuqueDaniel/pybooru" +__version__ = "4.0.0" +__license__ = "MIT" +__source_url__ = "http://github.com/LuqueDaniel/pybooru" +__author__ = "Daniel Luque " # pybooru imports -from .pybooru import Pybooru +from .moebooru import Moebooru +from .danbooru import Danbooru from .exceptions import PybooruError diff --git a/pybooru/api_danbooru.py b/pybooru/api_danbooru.py new file mode 100644 index 0000000..7d7f605 --- /dev/null +++ b/pybooru/api_danbooru.py @@ -0,0 +1,1171 @@ +# -*- coding: utf-8 -*- + +"""pybooru.api_danbooru + +This module contains all API calls of Danbooru. + +Classes: + DanbooruApi_Mixin -- Contains all API calls. +""" + +# __future__ imports +from __future__ import absolute_import + +# pybooru imports +from .exceptions import PybooruAPIError + + +class DanbooruApi_Mixin(object): + """Contains all Danbooru API calls. + + * API Version: v2.105.0 (77e06b6) + * Doc: https://danbooru.donmai.us/wiki_pages/43568 + """ + + def post_list(self, **params): + """Get a list of posts. + + Parameters: + :param limit: How many posts you want to retrieve. There is a hard + limit of 100 posts per request. + :param page: The page number. + :param tags: The tags to search for. Any tag combination that works + on the web site will work here. This includes all the + meta-tags. + :param raw: When this parameter is set the tags parameter will not + be parsed for aliased tags, metatags or multiple tags, + and will instead be parsed as a single literal tag. + """ + return self._get('posts.json', params) + + def post_show(self, post_id): + """Get a post. + + Parameters: + :param post_id: REQUIRED Where post_id is the post id. + """ + return self._get('/posts/{0}.json'.format(post_id)) + + def post_update(self, post_id, tag_string=None, rating=None, source=None, + parent_id=None): + """Update a specific post (Requires login). + + Parameters: + :param post_id: REQUIRED The id number of the post to update. + :param tag_string: A space delimited list of tags. + :param rating: The rating for the post. Can be: safe, questionable, + or explicit. + :param source: If this is a URL, Danbooru will download the file. + :param parent_id: The ID of the parent post. + """ + params = { + 'post[tag_string]': tag_string, + 'post[rating]': rating, + 'ost[source]': source, + 'post[parent_id]': parent_id + } + return self._get('/posts/{0}.json'.format(post_id), params, 'PUT', + auth=True) + + def post_revert(self, post_id, version_id): + """Function to reverts a post to a previous version (Requires login). + + Parameters: + :param post_id: REQUIRED post id. + :param version_id: REQUIRED The post version id to revert to. + """ + return self._get('/posts/{0}/revert.json'.format(post_id), + {'version_id': version_id}, 'PUT', auth=True) + + def post_copy_notes(self, post_id, other_post_id): + """Function to copy notes (requires login). + + Parameters: + :param post_id: REQUIRED Post id. + :param other_post_id: REQUIRED The id of the post to copy notes to. + """ + return self._get('/posts/{0}/copy_notes.json'.format(post_id), + {'other_post_id': other_post_id}, 'PUT', auth=True) + + def post_vote(self, post_id, score): + """Action lets you vote for a post (Requires login). + Danbooru: Post votes/create. + + Parameters: + :param post_id: REQUIRED Ppost id. + :param score: REQUIRED Can be: up, down. + """ + return self._get('/posts/{0}/votes.json'.format(post_id), + {'score': score}, 'POST', auth=True) + + def post_flag_list(self, creator_id=None, creator_name=None, post_id=None, + reason_matches=None, is_resolved=None, category=None): + """Function to flag a post (Requires login). + + Parameters: + :param creator_id: The user id of the flag's creator. + :param creator_name: The name of the flag's creator. + :param post_id: The post id if the flag. + :param reason_matches: Flag's reason. + :param is_resolved: Can be 1 or 0. + :param category: unapproved/banned/normal. + """ + params = { + 'search[creator_id]': creator_id, + 'search[creator_name]': creator_name, + 'search[post_id]': post_id, + 'search[reason_matches]': reason_matches, + 'search[is_resolved]': is_resolved, + 'search[category]': category + } + return self._get('post_flags.json', params, auth=True) + + def post_flag_create(self, post_id, reason): + """Function to flag a post. + + Parameters: + :param post_id: REQUIRED The id of the flagged post. + :param reason: REQUIRED The reason of the flagging. + """ + params = {'post_flag[post_id]': post_id, 'post_flag[reason]': reason} + return self._get('post_flags.json', params, 'POST', auth=True) + + def post_appeals_list(self, creator_id=None, creator_name=None, + post_id=None): + """Function to return list of appeals (Requires login). + + Parameters: + :param creator_id: The user id of the appeal's creator. + :param creator_name: The name of the appeal's creator. + :param post_id: The post id if the appeal. + """ + params = { + 'creator_id': creator_id, + 'creator_name': creator_name, + 'post_id': post_id + } + return self._get('post_appeals.json', params, auth=True) + + def post_appeals_create(self, post_id, reason): + """Function to create appeals (Requires login). + + Parameters: + :param post_id: REQUIRED The id of the appealed post. + :param reason: REQUIRED The reason of the appeal. + """ + params = {'post_appeal[post_id]': post_id, + 'post_appeal[reason]': reason} + return self._get('post_appeals.json', params, 'POST', auth=True) + + def post_versions(self, updater_name=None, updater_id=None, + post_id=None, start_id=None): + """Get list of post versions. + + Parameters: + :param updater_name: + :param updater_id: + :param post_id: + :param start_id: + """ + params = { + 'search[updater_name]': updater_name, + 'search[updater_id]': updater_id, + 'search[post_id]': post_id, + 'search[start_id]': start_id + } + return self._get('post_versions.json', params) + + def upload_list(self, uploader_id=None, uploader_name=None, source=None): + """Search and eturn a uploads list (Requires login). + + Parameters: + :param uploader_id: The id of the uploader. + :param uploader_name: The name of the uploader. + :param source: The source of the upload (exact string match). + """ + params = { + 'search[uploader_id]': uploader_id, + 'search[uploader_name]': uploader_name, + 'search[source]': source + } + return self._get('uploads.json', params, auth=True) + + def upload_show(self, upload_id): + """Get a upload (Requires login). + + Parameters: + :param upload_id: Where upload_id is the upload id. + """ + return self._get('uploads/{0}.json'.format(upload_id), auth=True) + + def upload_create(self, tag_string, rating, file_=None, source=None, + parent_id=None): + """Function to create a new upload (Requires login). + + Parameters: + :param tag_string: REQUIRED The tags. + :param rating: REQUIRED Can be: safe, questionable, explicit. + :param file_: The file data encoded as a multipart form. + :param source: The source URL. + :param parent_id: The parent post id. + """ + if file_ or source is not None: + params = { + 'upload[source]': source, + 'upload[rating]': rating, + 'upload[parent_id]': parent_id, + 'upload[tag_string]': tag_string + } + file_ = {'upload[file]': open(file_, 'rb')} + return self._get('uploads.json', params, 'POST', auth=True, + file_=file_) + else: + raise PybooruAPIError("'file_' or 'source' is required.") + + def comment_list(self, group_by, body_matches=None, post_id=None, + post_tags_match=None, creator_name=None, creator_id=None, + tags=None): + """Return a list of comments. + + Parameters: + :param group_by: + Can be 'comment', 'post'. Comment will return recent comments. + Post will return posts that have been recently commented on. + + :param group_by=comment: + :param body_matches: Body contains the given terms. + :param post_id: Post id. + :param post_tags_match: The comment's post's tags match + the given terms. + :param group_by=post: + :param tags: The post's tags match the given terms. + :param creator_name: The name of the creator (exact match) + :param creator_id: The user id of the creator + """ + params = {'group_by': group_by} + if group_by == 'comment': + params['search[body_matches]'] = body_matches + params['search[post_id]'] = post_id + params['search[post_tags_match]'] = post_tags_match + params['search[creator_name]'] = creator_name + params['search[creator_id]'] = creator_id + elif group_by == 'post': + params['tags'] = tags + else: + raise PybooruAPIError("'group_by' must be 'comment' or post") + return self._get('comments.json', params) + + def comment_create(self, post_id, body, do_not_bump_post=None): + """Action to lets you create a comment (Requires login). + + Parameters: + :param post_id: REQUIRED. + :param body: REQUIRED. + :param do_not_bump_post: Set to 1 if you do not want the post to be + bumped to the top of the comment listing. + """ + params = { + 'comment[post_id]': post_id, + 'comment[body]': body, + 'comment[do_not_bump_post]': do_not_bump_post + } + return self._get('comments.json', params, 'POST', auth=True) + + def comment_update(self, comment_id, body, do_not_bump_post=None): + """Function to update a comment (Requires login). + + Parameters: + :param comment_id: REQUIRED comment id. + :param body: REQUIRED. + :param do_not_bump_post: Set to 1 if you do not want the post to be + bumped to the top of the comment listing. + """ + params = { + 'comment[body]': body, + 'comment[do_not_bump_post]': do_not_bump_post + } + return self._get('comments/{0}.json'.format(comment_id), params, 'PUT', + auth=True) + + def comment_show(self, comment_id): + """Get a specific comment. + + Parameters: + :param comment_id: REQUIRED the id number of the comment to + retrieve. + """ + return self._get('comments/{0}.json'.format(comment_id)) + + def comment_delete(self, comment_id): + """Remove a specific comment (Requires login). + + Parameters: + :param comment_id: REQUIRED the id number of the comment to remove. + """ + return self._get('comments/{0}.json'.format(comment_id), + method='DELETE', auth=True) + + def favorite_list(self, user_id=None): + """Return a list with favorite posts (Requires login). + + Parameters: + :param user_id: Which user's favorites to show. Defaults to your + own if not specified. + """ + return self._get('favorites.json', {'user_id': user_id}, auth=True) + + def favorite_add(self, post_id): + """Add post to favorite (Requires login). + + Parameters: + :param post_id: REQUIRED The post to favorite. + """ + return self._get('favorites.json', {'post_id': post_id}, 'POST', + auth=True) + + def favorite_remove(self, post_id): + """Remove a post from favorites (Requires login). + + Parameters: + :param post_id: REQUIRED where post_id is the post id. + """ + return self._get('favorites/{0}.json'.format(post_id), method='DELETE', + auth=True) + + def dmail_list(self, message_matches=None, to_name=None, to_id=None, + from_name=None, from_id=None, read=None): + """Return list of Dmails. You can only view dmails you own + (Requires login). + + Parameters: + :param message_matches: The message body contains the given terms. + :param to_name: The recipient's name. + :param to_id: The recipient's user id. + :param from_name: The sender's name. + :param from_id: The sender's user id. + :param read: Can be: true, false. + """ + params = { + 'search[message_matches]': message_matches, + 'search[to_name]': to_name, + 'search[to_id]': to_id, + 'search[from_name]': from_name, + 'search[from_id]': from_id, + 'search[read]': read + } + return self._get('dmails.json', params, auth=True) + + def dmail_show(self, dmail_id): + """Return a specific dmail. You can only view dmails you own + (Requires login). + + Parameters: + :param dmail_id: REQUIRED where dmail_id is the dmail id. + """ + return self._get('dmails/{0}.json'.format(dmail_id), auth=True) + + def dmail_create(self, to_name, title, body): + """Create a dmail (Requires login) + + Parameters: + :param to_name: REQUIRED the recipient's name. + :param title: REQUIRED the title of the message. + :param body: REQUIRED the body of the message. + """ + params = { + 'dmail[to_name]': to_name, + 'dmail[title]': title, + 'dmail[body]': body + } + return self._get('dmails.json', params, 'POST', auth=True) + + def dmail_delete(self, dmail_id): + """Delete a dmail. You can only delete dmails you own (Requires login). + + Parameters: + :param dmail_id: REQUIRED where dmail_id is the dmail id. + """ + return self._get('dmails/{0}.json'.format(dmail_id), method='DELETE', + auth=True) + + def artist_list(self, query=None, artist_id=None, creator_name=None, + creator_id=None, is_active=None, is_banned=None, + empty_only=None, order=None): + """Get an artist of a list of artists. + + Parameters: + :param query: + This field has multiple uses depending on what the query starts + with: + + 'http:desired_url': + Search for artist with this URL. + 'name:desired_url': + Search for artists with the given name as their base name. + 'other:other_name': + Search for artists with the given name in their other + names. + 'group:group_name': + Search for artists belonging to the group with the given + name. + 'status:banned': + Search for artists that are banned. else Search for the + given name in the base name and the other names. + :param artist_id: The artist id. + :param creator_name: Exact creator name. + :param creator_id: Artist creator id. + :param is_active: Can be: true, false + :param is_banned: Can be: true, false + :param empty_only: Search for artists that have 0 posts. Can be: + true + :param order: Can be: name, updated_at. + """ + params = { + 'search[name]': query, + 'search[id]': artist_id, + 'search[creator_name]': creator_name, + 'search[creator_id]': creator_id, + 'search[is_active]': is_active, + 'search[is_banned]': is_banned, + 'search[empty_only]': empty_only, + 'search[order]': order + } + return self._get('artists.json', params) + + def artist_show(self, artist_id): + """Return a specific artist. + + Parameters: + :param artist_id:param : REQUIRED where artist_id is the artist id. + """ + return self._get('artists/{0}.json'.format(artist_id)) + + def artist_create(self, name, other_names_comma=None, group_name=None, + url_string=None): + """Function to create an artist (Requires login) (UNTESTED). + + Parameters: + :param name: REQUIRED. + :param other_names_comma: List of alternative names for this + artist, comma delimited. + :param group_name: The name of the group this artist belongs to. + :param url_string: List of URLs associated with this artist, + whitespace or newline delimited. + """ + params = { + 'artist[name]': name, + 'artist[other_names_comma]': other_names_comma, + 'artist[group_name]': group_name, + 'artist[url_string]': url_string + } + return self.get('artists.json', params, method='POST', auth=True) + + def artist_update(self, artist_id, name=None, other_names_comma=None, + group_name=None, url_string=None): + """Function to update artists (Requires login) (UNTESTED). + + Parameters: + :param artist_id: REQUIRED where artist_id is the artist id. + :param name: + :param other_names_comma: List of alternative names for this + artist, comma delimited. + :param group_name: The name of the group this artist belongs to. + :param url_string: List of URLs associated with this artist, + whitespace or newline delimited. + """ + params = { + 'artist[name]': name, + 'artist[other_names_comma]': other_names_comma, + 'artist[group_name]': group_name, + 'artist[url_string]': url_string + } + return self .get('artists/{0}.json'.format(artist_id), params, + method='PUT', auth=True) + + def artist_delete(self, artist_id): + """Action to lets you delete an artist (Requires login) (UNTESTED). + + Parameters: + :param artist_id: where artist_id is the artist id. + """ + return self._get('artists/{0}.json'.format(artist_id), method='DELETE', + auth=True) + + def artist_banned(self): + """This is a shortcut for an artist listing search with + name=status:banned.:param """ + return self._get('artists/banned.json') + + def artist_revert(self, artist_id, version_id): + """Revert an artist (Requires login) (UNTESTED). + + Parameters: + :param artist_id: REQUIRED The artist id. + :param version_id: REQUIRED The artist version id to revert to. + """ + params = {'version_id': version_id} + return self._get('artists/{0}/revert.json'.format(artist_id), params, + method='PUT', auth=True) + + def artist_versions(self, name=None, updater_id=None, artist_id=None, + is_active=None, is_banned=None, order=None): + """Get list of artist versions. + + Parameters: + :param name: + :param updater_id: + :param artist_id: + :param is_active: Can be: true, false. + :param is_banned: Can be: true, false. + :param order: Can be: name, date. + """ + params = { + 'search[name]': name, + 'search[updater_id]': updater_id, + 'search[artist_id]': artist_id, + 'search[is_active]': is_active, + 'search[is_banned]': is_banned, + 'search[order]': order + } + return self._get('artist_versions.json', params) + + def artist_commentary_list(self, text_matches=None, post_id=None, + post_tags_match=None, original_present=None, + translated_present=None): + """list artist commentary. + + Parameters: + :param text_matches: + :param post_id: + :param post_tags_match: The commentary's post's tags match the + giventerms. Meta-tags not supported. + :param original_present: Can be: yes, no. + :param translated_present: Can be: yes, no. + """ + params = { + 'search[text_matches]': text_matches, + 'search[post_id]': post_id, + 'search[post_tags_match]': post_tags_match, + 'search[original_present]': original_present, + 'search[translated_present]': translated_present + } + return self._get('artist_commentaries.json', params) + + def artist_commentary_create_update(self, post_id, original_title, + original_description, translated_title, + translated_description): + """Create or update artist commentary (Requires login) (UNTESTED). + + Parameters: + :param post_id: REQUIRED. + :param original_title: + :param original_description: + :param translated_title: + :param translated_description: + """ + params = { + 'artist_commentary[post_id]': post_id, + 'artist_commentary[original_title]': original_title, + 'artist_commentary[original_description]': original_description, + 'artist_commentary[translated_title]': translated_title, + 'artist_commentary[translated_description]': translated_description + } + return self._get('artist_commentaries/create_or_update.json', params, + method='POST', auth=True) + + def artist_commentary_revert(self, id_, version_id): + """Revert artist commentary (Requires login) (UNTESTED). + + Parameters: + :param id_: REQUIRED The artist commentary id. + :param version_id: REQUIRED The artist commentary version id to + revert to. + """ + params = {'version_id': version_id} + return self._get('artist_commentaries/{0}/revert.json'.format(id_), + params, method='PUT', auth=True) + + def artist_commentary_versions(self, post_id, updater_id): + """Return list of artist commentary versions. + + Parameters: + :param updater_id: REQUIRED. + :param post_id: REQUIRED. + """ + params = { + 'search[updater_id]': updater_id, + 'search[post_id]': post_id + } + return self._get('artist_commentary_versions.json', params) + + def note_list(self, group_by=None, body_matches=None, post_id=None, + post_tags_match=None, creator_name=None, creator_id=None): + """Return list of notes. + + Parameters: + :param group_by: Can be: note, post (by default post). + :param body_matches: The note's body matches the given terms. + :param post_id: A specific post. + :param post_tags_match: The note's post's tags match the given + terms. + :param creator_name: The creator's name. Exact match. + :param creator_id: The creator's user id. + """ + params = { + 'group_by': group_by, + 'search[body_matches]': body_matches, + 'search[post_id]': post_id, + 'search[post_tags_match]': post_tags_match, + 'search[creator_name]': creator_name, + 'search[creator_id]': creator_id + } + return self._get('notes.json', params) + + def note_show(self, note_id): + """Get a specific note. + + Parameters: + :param :param note_id: REQUIRED Where note_id is the note id. + """ + return self._get('notes/{0}.json'.format(note_id)) + + def note_create(self, post_id, coor_x, coor_y, width, height, body): + """Function to create a note (Requires login) (UNTESTED). + + Parameters: + :param post_id: REQUIRED + :param coor_x: REQUIRED The x coordinates of the note in pixels, + with respect to the top-left corner of the image. + :param coor_y: REQUIRED The y coordinates of the note in pixels, + with respect to the top-left corner of the image. + :param width: REQUIRED The width of the note in pixels. + :param height: REQUIRED The height of the note in pixels. + :param body: REQUIRED The body of the note. + """ + params = { + 'note[post_id]': post_id, + 'note[x]': coor_x, + 'note[y]': coor_y, + 'note[width]': width, + 'note[height]': height, + 'note[body]': body + } + return self._get('notes.json', params, method='POST', auth=True) + + def note_update(self, note_id, coor_x=None, coor_y=None, width=None, + height=None, body=None): + """Function to update a note (Requires login) (UNTESTED). + + Parameters: + :param note_id: REQUIRED Where note_id is the note id. + :param coor_x: REQUIRED The x coordinates of the note in pixels, + with respect to the top-left corner of the image. + :param coor_y: REQUIRED The y coordinates of the note in pixels, + with respect to the top-left corner of the image. + :param width: REQUIRED The width of the note in pixels. + :param height: REQUIRED The height of the note in pixels. + :param body: REQUIRED The body of the note. + """ + params = { + 'note[x]': coor_x, + 'note[y]': coor_y, + 'note[width]': width, + 'note[height]': height, + 'note[body]': body + } + return self._get('notes/{0}.jso'.format(note_id), params, method='PUT', + auth=True) + + def note_delete(self, note_id): + """delete a specific note (Requires login) (UNTESTED). + + Parameters: + :param note_id: REQUIRED Where note_id is the note id. + """ + return self._get('notes/{0}.json'.format(note_id), method='DELETE', + auth=True) + + def note_revert(self, note_id, version_id): + """Function to revert a specific note (Requires login) (UNTESTED). + + Parameters: + :param note_id: REQUIRED Where note_id is the note id. + :param version_id: REQUIRED The note version id to revert to. + """ + return self._get('notes/{0}/revert.json'.format(note_id), + {'version_id': version_id}, method='PUT', auth=True) + + def note_versions(self, updater_id=None, post_id=None, note_id=None): + """Get list of note versions. + + Parameters: + :param updater_id: + :param post_id: + :param note_id: + """ + params = { + 'search[updater_id]': updater_id, + 'search[post_id]': post_id, + 'search[note_id]': note_id + } + return self._get('note_versions.json', params) + + def user_list(self, name=None, min_level=None, max_level=None, level=None, + user_id=None, order=None): + """Function to get a list of users or a specific user. + + Levels: + Users have a number attribute called level representing their role. + The current levels are: + + Member 20, Gold 30, Platinum 31, Builder 32, Contributor 33, + Janitor 35, Moderator 40 and Admin 50. + + Parameters: + :param name: Supports patterns. + :param min_level: Minimum level (see section on levels). + :param max_level: Maximum level (see section on levels). + :param level: Current level (see section on levels). + :param user_id: The user id. + :param order: Can be: 'name', 'post_upload_count', 'note_count', + 'post_update_count', 'date'. + """ + params = { + 'search[name]': name, + 'search[min_level]': min_level, + 'search[max_level]': max_level, + 'search[level]': level, + 'search[id]': user_id, + 'search[order]': order + } + return self._get('users.json', params) + + def user_show(self, user_id): + """Get a specific user. + + Parameters: + :param user_id: REQUIRED Where user_id is the user id. + """ + return self._get('users/{0}.json'.format(user_id)) + + def pool_list(self, name_matches=None, description_matches=None, + creator_name=None, creator_id=None, is_active=None, + order=None, category=None): + """Get a list of pools. + + Parameters: + :param name_matches: + :param description_matches: + :param creator_name: + :param creator_id: + :param is_active: Can be: true, false. + :param order: Can be: name, created_at, post_count, date. + :param category: Can be: series, collection + """ + params = { + 'search[name_matches]': name_matches, + 'search[description_matches]': description_matches, + 'search[creator_name]': creator_name, + 'search[creator_id]': creator_id, + 'search[is_active]': is_active, + 'search[order]': order, + 'search[category]': category + } + return self._get('pools.json', params) + + def pool_show(self, pool_id): + """Get a specific pool. + + Parameters: + :param pool_id: REQUIRED Where pool_id is the pool id. + """ + return self._get('pools/{0}.json'.format(pool_id)) + + def pool_create(self, name, description, category): + """Function to create a pool (Requires login) (UNTESTED). + + Parameters: + :param name: REQUIRED. + :param description: REQUIRED. + :param category: Can be: series, collection. + """ + params = { + 'pool[name]': name, + 'pool[description]': description, + 'pool[category]': category + } + return self._get('pools.json', params, method='POST', auth=True) + + def pool_update(self, pool_id, name=None, description=None, post_ids=None, + is_active=None, category=None): + """Update a pool (Requires login) (UNTESTED). + + Parameters: + :param pool_id: REQUIRED Where pool_id is the pool id. + :param name: + :param description: + :param post_ids: List of space delimited post ids. + :param is_active: Can be: 1, 0 + :param category: Can be: series, collection + """ + params = { + 'pool[name]': name, + 'pool[description]': description, + 'pool[post_ids]': post_ids, + 'pool[is_active]': is_active, + 'pool[category]': category + } + return self._get('pools/{0}.json'.format(pool_id), params, + method='PUT', auth=True) + + def pool_delete(self, pool_id): + """Delete a pool (Requires login) (UNTESTED). + + Parameters: + :param pool_id: REQUIRED Where pool_id is the pool id. + """ + return self._get('pools/{0}.json'.format(pool_id), method='DELETE', + auth=True) + + def pool_undelete(self, pool_id): + """Undelete a specific poool (Requires login) (UNTESTED). + + Parameters: + :param pool_id: REQUIRED Where pool_id is the pool id. + """ + return self._get('pools/{0}/undelete.json'.format(pool_id), + method='POST', auth=True) + + def pool_revert(self, pool_id, version_id): + """Function to revert a specific pool (Requires login) (UNTESTED). + + Parameters: + :param pool_id: REQUIRED Where pool_id is the pool id. + :param version_id: REQUIRED. + """ + params = {'version_id': version_id} + return self._get('pools/{0}/revert.json'.format(pool_id), params, + method='PUT', auth=True) + + def pool_versions(self, updater_id=None, updater_name=None, pool_id=None): + """Get list of pool versions. + + Parameters: + :param updater_id: + :param updater_name: + :param pool_id: + """ + params = { + 'search[updater_id]': updater_id, + 'search[updater_name]': updater_name, + 'search[pool_id]': pool_id + } + return self._get('pool_versions.json', params) + + def tag_list(self, name_matches=None, category=None, hide_empty=None, + order=None, has_wiki=None, name=None): + """Get a list of tags. + + Parameters: + :param name_matches: + :param category: Can be: 0, 1, 3, 4 (general, artist, copyright, + character respectively) + :param hide_empty: Can be: yes, no. Excludes tags with 0 posts + when "yes". + :param order: Can be: name, date, count + :param has_wiki: Can be: yes, no + :param name: Allows searching for multiple tags with exact given + names, separated by commas. e.g. + search[name]=touhou,original,k-on! would return the + three listed tags. + """ + params = { + 'search[name_matches]': name_matches, + 'search[category]': category, + 'search[hide_empty]': hide_empty, + 'search[order]': order, + 'search[has_wiki]': has_wiki, + 'search[name]': name + } + return self._get('tags.json', params) + + def tag_aliases(self, name_matches=None, antecedent_name=None, + tag_id=None): + """Get tags aliases. + + Parameters: + :param name_matches: Match antecedent or consequent name. + :param antecedent_name: Match antecedent name (exact match). + :param tag_id: The tag alias id. + """ + params = { + 'search[name_matches]': name_matches, + 'search[antecedent_name]': antecedent_name, + 'search[id]': tag_id + } + return self._get('tag_aliases.json', params) + + def tag_implications(self, name_matches=None, antecedent_name=None, + tag_id=None): + """Get tags implications. + + Parameters: + :param name_matches: Match antecedent or consequent name. + :param antecedent_name: Match antecedent name (exact match). + :param tag_id: The tag implication id. + """ + params = { + 'search[name_matches]': name_matches, + 'search[antecedent_name]': antecedent_name, + 'search[id]': tag_id + } + return self._get('tag_implications.json', params) + + def tag_related(self, query, category=None): + """Get related tags. + + Parameters: + :param query: REQUIRED The tag to find the related tags for. + :param category: If specified, show only tags of a specific + category. Can be: General 0, Artist 1, Copyright + 3 and Character 4. + """ + params = {'query': query, 'category': category} + return self._get('related_tag.json', params) + + def wiki_list(self, title=None, creator_id=None, body_matches=None, + other_names_match=None, creator_name=None, order=None): + """Function to retrieves a list of every wiki page. + + Parameters: + :param title: + :param creator_id: + :param body_matches: + :param other_names_match: + :param creator_name: + :param order: Can be: date, title. + """ + params = { + 'search[title]': title, + 'search[creator_id]': creator_id, + 'search[body_matches]': body_matches, + 'search[other_names_match]': other_names_match, + 'search[creator_name]': creator_name, + 'search[order]': order + } + return self._get('wiki_pages.json', params) + + def wiki_show(self, wiki_page_id): + """Retrieve a specific page of the wiki. + + Parameters: + :param wiki_page_id: REQUIRED Where page_id is the wiki page id. + """ + return self._get('wiki_pages/{0}.json'.format(wiki_page_id)) + + def wiki_create(self, title, body, other_names=None): + """Action to lets you create a wiki page (Requires login) (UNTESTED). + + Parameters: + :param title: REQUIRED. + :param body: REQUIRED. + :param other_names: + """ + params = { + 'wiki_page[title]': title, + 'wiki_page[body]': body, + 'wiki_page[other_names]': other_names + } + return self._get('wiki_pages.json', params, method='POST', auth=True) + + def wiki_update(self, wiki_page_id, title=None, body=None, + other_names=None): + """Action to lets you update a wiki page (Requires login) (UNTESTED). + + Parameters: + :param wiki_page_id: REQURIED Whre page_id is the wiki page id. + :param title: + :param body: + :param other_names: + """ + params = { + 'wiki_page[title]': title, + 'wiki_page[body]': body, + 'wiki_page[other_names]': other_names + } + return self._get('wiki_pages/{0}.json'.format(wiki_page_id), params, + method='PUT', auth=True) + + def wiki_revert(self, wiki_page_id, version_id): + """Revert page to a previeous version (Requires login) (UNTESTED). + + Parameters: + :param wiki_page_id: REQUIRED Where page_id is the wiki page id. + :param version_id: REQUIRED. + """ + return self._get('wiki_pages/{0}/revert.json'.format(wiki_page_id), + {'version_id': version_id}, method='PUT', auth=True) + + def wiki_versions(self, wiki_page_id, updater_id): + """Return a list of wiki page version. + + Parameters: + :param wiki_page_id: REQUIRED. + :param updater_id: REQUIRED. + """ + params = { + 'earch[updater_id]': updater_id, + 'search[wiki_page_id]': wiki_page_id + } + return self._get('wiki_page_versions.json', params) + + def wiki_versions_show(self, wiki_page_id): + """Return a specific wiki page version. + + Parameters: + :param wiki_page_id: REQUIRED Where wiki_page_id is the wiki page + version id. + """ + return self._get('wiki_page_versions/{0}.json'.format(wiki_page_id)) + + def forum_topic_list(self, title_matches=None, title=None, + category_id=None): + """Function to get forum topics. + + Parameters: + :param title_matches: Search body for the given terms. + :param title: Exact title match. + :param category_id: Can be: 0, 1, 2 (General, Tags, Bugs & Features + respectively) + """ + params = { + 'search[title_matches]': title_matches, + 'search[title]': title, + 'search[category_id]': category_id + } + return self._get('forum_topics.json', params) + + def forum_topic_show(self, topic_id): + """Retrieve a specific forum topic. + + Parameters: + :param topic_id: REQUIRED Where topic_id is the forum topic id. + """ + return self._get('forum_topics/{0}.json'.format(topic_id)) + + def forum_topic_create(self, title, body, category=None): + """Function to create topic (Requires login) (UNTESTED). + + Parameters: + :param title: topic title. + :param body: Message of the initial post. + :param category: Can be: 0, 1, 2 (General, Tags, Bugs & Features + respectively). + """ + params = { + 'forum_topic[title]': title, + 'forum_topic[original_post_attributes][body]': body, + 'forum_topic[category_id]': category + } + return self._get('forum_topics.json', params, method='POST', auth=True) + + def forum_topic_update(self, topic_id, title=None, category=None): + """Update a specific topic (Login Requires) (UNTESTED). + + Parameters: + :param topic_id: REQUIRED .ñWhere topic_id is the topic id. + :param title: Topic title. + :param category: Can be: 0, 1, 2 (General, Tags, Bugs & Features + respectively) + """ + params = { + 'forum_topic[title]': title, + 'forum_topic[category_id]': category + } + return self._get('forum_topics/{0}.json'.format(topic_id), params, + method='PUT', auth=True) + + def forum_topic_delete(self, topic_id): + """Delete a topic (Login Requires) (Moderator+) (UNTESTED). + + Parameters: + :param topic_id: REQUIRED Where topic_id is the topic id. + """ + return self._get('forum_topics/{0}.json'.format(topic_id), + method='DELETE', auth=True) + + def forum_topic_undelete(self, topic_id): + """Un delete a topic (Login requries) (Moderator+) (UNTESTED). + + Parameters: + :param topic_id: REQUIRED Where topic_id is the topic id. + """ + return self._get('forum_topics/{0}/undelete.json'.format(topic_id), + method='POST', auth=True) + + def forum_post_list(self, creator_id=None, creator_name=None, + topic_id=None, topic_title_matches=None, + topic_category_id=None, body_matches=None): + """Return a list of forum posts. + + Parameters: + :param creator_id: + :param creator_name: + :param topic_id: + :param topic_title_matches: + :param topic_category_id: Can be: 0, 1, 2 (General, Tags, Bugs & + Features respectively) + :param body_matches: + """ + params = { + 'search[creator_id]': creator_id, + 'search[creator_name]': creator_name, + 'search[topic_id]': topic_id, + 'search[topic_title_matches]': topic_title_matches, + 'search[topic_category_id]': topic_category_id, + 'search[body_matches]': body_matches + } + return self._get('forum_posts.json', params) + + def forum_post_create(self, topic_id, body): + """Create a forum post (Requires login). + + Parameters: + :param topic_id: REQUIRED. + :param body: REQUIRED. + """ + params = { + 'forum_post[topic_id]': topic_id, + 'forum_post[body]': body + } + return self._get('forum_posts.json', params, method='POST', auth=True) + + def forum_post_update(self, topic_id, body): + """Update a specific forum post (Requries login)(Moderator+)(UNTESTED). + + Parameters: + :param post_id: REQUIRED Forum topic id. + :param body: REQUIRED. + """ + params = {'forum_post[body]': body} + return self._get('forum_posts/{0}.json'.format(topic_id), params, + method='PUT', auth=True) + + def forum_post_delete(self, post_id): + """Delete a specific forum post (Requires login)(Moderator+)(UNTESTED). + + Parameters: + :param post_id: REQUIRED forum post id. + """ + return self._get('forum_posts/{0}.json'.format(post_id), + method='DELETE', auth=True) + + def forum_post_undelete(self, post_id): + """Undelete a specific forum post (Requires login) (Moderator+) + (UNTESTED). + + Parameters: + :param post_id: REQUIRED forum post id. + """ + return self._get('forum_posts/{0}/undelete.json'.format(post_id), + method='POST', auth=True) diff --git a/pybooru/api_moebooru.py b/pybooru/api_moebooru.py new file mode 100644 index 0000000..10a1e32 --- /dev/null +++ b/pybooru/api_moebooru.py @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- + +"""pybooru.api_moebooru + +This module contains all API calls of Moebooru. + +Classes: + MoebooruApi_Mixin -- Contains all API calls. +""" + +# __future__ imports +from __future__ import absolute_import + +# pybooru imports +from .exceptions import PybooruAPIError + + +class MoebooruApi_Mixin(object): + """Contains all Moebooru API calls. + + * API Versions: 1.13.0+update.3 and 1.13.0 + * doc: https://yande.re/help/api or http://konachan.com/help/api + """ + + def post_list(self, **params): + """Get a list of posts. + + Parameters: + :param tags: The tags to search for. Any tag combination that works + on the web site will work here. This includes all the + meta-tags. + :param limit: How many posts you want to retrieve. There is a limit + of 100:param posts per request. + :param page: The page number. + """ + return self._get('post', params) + + def post_create(self, tags, file_=None, rating=None, source=None, + rating_locked=None, note_locked=None, parent_id=None, + md5=None): + """Function to create a new post (Requires login). + + There are only two mandatory fields: you need to supply the + 'tags', and you need to supply the 'file_', either through a + multipart form or through a source URL (Requires login) (UNTESTED). + + Parameters: + :param tags: A space delimited list of tags. + :param file_: The file data encoded as a multipart form. Path of + content. + :param rating: The rating for the post. Can be: safe, questionable, + or explicit. + :param source: If this is a URL, Moebooru will download the file. + :param rating_locked: Set to true to prevent others from changing + the rating. + :param note_locked: Set to true to prevent others from adding + notes. + :param parent_id: The ID of the parent post. + :param md5: Supply an MD5 if you want Moebooru to verify the file + after uploading. If the MD5 doesn't match, the post is + destroyed. + """ + if file_ or source is not None: + params = { + 'post[tags]': tags, + 'post[source]': source, + 'post[rating]': rating, + 'post[is_rating_locked]': rating_locked, + 'post[is_note_locked]': note_locked, + 'post[parent_id]': parent_id, + 'md5': md5} + file_ = {'post[file]': open(file_, 'rb')} + return self._get('post/create', params, 'POST', file_) + else: + raise PybooruAPIError("'file_' or 'source' is required.") + + def post_update(self, post_id, tags=None, file_=None, rating=None, + source=None, is_rating_locked=None, is_note_locked=None, + parent_id=None): + """Update a specific post. + + Only the 'post_id' parameter is required. Leave the other parameters + blank if you don't want to change them (Requires login). + + Parameters: + :param post_id: The id number of the post to update. + :param tags: A space delimited list of tags. Specify previous tags. + :param file_: The file data ENCODED as a multipart form. + :param rating: The rating for the post. Can be: safe, questionable, + or explicit. + :param source: If this is a URL, Moebooru will download the file. + :param rating_locked: Set to true to prevent others from changing + the rating. + :param note_locked: Set to true to prevent others from adding + notes. + :param parent_id: The ID of the parent post. + """ + params = { + 'id': post_id, + 'post[tags]': tags, + 'post[rating]': rating, + 'post[source]': source, + 'post[is_rating_locked]': is_rating_locked, + 'post[is_note_locked]': is_note_locked, + 'post[parent_id]': parent_id + } + if file_ is not None: + file_ = {'post[file]': open(file_, 'rb')} + return self._get('post/update', params, 'PUT', file_) + else: + return self._get('post/update', params, 'PUT') + + def post_destroy(self, post_id): + """Function to destroy a specific post. + + You must also be the user who uploaded the post (or you must be a + moderator) (Requires Login) (UNTESTED). + + Parameters: + :param Post_id: The id number of the post to delete. + """ + return self._get('post/destroy', {'id': post_id}, 'DELETE') + + def post_revert_tags(self, post_id, history_id): + """Function to reverts a post to a previous set of tags + (Requires login) (UNTESTED). + + Parameters: + :param post_id: The post id number to update. + :param history_id: The id number of the tag history. + """ + params = {'id': post_id, 'history_id': history_id} + return self._get('post/revert_tags', params, 'PUT') + + def post_vote(self, post_id, score): + """Action lets you vote for a post (Requires login). + + Parameters: + :param post_id: The post id. + :param score: + * 0: No voted or Remove vote. + * 1: Good. + * 2: Great. + * 3: Favorite, add post to favorites. + """ + if score <= 3: + params = {'id': post_id, 'score': score} + return self._get('post/vote', params, 'POST') + else: + raise PybooruAPIError("Value of 'score' only can be 0, 1, 2 or 3.") + + def tag_list(self, **params): + """Get a list of tags. + + Parameters: + :param name: The exact name of the tag. + :param id: The id number of the tag. + :param limit: How many tags to retrieve. Setting this to 0 will + return every tag (Default value: 0). + :param page: The page number. + :param order: Can be 'date', 'name' or 'count'. + :param after_id: Return all tags that have an id number greater + than this. + """ + return self._get('tag', params) + + def tag_update(self, name=None, tag_type=None, is_ambiguous=None): + """Action to lets you update tag (Requires login) (UNTESTED). + + Parameters: + :param name: The name of the tag to update. + :param tag_type: + * General: 0. + * artist: 1. + * copyright: 3. + * character: 4. + :param is_ambiguous: Whether or not this tag is ambiguous. Use 1 + for true and 0 for false. + """ + params = { + 'name': name, + 'tag[tag_type]': tag_type, + 'tag[is_ambiguous]': is_ambiguous + } + return self._get('tag/update', params, 'PUT') + + def tag_related(self, **params): + """Get a list of related tags. + + Parameters: + :param tags: The tag names to query. + :param type: Restrict results to this tag type. Can be general, + artist, copyright, or character. + """ + return self._get('tag/related', params) + + def artist_list(self, **params): + """Get a list of artists. + + Parameters: + :param name: The name (or a fragment of the name) of the artist. + :param order: Can be date or name. + :param page: The page number. + """ + return self._get('artist', params) + + def artist_create(self, name, urls=None, alias=None, group=None): + """Function to create an artist (Requires login) (UNTESTED). + + Parameters: + :param name: The artist's name. + :param urls: A list of URLs associated with the artist, whitespace + delimited. + :param alias: The artist that this artist is an alias for. Simply + enter the alias artist's name. + :param group: The group or cicle that this artist is a member of. + Simply:param enter the group's name. + """ + params = { + 'artist[name]': name, + 'artist[urls]': urls, + 'artist[alias]': alias, + 'artist[group]': group + } + return self._get('artist/create', params, 'POST') + + def artist_update(self, artist_id, name=None, urls=None, alias=None, + group=None): + """Function to update artists (Requires Login). + + Only the artist_id parameter is required. The other parameters are + optional. (Requires login) (UNTESTED). + + Parameters: + :param artist_id: The id of thr artist to update (Type: INT). + :param name: The artist's name. + :param urls: A list of URLs associated with the artist, whitespace + delimited. + :param alias: The artist that this artist is an alias for. Simply + enter the alias artist's name. + :param group: The group or cicle that this artist is a member of. + Simply enter the group's name. + """ + params = { + 'id': artist_id, + 'artist[name]': name, + 'artist[urls]': urls, + 'artist[alias]': alias, + 'artist[group]': group + } + return self._get('artist/update', params, 'PUT') + + def artist_destroy(self, artist_id): + """Action to lets you remove artist (Requires login) (UNTESTED). + + Parameters: + :param artist_id: The id of the artist to destroy. + """ + return self._get('artist/destroy', {'id': artist_id}, 'POST') + + def comment_show(self, comment_id): + """Get a specific comment. + + Parameters: + :param :param comment_id: The id number of the comment to retrieve. + """ + return self._get('comment/show', {'id': comment_id}) + + def comment_create(self, post_id, comment_body, anonymous=None): + """Action to lets you create a comment (Requires login). + + Parameters: + :param post_id: The post id number to which you are responding. + :param comment_body: The body of the comment. + :param anonymous: Set to 1 if you want to post this comment + anonymously. + """ + if post_id and comment_body is not None: + params = { + 'comment[post_id]': post_id, + 'comment[body]': comment_body, + 'comment[anonymous]': anonymous + } + return self._get('comment/create', params, 'POST') + else: + raise PybooruAPIError("Required 'post_id' and 'comment_body' " + "parameters") + + def comment_destroy(self, comment_id): + """Remove a specific comment (Requires login). + + Parameters: + :param comment_id: The id number of the comment to remove. + """ + return self._get('comment/destroy', {'id': comment_id}, 'DELETE') + + def wiki_list(self, **params): + """Function to retrieves a list of every wiki page. + + Parameters: + :param query: A word or phrase to search for (Default: None). + :param order: Can be: title, date (Default: title). + :param limit: The number of pages to retrieve (Default: 100). + :param page: The page number. + """ + return self._get('wiki', params) + + def wiki_create(self, title, body): + """Action to lets you create a wiki page (Requires login) (UNTESTED). + + Parameters: + :param title: The title of the wiki page. + :param body: The body of the wiki page. + """ + params = {'wiki_page[title]': title, 'wiki_page[body]': body} + return self._get('wiki/create', params, 'POST') + + def wiki_update(self, page_title, new_title=None, page_body=None): + """Action to lets you update a wiki page (Requires login) (UNTESTED). + + Parameters: + :param page_title: The title of the wiki page to update. + :param new_title: The new title of the wiki page. + :param page_body: The new body of the wiki page. + """ + params = { + 'title': page_title, + 'wiki_page[title]': new_title, + 'wiki_page[body]': page_body + } + return self._get('wiki/update', params, 'PUT') + + def wiki_show(self, **params): + """Get a specific wiki page. + + Parameters: + :param title: The title of the wiki page to retrieve. + :param version: The version of the page to retrieve. + """ + return self._get('wiki/show', params) + + def wiki_destroy(self, title): + """Function to delete a specific wiki page (Requires login) + (Only moderators) (UNTESTED). + + Parameters: + :param title: The title of the page to delete. + """ + return self._get('wiki/destroy', {'title': title}, 'DELETE') + + def wiki_lock(self, title): + """Function to lock a specific wiki page (Requires login) + (Only moderators) (UNTESTED). + + Parameters: + :param title: The title of the page to lock. + """ + return self._get('wiki/lock', {'title': title}, 'POST') + + def wiki_unlock(self, title): + """Function to unlock a specific wiki page (Requires login) + (Only moderators) (UNTESTED). + + Parameters: + :param title: The title of the page to unlock. + """ + return self._get('wiki/unlock', {'title': title}, 'POST') + + def wiki_revert(self, title, version): + """Function to revert a specific wiki page (Requires login) (UNTESTED). + + Parameters: + :param title: The title of the wiki page to update. + :param version: The version to revert to. + """ + params = {'title': title, 'version': version} + return self._get('wiki/revert', params, 'PUT') + + def wiki_history(self, title): + """Get history of specific wiki page. + + Parameters: + :param title: The title of the wiki page to retrieve versions for. + """ + return self._get('wiki/history', {'title': title}) + + def note_list(self, **params): + """Get note list. + + Parameters: + :param post_id: The post id number to retrieve notes for. + """ + return self._get('note', params) + + def note_search(self, query): + """Search specific note. + + Parameters: + :param query: A word or phrase to search for. + """ + return self._get('note/search', {'query': query}) + + def note_history(self, **params): + """Get history of notes. + + Parameters: + :param post_id: The post id number to retrieve note versions for. + :param id: The note id number to retrieve versions for. + :param limit: How many versions to retrieve (Default: 10). + :param page: The note id number to retrieve versions for. + """ + return self._get('note/history', params) + + def note_revert(self, note_id, version): + """Function to revert a specific note (Requires login) (UNTESTED). + + Parameters: + :param note_id: The note id to update. + :param version: The version to revert to. + """ + params = {'id': note_id, 'version': version} + return self._get('note/revert', params, 'PUT') + + def note_create_update(self, post_id=None, coor_x=None, coor_y=None, + width=None, height=None, is_active=None, body=None, + note_id=None): + """Function to create or update note (Requires login) (UNTESTED). + + Parameters: + :param post_id: The post id number this note belongs to. + :param coor_x: The X coordinate of the note. + :param coor_y: The Y coordinate of the note. + :param width: The width of the note. + :param height: The height of the note. + :param is_active: Whether or not the note is visible. Set to 1 for + active, 0 for inactive. + :param body: The note message. + :param note_id: If you are updating a note, this is the note id + number to update. + """ + params = { + 'id': note_id, + 'note[post]': post_id, + 'note[x]': coor_x, + 'note[y]': coor_y, + 'note[width]': width, + 'note[height]': height, + 'note[body]': body, + 'note[is_active]': is_active + } + return self._get('note/update', params, 'POST') + + def user_search(self, **params): + """Search users. + + If you don't specify any parameters you'll _get a listing of all users. + + Parameters: + :param id: The id number of the user. + :param name: The name of the user. + """ + return self._get('user', params) + + def forum_list(self, **params): + """Function to get forum posts. + + If you don't specify any parameters you'll _get a listing of all users. + + Parameters: + :param parent_id: The parent ID number. You'll return all the + responses to that forum post. + """ + return self._get('forum', params) + + def pool_list(self, **params): + """Function to get pools. + + If you don't specify any parameters you'll _get a list of all pools. + + Parameters: + :param query: The title. + :param page: The page. + """ + return self._get('pool', params) + + def pool_posts(self, **params): + """Function to _get pools posts. + + If you don't specify any parameters you'll _get a list of all pools. + + Parameters: + :param id: The pool id number. + :param page: The page. + """ + return self._get('pool/show', params) + + def pool_update(self, pool_id, name=None, is_public=None, + description=None): + """Function to update a pool (Requires login) (UNTESTED). + + Parameters: + :param pool_id: The pool id number. + :param name: The name. + :param is_public: 1 or 0, whether or not the pool is public. + :param description: A description of the pool. + """ + params = { + 'id': pool_id, + 'pool[name]': name, + 'pool[is_public]': is_public, + 'pool[description]': description + } + return self._get('pool/update', params, 'PUT') + + def pool_create(self, name, description, is_public): + """Function to create a pool (Require login) (UNTESTED). + + Parameters: + :param name: The name. + :param description: A description of the pool. + :param is_public: 1 or 0, whether or not the pool is public. + """ + params = {'pool[name]': name, 'pool[description]': description, + 'pool[is_public]': is_public} + return self._get('pool/create', params, 'POST') + + def pool_destroy(self, pool_id): + """Function to destroy a specific pool (Require login) (UNTESTED). + + Parameters: + :param pool_id: The pool id number. + """ + return self._get('pool/destroy', {'id': pool_id}, 'DELETE') + + def pool_add_post(self, **params): + """Function to add a post (Require login) (UNTESTED). + + Parameters: + :param pool_id: The pool to add the post to. + :param post_id: The post to add. + """ + return self._get('pool/add_post', params, 'PUT') + + def pool_remove_post(self, **params): + """Function to remove a post (Require login) (UNTESTED). + + Parameters: + :param pool_id: The pool to remove the post to. + :param post_id: The post to remove. + """ + return self._get('pool/remove_post', params, 'PUT') + + def favorite_list_users(self, post_id): + """Function to return a list with all users who have added to favorites + a specific post. + + Parameters: + :param post_id: The post id. + """ + response = self._get('favorite/list_users', {'id': post_id}) + # Return list with users + return response['favorited_users'].split(',') diff --git a/pybooru/danbooru.py b/pybooru/danbooru.py new file mode 100644 index 0000000..8e05574 --- /dev/null +++ b/pybooru/danbooru.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +"""pybooru.danbooru + +This module contains Danbooru class for access to API calls, authentication, +build url and return JSON response. + +Classes: + Danbooru -- Danbooru main classs. +""" + +# Pybooru imports +from .pybooru import _Pybooru +from .api_danbooru import DanbooruApi_Mixin +from .exceptions import PybooruError + + +class Danbooru(_Pybooru, DanbooruApi_Mixin): + """Danbooru class (inherits: Pybooru and DanbooruApi_Mixin). + + To initialize Pybooru, you need to specify one of these two + parameters: 'site_name' or 'site_url'. If you specify 'site_name', Pybooru + checks whether there is in the list of default sites (You can get list + of sites in the 'resources' module). + + To specify a site that isn't in list of default sites, you need use + 'site_url' parameter and specify url. + + Some actions may require you to log in. always specify two parameters to + log in: 'username' and 'api_key'. + + Attributes: + :var site_name: Return site name. + :var site_url: Return the URL of Moebooru based site. + :var username: Return user name. + :var api_key: Return API key. + :var last_call: Return last call. + """ + + def __init__(self, site_name="", site_url="", username="", api_key=""): + """Initialize Danbooru. + + Keyword arguments: + :param site_name: The site name in 'SITE_LIST', default sites. + :param site_url: URL of on Moebooru based sites. + :param username: Your username of the site (Required only for + functions that modify the content). + :param api_key: Your api key of the site (Required only for + functions that modify the content). + """ + super(Danbooru, self).__init__(site_name, site_url, username) + + if api_key is not "": + self.api_key = api_key + + def _get(self, api_call, params=None, method='GET', auth=False, + file_=None): + """Function to preapre API call. + + Parameters: + :param api_call: API function to be called. + :param params: API function parameters. + :param method: (Defauld: GET) HTTP method (GET, POST, PUT or + DELETE) + :param file_: File to upload (only uploads). + + :raise AttributeError: When 'username' or 'api_key' are not set. + """ + url = "{0}/{1}".format(self.site_url, api_call) + + if method == 'GET': + request_args = {'params': params} + else: + request_args = {'data': params, 'files': file_} + + # Adds auth + if auth is True: + try: + request_args['auth'] = (self.username, self.api_key) + except AttributeError: + raise PybooruError("'username' and 'api_key' attribute of \ + Danbooru are required.") + + # Do call + return self._request(url, api_call, request_args, method) diff --git a/pybooru/exceptions.py b/pybooru/exceptions.py index 7eaee6c..c9f03ab 100644 --- a/pybooru/exceptions.py +++ b/pybooru/exceptions.py @@ -1,50 +1,45 @@ # -*- coding: utf-8 -*- -"""This module contains the exceptions.""" +"""pybooru.exceptions + +This module contains Pybooru exceptions. + +Classes: + * PybooruError -- Main Pybooru exception class. + * PybooruHTTPError -- Manages HTTP status errors. + * PybooruAPIError -- Manages all API errors. +""" # __furute__ imports from __future__ import absolute_import -from __future__ import unicode_literals # pybooru imports -from .resources import HTTP_STATUS_CODES +from .resources import HTTP_STATUS_CODE class PybooruError(Exception): - """Class to return error message. - - Init Parameters: - msg: - The error message. - - http_code: - The HTTP status code. - - url: - The URL. + """Class to catch Pybooru error message.""" + pass - Attributes: - msg: Return the error message. - http_code: Return the HTTP status code. - url: return the URL. - """ - def __init__(self, msg, http_code=None, url=None): - super(PybooruError, self).__init__() +class PybooruHTTPError(PybooruError): + """Class to catch HTTP error message.""" - self.msg = msg - self.http_code = http_code - self.url = url + def __init__(self, msg, http_code, url): + """Initialize PybooruHTTPError. - if http_code in HTTP_STATUS_CODES and self.url is not None: - self.msg = "{0}: {1}, {2} - {3} -- URL: {4}".format(self.msg, - http_code, HTTP_STATUS_CODES[http_code][0], - HTTP_STATUS_CODES[http_code][1], self.url) + Keyword arguments: + :param msg: The error message. + :param http_code: The HTTP status code. + :param url: The URL. + """ + super(PybooruHTTPError, self).__init__(msg, http_code, url) + if http_code in HTTP_STATUS_CODE and url is not None: + msg = "{0}: {1} - {2}, {3} - URL: {4}".format( + msg, http_code, HTTP_STATUS_CODE[http_code][0], + HTTP_STATUS_CODE[http_code][1], url) - def __str__(self): - """Function to return error message.""" - return repr(self.msg) - def __repr__(self): - """Function to return self.msg repr.""" - return self.msg +class PybooruAPIError(PybooruError): + """Class to catch all API errors.""" + pass diff --git a/pybooru/moebooru.py b/pybooru/moebooru.py new file mode 100644 index 0000000..c040fb3 --- /dev/null +++ b/pybooru/moebooru.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +"""pybooru.moebooru + +This module contains Moebooru class for access to API calls, +authentication, build url and return JSON response. + +Classes: + Moebooru -- Moebooru classs. +""" + +# __furute__ imports +from __future__ import absolute_import + +# External imports +import hashlib + +# Pybooru imports +from .pybooru import _Pybooru +from .api_moebooru import MoebooruApi_Mixin +from .exceptions import PybooruError +from .resources import SITE_LIST + + +class Moebooru(_Pybooru, MoebooruApi_Mixin): + """Moebooru class (inherits: Pybooru and MoebooruApi_Mixin). + + To initialize Pybooru, you need to specify one of these two + parameters: 'site_name' or 'site_url'. If you specify 'site_name', Pybooru + checks whether there is in the list of default sites (You can get list + of sites in the 'resources' module). + + To specify a site that isn't in list of default sites, you need use + 'site_url' parameter and specify url. + + Some actions may require you to log in. always specify three parameters to + log in: 'hash_string', 'username' and 'password'. Default sites has an + associate hash string. + + Attributes: + :var site_name: Return site name. + :var site_url: Return the URL of Moebooru based site. + :var api_version: Version of Moebooru API. + :var username: Return user name. + :var password: Return password in plain text. + :var hash_string: Return hash_string of the site. + :var last_call: Return last call. + """ + + def __init__(self, site_name="", site_url="", username="", password="", + hash_string="", api_version="1.13.0+update.3"): + """Initialize Moebooru. + + Keyword arguments: + :param site_name: The site name in 'SITE_LIST', default sites. + :param site_url: URL of on Moebooru based sites. + :param api_version: Version of Moebooru API. + :param hash_string: String that is hashed (required to login). + (See the API documentation of the site for more + information). + :param username: Your username of the site (Required only for + functions that modify the content). + :param password: Your user password in plain text (Required only + for functions that modify the content). + """ + super(Moebooru, self).__init__(site_name, site_url, username) + + self.api_version = api_version.lower() + if password is not "": + self.password = password + self.password_hash = None + + def _build_url(self, api_call): + """Build request url. + + Parameters: + :param api_call: Base API Call. + """ + if self.api_version in ('1.13.0', '1.13.0+update.1', '1.13.0+update.2'): + if '/' not in api_call: + return "{0}/{1}/index.json".format(self.site_url, api_call) + return "{0}/{1}.json".format(self.site_url, api_call) + + def _build_hash_string(self): + """Function for build password hash string.""" + # Build AUTENTICATION hash_string + # Check if hash_string exists + if self.site_name in SITE_LIST or self.hash_string is not "": + if self.username and self.password is not "": + try: + hash_string = self.hash_string.format(self.password) + except TypeError: + raise PybooruError("Pybooru can't add 'password' " + "to 'hash_string'") + # encrypt hashed_string to SHA1 and return hexdigest string + self.password_hash = hashlib.sha1( # pylint: disable=E1101 + hash_string.encode('utf-8')).hexdigest() + else: + raise PybooruError("Specify the 'username' and 'password' " + "parameters of the Pybooru object, for " + "setting 'password_hash' attribute.") + else: + raise PybooruError( + "Specify the 'hash_string' parameter of the Pybooru" + " object, for the functions that requires login.") + + def _get(self, api_call, params, method='GET', file_=None): + """Function to preapre API call. + + Parameters: + :param api_call: API function to be called. + :param params: API function parameters. + :param method: (Defauld: GET) HTTP method 'GET' or 'POST' + :param file_: File to upload. + """ + url = self._build_url(api_call) + + if method == 'GET': + request_args = {'params': params} + else: + if self.password_hash is None: + self._build_hash_string() + + # Set login + params['login'] = self.username + params['password_hash'] = self.password_hash + request_args = {'data': params, 'files': file_} + + # Do call + return self._request(url, api_call, request_args, method) diff --git a/pybooru/pybooru.py b/pybooru/pybooru.py index caabbff..03162e4 100644 --- a/pybooru/pybooru.py +++ b/pybooru/pybooru.py @@ -1,109 +1,83 @@ # -*- coding: utf-8 -*- -"""This module contains pybooru object class.""" +"""pybooru.pybooru + +This module contains pybooru main class for access to API calls, +authentication and return JSON response. + +Classes: + _Pybooru -- Main pybooru classs, define Pybooru object and do requests. +""" # __furute__ imports from __future__ import absolute_import -from __future__ import unicode_literals - -# pyborru exceptions imports -from .exceptions import PybooruError -# pybooru resources imports -from .resources import (API_BASE_URL, SITE_LIST) -# requests imports +# External imports +import re import requests -# hashlib imports -import hashlib - -# re imports -import re +# pybooru imports +from . import __version__ +from .exceptions import (PybooruError, PybooruHTTPError) +from .resources import (SITE_LIST, HTTP_STATUS_CODE) -class Pybooru(object): +class _Pybooru(object): """Pybooru main class. - To initialize Pybooru, you need to specify one of these two - parameters: site_name or site_url. If you specify site_name, Pybooru checks - whether there is in the list of default sites (You can get list of sites in - the resources module). - - To specify a site that isn't in list of default sites, you need use site_url - parameter. - - Some actions may require you to log in. always specify three parameters to - log in: hash_string, username and password. Default sites has an associate - hash string. - - Init Parameters: - site_name (Type STR): - The site name in SITE_LIST, default sites. - - site_url (Type STR): - URL of based on Danbooru/Moebooru sites. - - hash_string (Type STR): - String that is hashed (required to login). - (See the API documentation of the site for more information). - - username (Type STR): - Your username of the site - (Required only for functions that modify the content). - - password (Type STR): - Your user password in plain text - (Required only for functions that modify the content). - Attributes: - site_name: Return site name. - site_url: Return URL of based danbooru/Moebooru site. - username: Return user name. - password: Return password in plain text. - hash_string: Return hash_string. + :var site_name: Return site name. + :var site_url: Return the URL of Moebooru/Danbooru based site. + :var username: Return user name. + :var last_call: Return last call. """ - def __init__(self, site_name="", site_url="", username="", password="", - hash_string=""): + def __init__(self, site_name="", site_url="", username=""): + """Initialize Pybooru. + Keyword arguments: + :param site_name: The site name in 'SITE_LIST', default sites. + :param site_url: URL of on Moebooru/Danbooru based sites. + :param username: Your username of the site (Required only for + functions that modify the content). + """ # Attributes self.site_name = site_name.lower() self.site_url = site_url.lower() - self.username = username - self.password = password - self.hash_string = hash_string + if username is not "": + self.username = username + self.last_call = {} + + # Set HTTP Client + self.client = requests.Session() + headers = {'user-agent': 'Pybooru/{0}'.format(__version__), + 'content-type': 'application/json; charset=utf-8'} + self.client.headers = headers # Validate site_name or site_url - if site_url is not "" or site_name is not "": + if site_url or site_name is not "": if site_name is not "": - self._site_name_validator(self.site_name) + self._site_name_validator() elif site_url is not "": - self._url_validator(self.site_url) + self._url_validator() else: raise PybooruError("Unexpected empty strings," - " specify parameter site_name or site_url.") - - def _site_name_validator(self, site_name): - """Function that checks the site name and get the url. - - Parameters: - site_name (Type STR): - The name of a based Danbooru/Moebooru site. You can get list - of sites in the resources module. - """ - if site_name in list(SITE_LIST.keys()): - self.site_url = SITE_LIST[site_name]['url'] + " specify parameter 'site_name' or 'site_url'.") + + def _site_name_validator(self): + """Function that checks the site name and get url.""" + if self.site_name in SITE_LIST: + self.site_url = SITE_LIST[self.site_name]['url'] + # Only for Moebooru + if 'api_version' and 'hashed_string' in SITE_LIST[self.site_name]: + self.api_version = SITE_LIST[self.site_name]['api_version'] + self.hash_string = SITE_LIST[self.site_name]['hashed_string'] else: raise PybooruError( - "The site name is not valid, use the site_url parameter") + "The 'site_name' is not valid, specify a valid 'site_name'.") - def _url_validator(self, url): - """URL validator for site_url parameter of Pybooru. - - Parameters: - url (Type STR): - The URL to validate. - """ + def _url_validator(self): + """URL validator for site_url attribute.""" # Regular expression to URL validate regex = re.compile( r'^(?:http|https)://' # Scheme only HTTP/HTTPS @@ -116,884 +90,54 @@ def _url_validator(self, url): r'(?:/?|[/?]\S+)$', re.IGNORECASE) # Validate URL - if re.match('^(?:http|https)://', url): - if re.search(regex, url): - self.site_url = url - else: - raise PybooruError("Invalid URL", url=url) + if re.match('^(?:http|https)://', self.site_url): + if not re.search(regex, self.site_url): + raise PybooruError("Invalid URL: {0}".format(self.site_url)) else: - raise PybooruError("Invalid URL scheme, use HTTP or HTTPS", url=url) - - def _build_request_url(self, api_name, params=None): - """Function for build url. - - Parameters: - api_name: - The NAME of the API function. - - params (Default: None): - The parameters of the API function. - """ - if params is None: - params = {} - - # Create url - url = self.site_url + API_BASE_URL[api_name]['url'] - - # Build AUTENTICATION hash_string - # Check if hash_string exists - if API_BASE_URL[api_name]['required_login'] is True: - if self.site_name in list(SITE_LIST.keys()) or \ - self.hash_string is not "": - - # Check if the username and password are empty - if self.username is not "" and self.password is not "": - # Set username login parameter - params['login'] = self.username - - # Create hashed string - if self.hash_string is not "": - try: - hash_string = self.hash_string.format(self.password) - except TypeError: - raise PybooruError(r"Use \{0\} in hash_string") - else: - hash_string = SITE_LIST[self.site_name]['hashed_string'].format(self.password) - - # Set password_hash parameter - # Convert hashed_string to SHA1 and return hex string - params['password_hash'] = hashlib.sha1( # pylint: disable=E1101 - hash_string).hexdigest() - else: - raise PybooruError("Specify the username and password " - "parameter of the Pybooru object, for " - "setting password_hash attribute.") - else: - raise PybooruError( - "Specify the hash_string parameter of the Pybooru" - " object, for the functions which require login.") - - return self._json_request(url, params) + raise PybooruError("Invalid URL scheme, use HTTP " + "or HTTPS: {0}".format(self.site_url)) @staticmethod - def _json_request(url, params): - """Function to read and returning JSON response. - - Parameters: - url: - API function url. - - params: - API function parameters. - """ - # Header - headers = {'content-type': 'application/json; charset=utf-8'} - - try: - # Request - response = requests.post(url, params=params, headers=headers, - timeout=60) - # Enable raise status error - response.raise_for_status() - # Read and return JSON data - return response.json() - except requests.exceptions.HTTPError as err: - raise PybooruError("In _json_request", response.status_code, url) - except requests.exceptions.Timeout as err: - raise PybooruError("Timeout! in url: {0}".format(url)) - except ValueError as err: - raise PybooruError("JSON Error: {0} in line {1} column {2}".format( - err.msg, err.lineno, err.colno)) - - def posts_list(self, tags=None, limit=100, page=1): - """Get a list of posts. - - Parameters: - tags: - The tags of the posts (Default: None). - - limit: - Limit of posts. Limit is 100 posts per request (Default: 100). - - page: - The page number (Default: 1). - """ - params = {'limit': limit, 'page': page} - - if tags is not None: - params['tags'] = tags - - return self._build_request_url('posts_list', params) - - def posts_create(self, tags, file_=None, rating=None, source=None, - is_rating_locked=None, is_note_locked=None, - parent_id=None, md5=None): - """Function to create a new post. - - There are only two mandatory fields: you need to supply the tags, and - you need to supply the file, either through a multipart form or - through a source URL (Requires login)(UNTESTED). - - Parameters: - tags: - A space delimited list of tags. - - file_: - The file data encoded as a multipart form. - - rating: - The rating for the post. Can be: safe, questionable, or - explicit. - - source: - If this is a URL, Danbooru/Moebooru will download the file. - - is_rating_locked: - Set to true to prevent others from changing the rating. - - is_note_locked: - Set to true to prevent others from adding notes. - - parent_id: - The ID of the parent post. - - md5: - Supply an MD5 if you want Danbooru/Moebooru to verify the file after - uploading. If the MD5 doesn't match, the post is destroyed. - """ - params = {'post[tags]': tags} - - if source is not None or file_ is not None: - if file_ is not None: - params['post[file]'] = file_ - if source is not None: - params['post[source]'] = source - if rating is not None: - params['post[rating]'] = rating - if is_rating_locked is not None: - params['post[is_rating_locked]'] = is_rating_locked - if is_note_locked is not None: - params['post[is_note_locked]'] = is_note_locked - if parent_id is not None: - params['post[parent_id]'] = parent_id - if md5 is not None: - params['md5'] = md5 - - return self._build_request_url('posts_create', params) + def _get_status(status_code): + """Get status message for status code""" + if status_code in HTTP_STATUS_CODE: + return "{0}, {1}".format(*HTTP_STATUS_CODE[status_code]) else: - raise PybooruError("source or file_ is required") + return None - def posts_update(self, id_, tags, file_, rating, source, is_rating_locked, - is_note_locked, parent_id): - """Function update a specific post. - - Only the id_ parameter is required. Leave the other parameters blank - if you don't want to change them (Requires login)(UNESTED). + def _request(self, url, api_call, request_args, method='GET'): + """Function to request and returning JSON data. Parameters: - id_: - The id number of the post to update (Type: INT). - - tags: - A space delimited list of tags (Type: STR). - - file_: - The file data ENCODED as a multipart form. - - rating: - The rating for the post. Can be: safe, questionable, or - explicit. - - source: - If this is a URL, Danbooru/Moebooru will download the file. + :param url: Base url call. + :param api_call: API function to be called. + :param request_args: All requests parameters. + :param method: (Defauld: GET) HTTP method 'GET' or 'POST' - is_rating_locked: - Set to true to prevent others from changing the rating. - - is_note_locked: - Set to true to prevent others from adding notes. - - parent_id: - The ID of the parent post. - """ - params = {'id': id_} - - if tags is not None: - params['post[tags]'] = tags - if file_ is not None: - params['post[file]'] = file_ - if rating is not None: - params['post[rating]'] = rating - if source is not None: - params['post[source]'] = source - if is_rating_locked is not None: - params['post[is_rating_locked]'] = is_rating_locked - if is_note_locked is not None: - params['post[is_note_locked]'] = is_note_locked - if parent_id is not None: - params['post[parent_id]'] = parent_id - - return self._build_request_url('posts_update', params) - - def posts_destroy(self, id_): - """Function to destroy a specific post. - - You must also be the user who uploaded the post (or you must be a - moderator) (Requires Login)(UNTESTED). - - Parameters: - id_: - The id number of the post to delete. + :raises requests.exceptions.Timeout: When HTTP Timeout. + :raises ValueError: When can't decode JSON response. """ - params = {'id': id_} - response = self._build_request_url('posts_destroy', params) - return response['success'] - - def posts_revert_tags(self, id_, history_id): - """Function to reverts a post to a previous set of tags - (Requires login)(UNTESTED). - - Parameters: - id_: - The post id number to update (Type: INT). - - history_id: - The id number of the tag history. - """ - params = {'id': id_, 'history_id': history_id} - return self._build_request_url('posts_revert_tags', params) - - def posts_vote(self, id_, score): - """Action lets you vote for a post (Requires login). - - Parameters: - id_: - The post id (Type: INT). - - score: - Be can: - 0: No voted or Remove vote. - 1: Good. - 2: Great. - 3: Favorite, add post to favorites. - """ - if score <= 3: - params = {'id': id_, 'score': score} - return self._build_request_url('posts_vote', params) - else: - raise PybooruError("Value of score only can be 0, 1, 2 and 3.") - - def tags_list(self, name=None, id_=None, limit=0, page=1, order='name', - after_id=None): - """Get a list of tags. - - Parameters: - name: - The exact name of the tag. - - id_: - The id number of the tag. - - limit: - How many tags to retrieve. Setting this to 0 will return - every tag (Default value: 0). - - page: - The page number. - - order: - Can be 'date', 'name' or 'count' (Default: name). - - after_id: - Return all tags that have an id number greater than this. - """ - params = {'limit': limit, 'page': page, 'order': order} - - if id_ is not None: - params['id'] = id_ - elif name is not None: - params['name'] = name - elif after_id is not None: - params['after_id'] = after_id - - return self._build_request_url('tags_list', params) - - def tags_update(self, name, tag_type, is_ambiguous): - """Action to lets you update tag (Requires login)(UNTESTED). - - Parameters: - name: - The name of the tag to update. - - tag_type: - The tag type. - General: 0. - artist: 1. - copyright: 3. - character: 4. - - is_ambiguous: - Whether or not this tag is ambiguous. Use 1 for true and 0 - for false. - """ - params = {'name': name, 'tag[tag_type]': tag_type, - 'tag[is_ambiguous]': is_ambiguous} - - return self._build_request_url('tags_update', params) - - def tags_related(self, tags, type_=None): - """Get a list of related tags. - - Parameters: - tags: - The tag names to query. - - type_: - Restrict results to this tag type. Can be general, artist, - copyright, or character (Default value: None). - """ - params = {'tags': tags} - - if type_ is not None: - params['type'] = type_ - - return self._build_request_url('tags_related', params) - - def artists_list(self, name=None, order=None, page=1): - """Get a list of artists. - - Parameters: - name: - The name (or a fragment of the name) of the artist. - - order: - Can be date or name (Default value: None). - - page: - The page number. - """ - params = {'page': page} - - if name is not None: - params['name'] = name - if order is not None: - params['order'] = order - - return self._build_request_url('artists_list', params) - - def artists_create(self, name, urls, alias, group): - """Function to create a artist (Requires login)(UNTESTED). - - Parameters: - name: - The artist's name. - - urls: - A list of URLs associated with the artist, whitespace delimited. - - alias: - The artist that this artist is an alias for. Simply enter the - alias artist's name. - - group: - The group or cicle that this artist is a member of. Simply - enter the group's name. - """ - params = {'artist[name]': name, 'artist[urls]': urls, - 'artist[alias]': alias, 'artist[group]': group} - return self._build_request_url('artists_create', params) - - def artists_update(self, id_, name=None, urls=None, alias=None, group=None): - """Function to update an artists. - - Only the id_ parameter is required. The other parameters are optional. - (Requires login)(UNTESTED). - - Parameters: - id_: - The id of thr artist to update (Type: INT). - - name: - The artist's name. - - urls: - A list of URLs associated with the artist, whitespace delimited. - - alias: - The artist that this artist is an alias for. Simply enter the - alias artist's name. - - group: - The group or cicle that this artist is a member of. Simply - enter the group's name. - """ - params = {'id': id_} - - if name is not None: - params['artist[name]'] = name - if urls is not None: - params['artist[urls]'] = urls - if alias is not None: - params['artist[alias]'] = alias - if group is not None: - params['artist[group]'] = group - - return self._build_request_url('artists_update', params) - - def artists_destroy(self, id_): - """Action to lets you remove artist (Requires login)(UNTESTED). - - Parameters: - id_: - The id of the artist to destroy (Type: INT). - """ - params = {'id': id_} - response = self._build_request_url('artists_destroy', params) - return response['success'] - - def comments_show(self, id_): - """Get a specific comment. - - Parameters: - id_: - The id number of the comment to retrieve (Type: INT). - """ - params = {'id': id_} - return self._build_request_url('comments_show', params) - - def comments_create(self, post_id, comment_body): - """Action to lets you create a comment (Requires login). - - Parameters: - post_id: - The post id number to which you are responding (Type: INT). - - comment_body: - The body of the comment. - """ - params = {'comment[post_id]': post_id, - 'comment[body]': comment_body} - response = self._build_request_url('comments_create', params) - return response['success'] - - def comments_destroy(self, id_=None): - """Remove a specific comment (Requires login). - - Parameters: - id_: - The id number of the comment to remove (Type: INT). - """ - params = {'id': id_} - response = self._build_request_url('comments_destroy', params) - return response['success'] - - def wiki_list(self, query=None, order='title', limit=100, page=1): - """Function to retrieves a list of every wiki page. - - Parameters: - query: - A word or phrase to search for (Default: None). - - order: - Can be: title, date (Default: title). - - limit: - The number of pages to retrieve (Default: 100). - - page: - The page number. - """ - params = {'order': order, 'limit': limit, 'page': page} - - if query is not None: - params['query'] = query - - return self._build_request_url('wiki_list', params) - - def wiki_create(self, title, body): - """Action to lets you create a wiki page (Requires login)(UNTESTED). - - Parameters: - title: - The title of the wiki page. - - body: - The body of the wiki page. - """ - - params = {'wiki_page[title]': str(title), 'wiki_page[body]': str(body)} - return self._build_request_url('wiki_create', params) - - def wiki_update(self, page_title, new_title, page_body): - """Action to lets you update a wiki page (Requires login)(UNTESTED). - - Parameters: - page_title: - The title of the wiki page to update. - - new_title: - The new title of the wiki page. - - page_body: - The new body of the wiki page. - """ - params = {'title': page_title, 'wiki_page[title]': new_title, - 'wiki_page[body]': page_body} - return self._build_request_url('wiki_update', params) - - def wiki_show(self, title, version=None): - """Get a specific wiki page. - - Parameters: - title: - The title of the wiki page to retrieve. - - version: - The version of the page to retrieve. - """ - params = {'title': title} - - if version is not None: - params['version'] = version - - return self._build_request_url('wiki_show', params) - - def wiki_destroy(self, title): - """Function to delete a specific wiki page (Requires login) - (Only moderators)(UNTESTED). - - Parameters: - title: - The title of the page to delete. - """ - params = {'title': title} - response = self._build_request_url('wiki_destroy', params) - return response['success'] - - def wiki_lock(self, title): - """Function to lock a specific wiki page (Requires login) - (Only moderators)(UNTESTED). - - Parameters: - title: - The title of the page to lock. - """ - params = {'title': title} - response = self._build_request_url('wiki_lock', params) - return response['success'] - - def wiki_unlock(self, title): - """Function to unlock a specific wiki page (Requires login) - (Only moderators)(UNTESTED). - - Parameters: - title: - The title of the page to unlock. - """ - params = {'title': title} - response = self._build_request_url('wiki_unlock', params) - return response['success'] - - def wiki_revert(self, title, version): - """Function to revert a specific wiki page (Requires login)(UNTESTED). - - Parameters: - title: - The title of the wiki page to update. - - version: - The version to revert to. - """ - params = {'title': title, 'version': version} - response = self._build_request_url('wiki_revert', params) - return response['success'] - - def wiki_history(self, title): - """Get history of specific wiki page. - - Parameters: - title: - The title of the wiki page to retrieve versions for. - """ - params = {'title': title} - return self._build_request_url('wiki_history', params) - - def notes_list(self, post_id=None): - """Get note list. - - Parameters: - post_id: - The post id number to retrieve notes for (Default: None) - (Type: INT). - """ - if post_id is not None: - params = {'post_id': post_id} - return self._build_request_url('notes_list', params) - else: - return self._build_request_url('notes_list') - - def notes_search(self, query): - """Search specific note. - - Parameters: - query: - A word or phrase to search for. - """ - params = {'query': query} - return self._build_request_url('notes_search', params) - - def notes_history(self, post_id=None, id_=None, limit=10, page=1): - """Get history of notes. - - Parameters: - post_id: - The post id number to retrieve note versions for. - - id_: - The note id number to retrieve versions for (Type: INT). - - limit: - How many versions to retrieve (Default: 10). - - page: - The note id number to retrieve versions for. - """ - params = {'limit': limit, 'page': page} - - if post_id is not None: - params['post_id'] = post_id - elif id_ is not None: - params['id'] = id_ - - return self._build_request_url('notes_history', params) - - def notes_revert(self, id_, version): - """Function to revert a specific note (Requires login)(UNTESTED). - - Parameters: - id_: - The note id to update (Type: INT). - - version: - The version to revert to. - """ - params = {'id': id_, 'version': version} - response = self._build_request_url('wiki_revert', params) - return response['success'] - - def notes_create_update(self, post_id, coor_x, coor_y, width, height, - is_active, body, id_=None): - """Function to create or update note (Requires login)(UNTESTED). - - Parameters: - post_id: - The post id number this note belongs to. - - coor_x: - The X coordinate of the note. - - coor_y: - The Y coordinate of the note. - - width: - The width of the note. - - height: - The height of the note. - - is_active: - Whether or not the note is visible. Set to 1 for active, 0 for - inactive. - - body: - The note message. - - id_: - If you are updating a note, this is the note id number to - update. - """ - params = {'note[post_id]': post_id, 'note[x]': coor_x, - 'note[y]': coor_y, 'note[width]': width, - 'note[height]': height, 'note[body]': body} - - if id_ is not None: - params['id'] = id_ - if is_active <= 1: - params['note[is_active]'] = is_active - else: - raise PybooruError("is_active parameters required 1 or 0") - - return self._build_request_url('notes_create_update', params) - - def users_search(self, name=None, id_=None): - """Search users. - - If you don't specify any parameters you'll get a listing of all users. - - Parameters: - name: - The name of the user. - - id_: - The id number of the user. - """ - if name is not None: - params = {'name': name} - return self._build_request_url('users_search', params) - elif id_ is not None: - params = {'id': id_} - return self._build_request_url('users_search', params) - else: - return self._build_request_url('users_search') - - def forum_list(self, parent_id=None): - """Function to get forum posts. - - If you don't specify any parameters you'll get a listing of all users. - - Parameters: - parent_id: - The parent ID number. You'll return all the responses to that - forum post. - """ - if parent_id is not None: - params = {'parent_id': parent_id} - return self._build_request_url('forum_list', params) - else: - return self._build_request_url('forum_list') - - def pools_list(self, query=None, page=1): - """Function to get pools. - - If you don't specify any parameters you'll get a list of all pools. - - Parameters: - query: - The title. - - page: - The page. - """ - params = {'page': page} - - if query is not None: - params['query'] = query - - return self._build_request_url('pools_list', params) - - def pools_posts(self, id_=None, page=1): - """Function to get pools posts. - - If you don't specify any parameters you'll get a list of all pools. - - Parameters: - id_: - The pool id number. - - page: - The page. - """ - params = {'page': page} - - if id_ is not None: - params['id'] = id_ - - return self._build_request_url('pools_posts', params) - - def pools_update(self, id_, name, is_public, description): - """Function to update a pool (Requires login)(UNTESTED). - - Parameters: - id_: - The pool id number. - - name: - The name. - - is_public: - 1 or 0, whether or not the pool is public. - - description: - A description of the pool. - """ - params = {'id': id_, 'pool[name]': name, - 'pool[description]': description} - - if is_public <= 1: - params['pool[is_public]'] = is_public - else: - raise PybooruError("is_public require 1 or 0") - - return self._build_request_url('pools_update', params) - - def pools_create(self, name, is_public, description): - """Function to create a pool (Require login)(UNTESTED). - - Parameters: - name: - The name. - - is_public: - 1 or 0, whether or not the pool is public. - - description: - A description of the pool. - """ - params = {'pool[name]': name, 'pool[description]': description} - - if is_public <= 1: - params['pool[name]'] = is_public - else: - raise PybooruError("is_public required 1 or 0") - - return self._build_request_url('pools_create', params) - - def pools_destroy(self, id_): - """Function to destroy a specific pool (Require login)(UNTESTED). - - Parameters: - id_: - The pool id number (Type: INT). - """ - params = {'id': id_} - response = self._build_request_url('pools_destroy', params) - return response['success'] - - def pools_add_post(self, pool_id, post_id): - """Function to add a post (Require login)(UNTESTED). - - Parameters: - pool_id: - The pool to add the post to. - - post_id: - The post to add. - """ - params = {'pool_id': pool_id, 'post_id': post_id} - return self._build_request_url('pools_add_post', params) - - def pools_remove_post(self, pool_id, post_id): - """Function to remove a post (Require login)(UNTESTED). - - Parameters: - pool_id: - The pool to remove the post to. - - post_id: - The post to remove. - """ - params = {'pool_id': pool_id, 'post_id': post_id} - return self._build_request_url('pools_remove_post', params) - - def favorites_list_users(self, id_): - """Function to return a list with all users who have added to favorites - a specific post. - - Parameters: - id_: - The post id (Type: INT). - """ - params = {'id': id_} - response = self._build_request_url('favorites_list_users', params) - # Return list with users - return response['favorited_users'].split(',') + try: + if method != 'GET': + # Reset content-type for data encoded as a multipart form + self.client.headers.update({'content-type': None}) + response = self.client.request(method, url, **request_args) + + self.last_call.update({ + 'API': api_call, + 'url': response.url, + 'status_code': response.status_code, + 'status': self._get_status(response.status_code), + 'headers': response.headers + }) + + if response.status_code in (200, 201, 202, 204): + return response.json() + else: + raise PybooruHTTPError("In _request", response.status_code, + response.url) + except requests.exceptions.Timeout: + raise PybooruError("Timeout! in url: {0}".format(response.url)) + except ValueError as e: + raise PybooruError("JSON Error: {0} in line {1} column {2}".format( + e.msg, e.lineno, e.colno)) diff --git a/pybooru/resources.py b/pybooru/resources.py index 5385145..3f6d5e9 100644 --- a/pybooru/resources.py +++ b/pybooru/resources.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- -"""This module contains all resources for pybooru. +"""pybooru.resources -site_list: - Is a dict that contains various based Danbooru/Moebooru, default sites. -api_base_url: - Is a dict that contains the urls for API functions. -http_status_codes: - Is a dict that contains the http status code for Danbooru/Moebooru API. +This module contains all resources for Pybooru. + +SITE_LIST: + Is a dict that contains various based Moebooru, default sites. +HTTP_STATUS_CODE: + Is a dict that contains the http status code for Moebooru API. """ @@ -15,142 +15,29 @@ SITE_LIST = { 'konachan': { 'url': "http://konachan.com", + 'api_version': "1.13.0+update.3", 'hashed_string': "So-I-Heard-You-Like-Mupkids-?--{0}--"}, - 'yandere': { 'url': "https://yande.re", - 'hashed_string': "choujin-steiner--{0}--"} - } - - -# API_BASE_URL for the API functions -API_BASE_URL = { - 'posts_list': { - 'url': "/post.json", - 'required_login': False}, - 'posts_create': { - 'url': "/post/create.json", - 'required_login': True}, - 'posts_update': { - 'url': "/post/update.json", - 'required_login': True}, - 'posts_destroy': { - 'url': "/post/destroy.json", - 'required_login': True}, - 'posts_revert_tags': { - 'url': "/post/revert_tags.json", - 'required_login': True}, - 'posts_vote': { - 'url': "/post/vote.json", - 'required_login': True}, - 'tags_list': { - 'url': "/tag.json", - 'required_login': False}, - 'tags_update': { - 'url': "/tag/update.json", - 'required_login': True}, - 'tags_related': { - 'url': "/tag/related.json", - 'required_login': False}, - 'artists_list': { - 'url': "/artist.json", - 'required_login': False}, - 'artists_create': { - 'url': "/artist/create.json", - 'required_login': True}, - 'artists_update': { - 'url': "/artist/update.json", - 'required_login': True}, - 'artists_destroy': { - 'url': "/artist/destroy.json", - 'required_login': True}, - 'comments_show': { - 'url': "/comment/show.json", - 'required_login': False}, - 'comments_create': { - 'url': "/comment/create.json", - 'required_login': True}, - 'comments_destroy': { - 'url': "/comment/destroy.json", - 'required_login': True}, - 'wiki_list': { - 'url': "/wiki.json", - 'required_login': False}, - 'wiki_create': { - 'url': "/wiki/create.json", - 'required_login': True}, - 'wiki_update': { - 'url': "/wiki/update.json", - 'required_login': True}, - 'wiki_show': { - 'url': "/wiki/show.json", - 'required_login': False}, - 'wiki_destroy': { - 'url': "/wiki/destroy.json", - 'required_login': True}, - 'wiki_lock': { - 'url': "/wiki/lock.json", - 'required_login': True}, - 'wiki_unlock': { - 'url': "/wiki/unlock.json", - 'required_login': True}, - 'wiki_revert': { - 'url': "/wiki/revert.json", - 'required_login': True}, - 'wiki_history': { - 'url': "/wiki/history.json", - 'required_login': False}, - 'notes_list': { - 'url': "/note.json", - 'required_login': False}, - 'notes_search': { - 'url': "/note/search.json", - 'required_login': False}, - 'notes_history': { - 'url': "/note/history.json", - 'required_login': False}, - 'notes_revert': { - 'url': "/note/revert.json", - 'required_login': True}, - 'notes_create_update': { - 'url': "/note/update.json", - 'required_login': True}, - 'users_search': { - 'url': "/user.json", - 'required_login': False}, - 'forum_list': { - 'url': "/forum.json", - 'required_login': False}, - 'pools_list': { - 'url': "/pool.json", - 'required_login': False}, - 'pools_posts': { - 'url': "/pool/show.json", - 'required_login': False}, - 'pools_update': { - 'url': "/pool/update.json", - 'required_login': True}, - 'pools_create': { - 'url': "/pool/create.json", - 'required_login': True}, - 'pools_destroy': { - 'url': "/pool/destroy.json", - 'required_login': True}, - 'pools_add_post': { - 'url': "/pool/add_post.json", - 'required_login': True}, - 'pools_remove_post': { - 'url': "/pool/remove_post.json", - 'required_login': True}, - 'favorites_list_users': { - 'url': "/favorite/list_users.json", - 'required_login': False} + 'api_version': "1.13.0+update.3", + 'hashed_string': "choujin-steiner--{0}--"}, + 'danbooru': { + 'url': "http://danbooru.donmai.us"} } -# HTTP_STATUS_CODES -HTTP_STATUS_CODES = { +# HTTP_STATUS_CODE +HTTP_STATUS_CODE = { 200: ("OK", "Request was successful"), + 201: ("Created" "The request has been fulfilled, resulting in the creation \ + of a new resource"), + 202: ("Accepted", "The request has been accepted for processing, but the \ + processing has not been completed."), + 204: ("No Content", "The server successfully processed the request and is \ + not returning any content."), + 400: ("Bad request", "The server cannot or will not process the request"), + 401: ("Unauthorized", "Authentication is required and has failed or has \ + not yet been provided."), 403: ("Forbidden", "Access denied"), 404: ("Not Found", "Not found"), 420: ("Invalid Record", "Record could not be saved"), diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/setup.cfg b/setup.cfg index 5e40900..2a9acf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,2 @@ -[wheel] +[bdist_wheel] universal = 1 diff --git a/setup.py b/setup.py index 31e1335..beee7a9 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # -*- coding utf-8 -*- -#!/usr/bin/env python +# !/usr/bin/env python # setuptools imports from setuptools import setup @@ -9,6 +9,7 @@ import pybooru +# Read description file with open('README.rst', 'r') as f: long_description = f.read() @@ -17,25 +18,28 @@ name="Pybooru", version=pybooru.__version__, author=pybooru.__author__, - description="Pybooru is a Python library to access API of Danbooru/Moebooru based sites.", + description="Pybooru is a Python package to access to the API of Danbooru/Moebooru based sites.", long_description=long_description, author_email="danielluque14@gmail.com", url="https://github.com/LuqueDaniel/pybooru", license="MIT License", - keywords="Pybooru moebooru danbooru API", + keywords="Pybooru moebooru danbooru API client", + packages=find_packages(), + platforms=['any'], + install_requires=['requests'], + include_package_data=True, classifiers=[ "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Internet" ], - platforms=['any'], - install_requires=['requests'], - packages=find_packages(), - include_package_data=True, ) diff --git a/tools/preview_documentation.bat b/tools/preview_documentation.bat new file mode 100644 index 0000000..c6fc09d --- /dev/null +++ b/tools/preview_documentation.bat @@ -0,0 +1,7 @@ +@echo off + +cd docs +call .\make.bat html +cd build\html +call python -m http.server 80 +cd ..\..\..\ diff --git a/tools/remove_binary.bat b/tools/remove_binary.bat new file mode 100644 index 0000000..c780bf5 --- /dev/null +++ b/tools/remove_binary.bat @@ -0,0 +1,3 @@ +DEL /S .\*.pyc + +RD /S /Q .\pybooru\__pycache__ diff --git a/tools/remove_binary.sh b/tools/remove_binary.sh new file mode 100644 index 0000000..96ee240 --- /dev/null +++ b/tools/remove_binary.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Remove binary files +find . -iname '*.py[cod]' -delete + +# Remove __pycache__ Py3k +rm --recursive --force --verbose pybooru/__pycache__