Initial commit - toot - Unnamed repository; edit this file 'description' to name the repository.
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
(DIR) commit 40a07392274e0f15f9cdce8e6d22ac4cedb6be3a
(HTM) Author: Ivan Habunek <ivan@habunek.com>
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(-)
---
(DIR) 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
(DIR) 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/*
(DIR) 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.
(DIR) diff --git a/setup.cfg b/setup.cfg
@@ -0,0 +1,2 @@
+[bdist_wheel]
+universal=1
(DIR) 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='ivan@habunek.com',
+ 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',
+ ],
+ }
+)
(DIR) 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('ivan@habunek.com', 'K2oEeDHdMEvCbAnEJjeB18sv', to_file='user_creds.txt')
+
+
+# # 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')
+
+
+
+
(DIR) 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()
(DIR) 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)
(DIR) 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, CONFIG_USER_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()