Initial commit - toot - Unnamed repository; edit this file 'description' to nam… | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit 40a07392274e0f15f9cdce8e6d22ac4cedb6be3a | |
Author: Ivan Habunek <[email protected]> | |
Date: Wed, 12 Apr 2017 16:42:04 +0200 | |
Initial commit | |
Diffstat: | |
.gitignore | 9 +++++++++ | |
Makefile | 18 ++++++++++++++++++ | |
README.rst | 28 ++++++++++++++++++++++++++++ | |
setup.cfg | 2 ++ | |
setup.py | 39 +++++++++++++++++++++++++++++++ | |
toot.py | 28 ++++++++++++++++++++++++++++ | |
toot/__init__.py | 58 ++++++++++++++++++++++++++++++ | |
toot/config.py | 57 +++++++++++++++++++++++++++++++ | |
toot/console.py | 90 +++++++++++++++++++++++++++++++ | |
9 files changed, 329 insertions(+), 0 deletions(-) | |
--- | |
diff --git a/.gitignore b/.gitignore | |
@@ -0,0 +1,8 @@ | |
+*.egg-info/ | |
+*.pyc | |
+.cache/ | |
+build/ | |
+dist/ | |
+tmp/ | |
+.pypirc | |
+/.env | |
+\ No newline at end of file | |
diff --git a/Makefile b/Makefile | |
@@ -0,0 +1,18 @@ | |
+default : clean dist | |
+ | |
+dist : | |
+ @echo "\nMaking source" | |
+ @echo "-------------" | |
+ @python setup.py sdist | |
+ | |
+ @echo "\nMaking wheel" | |
+ @echo "-------------" | |
+ @python setup.py bdist_wheel --universal | |
+ | |
+ @echo "\nDone." | |
+ | |
+clean : | |
+ rm -rf build dist *.egg-info MANIFEST | |
+ | |
+publish : | |
+ twine upload dist/* | |
diff --git a/README.rst b/README.rst | |
@@ -0,0 +1,28 @@ | |
+==== | |
+Toot | |
+==== | |
+ | |
+Post to Mastodon social networks from the command line. | |
+ | |
+ | |
+Installation | |
+------------ | |
+ | |
+Install using pip: | |
+ | |
+.. code-block:: | |
+ | |
+ pip install toot | |
+ | |
+ | |
+Usage | |
+----- | |
+ | |
+Currently implements only posting a new status: | |
+ | |
+ | |
+.. code-block:: | |
+ | |
+ toot post "Hello world!" | |
+ | |
+On first use, will ask you to choose a Mastodon instance and log in. | |
diff --git a/setup.cfg b/setup.cfg | |
@@ -0,0 +1,2 @@ | |
+[bdist_wheel] | |
+universal=1 | |
diff --git a/setup.py b/setup.py | |
@@ -0,0 +1,39 @@ | |
+#!/usr/bin/env python | |
+ | |
+from setuptools import setup | |
+ | |
+with open("README.rst") as readme: | |
+ long_description = readme.read() | |
+ | |
+setup( | |
+ name='toot', | |
+ version='0.1.0', | |
+ description='Interact with Mastodon social networks from the command line.… | |
+ long_description=long_description, | |
+ author='Ivan Habunek', | |
+ author_email='[email protected]', | |
+ url='https://github.com/ihabunek/toot/', | |
+ keywords='mastodon toot', | |
+ license='MIT', | |
+ classifiers=[ | |
+ 'Development Status :: 4 - Beta', | |
+ 'License :: OSI Approved :: MIT License', | |
+ 'Programming Language :: Python :: 2', | |
+ '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', | |
+ 'Programming Language :: Python :: 3.6', | |
+ ], | |
+ packages=['toot'], | |
+ install_requires=[ | |
+ 'future' | |
+ ], | |
+ entry_points={ | |
+ 'console_scripts': [ | |
+ 'toot=toot.console:main', | |
+ ], | |
+ } | |
+) | |
diff --git a/toot.py b/toot.py | |
@@ -0,0 +1,28 @@ | |
+from mastodon import Mastodon | |
+ | |
+# app = Mastodon.create_app('toot', to_file='app_creds.txt') | |
+# print app | |
+ | |
+# mastodon = Mastodon(client_id='app_creds.txt') | |
+# mastodon.log_in('[email protected]', 'K2oEeDHdMEvCbAnEJjeB18sv', to_file='use… | |
+ | |
+ | |
+# # Create actual instance | |
+# mastodon = Mastodon( | |
+# client_id='app_creds.txt', | |
+# access_token='user_creds.txt' | |
+# ) | |
+ | |
+# mastodon.toot('Testing') | |
+ | |
+ | |
+# import ConfigParser | |
+ | |
+# config = ConfigParser.ConfigParser() | |
+# config.read('auth.ini') | |
+ | |
+# print config.get('Auth', 'foo2') | |
+ | |
+ | |
+ | |
+ | |
diff --git a/toot/__init__.py b/toot/__init__.py | |
@@ -0,0 +1,58 @@ | |
+import requests | |
+ | |
+from collections import namedtuple | |
+ | |
+App = namedtuple('App', ['base_url', 'client_id', 'client_secret']) | |
+User = namedtuple('User', ['username', 'access_token']) | |
+ | |
+APP_NAME = 'toot' | |
+DEFAULT_INSTANCE = 'mastodon.social' | |
+ | |
+ | |
+def create_app(base_url): | |
+ url = base_url + 'api/v1/apps' | |
+ | |
+ response = requests.post(url, { | |
+ 'client_name': 'toot', | |
+ 'redirect_uris': 'urn:ietf:wg:oauth:2.0:oob', | |
+ 'scopes': 'read write', | |
+ 'website': 'https://github.com/ihabunek/toot', | |
+ }) | |
+ | |
+ response.raise_for_status() | |
+ | |
+ data = response.json() | |
+ client_id = data.get('client_id') | |
+ client_secret = data.get('client_secret') | |
+ | |
+ return App(base_url, client_id, client_secret) | |
+ | |
+ | |
+def login(app, username, password): | |
+ url = app.base_url + 'oauth/token' | |
+ | |
+ response = requests.post(url, { | |
+ 'grant_type': 'password', | |
+ 'client_id': app.client_id, | |
+ 'client_secret': app.client_secret, | |
+ 'username': username, | |
+ 'password': password, | |
+ 'scope': 'read write', | |
+ }) | |
+ | |
+ response.raise_for_status() | |
+ | |
+ data = response.json() | |
+ access_token = data.get('access_token') | |
+ | |
+ return User(username, access_token) | |
+ | |
+ | |
+def post_status(app, user, status): | |
+ url = app.base_url + '/api/v1/statuses' | |
+ headers = {"Authorization": "Bearer " + user.access_token} | |
+ | |
+ response = requests.post(url, {'status': status}, headers=headers) | |
+ response.raise_for_status() | |
+ | |
+ return response.json() | |
diff --git a/toot/config.py b/toot/config.py | |
@@ -0,0 +1,57 @@ | |
+import os | |
+ | |
+from . import User, App | |
+ | |
+CONFIG_DIR = os.environ['HOME'] + '/.config/toot/' | |
+CONFIG_APP_FILE = CONFIG_DIR + 'app.cfg' | |
+CONFIG_USER_FILE = CONFIG_DIR + 'user.cfg' | |
+ | |
+ | |
+def collapse(tuple): | |
+ return [v for k, v in tuple.__dict__.items()] | |
+ | |
+ | |
+def _load(file, tuple_class): | |
+ if not os.path.exists(file): | |
+ return None | |
+ | |
+ with open(file, 'r') as f: | |
+ lines = f.read().split() | |
+ try: | |
+ return tuple_class(*lines) | |
+ except TypeError: | |
+ return None | |
+ | |
+ | |
+def _save(file, named_tuple): | |
+ directory = os.path.dirname(file) | |
+ if not os.path.exists(directory): | |
+ os.makedirs(directory) | |
+ | |
+ with open(file, 'w') as f: | |
+ values = [v for k, v in named_tuple.__dict__.items()] | |
+ return f.write("\n".join(values)) | |
+ | |
+ | |
+def load_app(): | |
+ return _load(CONFIG_APP_FILE, App) | |
+ | |
+ | |
+def load_user(): | |
+ return _load(CONFIG_USER_FILE, User) | |
+ | |
+ | |
+def save_app(app): | |
+ return _save(CONFIG_APP_FILE, app) | |
+ | |
+ | |
+def save_user(user): | |
+ return _save(CONFIG_USER_FILE, user) | |
+ | |
+ | |
+def delete_app(app): | |
+ return os.unlink(CONFIG_APP_FILE) | |
+ | |
+ | |
+def delete_user(user): | |
+ return os.unlink(CONFIG_USER_FILE) | |
diff --git a/toot/console.py b/toot/console.py | |
@@ -0,0 +1,90 @@ | |
+import os | |
+import sys | |
+ | |
+from builtins import input | |
+from getpass import getpass | |
+ | |
+from .config import save_user, load_user, load_app, save_app, CONFIG_APP_FILE,… | |
+from . import create_app, login, post_status, DEFAULT_INSTANCE | |
+ | |
+ | |
+def green(text): | |
+ return "\033[92m{}\033[0m".format(text) | |
+ | |
+ | |
+def red(text): | |
+ return "\033[91m{}\033[0m".format(text) | |
+ | |
+ | |
+def create_app_interactive(): | |
+ instance = input("Choose an instance [{}]: ".format(DEFAULT_INSTANCE)) | |
+ if not instance: | |
+ instance = DEFAULT_INSTANCE | |
+ | |
+ base_url = 'https://{}'.format(instance) | |
+ | |
+ print("Creating app with {}".format(base_url)) | |
+ app = create_app(base_url) | |
+ | |
+ print("App tokens saved to: {}".format(green(CONFIG_APP_FILE))) | |
+ save_app(app) | |
+ | |
+ | |
+def login_interactive(app): | |
+ print("\nLog in to " + green(app.base_url)) | |
+ email = input('Email: ') | |
+ password = getpass('Password: ') | |
+ | |
+ print("Authenticating...") | |
+ user = login(app, email, password) | |
+ | |
+ save_user(user) | |
+ print("User token saved to " + green(CONFIG_USER_FILE)) | |
+ | |
+ return user | |
+ | |
+ | |
+def print_usage(): | |
+ print("toot - interact with Mastodon from the command line") | |
+ print("") | |
+ print("Usage:") | |
+ print(" toot post \"All your base are belong to us\"") | |
+ print("") | |
+ print("https://github.com/ihabunek/toot") | |
+ | |
+ | |
+def cmd_post_status(app, user): | |
+ if len(sys.argv) < 3: | |
+ print red("No status text given") | |
+ return | |
+ | |
+ response = post_status(app, user, sys.argv[2]) | |
+ | |
+ print "Toot posted: " + green(response.get('url')) | |
+ | |
+ | |
+def cmd_auth(app, user): | |
+ if app and user: | |
+ print("You are logged in") | |
+ print("Mastodon instance: " + green(app.base_url)) | |
+ print("Username: " + green(user.username)) | |
+ else: | |
+ print("You are not logged in") | |
+ | |
+ | |
+def main(): | |
+ command = sys.argv[1] if len(sys.argv) > 1 else None | |
+ | |
+ if os.getenv('TOOT_DEBUG'): | |
+ import logging | |
+ logging.basicConfig(level=logging.DEBUG) | |
+ | |
+ app = load_app() or create_app_interactive() | |
+ user = load_user() or login_interactive(app) | |
+ | |
+ if command == 'post': | |
+ cmd_post_status(app, user) | |
+ elif command == 'auth': | |
+ cmd_auth(app, user) | |
+ else: | |
+ print_usage() |