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()