├── tests ├── fixtures │ ├── a │ │ ├── a.png │ │ ├── b.png │ │ └── c.png │ └── b │ │ ├── a.png │ │ ├── b.png │ │ └── c.png ├── __init__.py └── tests_ibk.py ├── imagebotkit ├── __init__.py ├── cli.py └── imagebotkit.py ├── tox.ini ├── README.md ├── .travis.yml ├── Makefile ├── .gitignore └── setup.py /tests/fixtures/a/a.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/a/b.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/a/c.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/b/a.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/b/b.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/b/c.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkit 6 | 7 | # Licensed under the MIT license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | 11 | from . import tests_ibk 12 | -------------------------------------------------------------------------------- /imagebotkit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkit 6 | 7 | # Licensed under the MIT license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | 11 | from . import imagebotkit 12 | 13 | __version__ = '0.1.0' 14 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # This file is part of imagebot. 2 | # https://github.com/fitnr/imagebotkit 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/GPL-3.0 6 | # Copyright (c) 2015, imagebot 7 | 8 | [tox] 9 | envlist = py27, py34 10 | 11 | [testenv] 12 | commands = 13 | pip install docutils 14 | make test 15 | 16 | whitelist_externals = 17 | make 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # image bot kit 2 | 3 | Create a bot that post images (and video) to Twitter. 4 | 5 | ````bash 6 | # Update Twitter with 'image.png' 7 | imagebot screen_name image.png --status "Hello World!" 8 | 9 | # Update Twitter with a random image from 'folder/with/images' 10 | imagebot screen_name folder/with/images --status "Hello World!" 11 | 12 | # Post a random image, using 'record.txt' as a list of previously-posted images 13 | imagebot screen_name folder/with/images --status "Hello World!" --record record.txt 14 | ```` 15 | 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This file is part of imagebot. 2 | # https://github.com/fitnr/imagebotkitkit 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/GPL-3.0 6 | # Copyright (c) 2015, imagebot 7 | 8 | language: python 9 | 10 | python: 11 | - 2.7 12 | - 3.3 13 | - 3.4 14 | - 3.5 15 | - pypy 16 | 17 | before_install: 18 | - pip install coverage docutils 19 | 20 | install: 21 | - make install 22 | 23 | script: 24 | - coverage run --include=imagebotkit/* setup.py test 25 | - coverage report 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This file is part of imagebot. 2 | # https://github.com/fitnr/imagebotkit 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/GPL-3.0 6 | # Copyright (c) 2015, imagebot 7 | 8 | .PHONY: install deploy clean 9 | install test develop: %: README.rst 10 | python setup.py $* 11 | 12 | README.rst: README.md 13 | pandoc $< -o $@ || touch $@ 14 | python setup.py check --restructuredtext --strict 15 | 16 | deploy: README.rst | clean 17 | python setup.py bdist_wheel 18 | python3 setup.py bdist_wheel 19 | twine upload dist/* 20 | git push 21 | git push --tags 22 | 23 | clean: ; rm -rf dist 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is part of imagebot. 2 | # https://github.com/fitnr/imagebotkit 3 | 4 | # Licensed under the MIT license: 5 | # http://opensource.org/licenses/GPL-3.0 6 | # Copyright (c) 2015, imagebot 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | bin/ 19 | build/ 20 | develop-eggs/ 21 | .eggs 22 | dist/ 23 | eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Sphinx documentation 46 | docs/_build/ 47 | 48 | README.rst 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkit 6 | 7 | # Licensed under the GPL-3.0 license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | 11 | from setuptools import setup, find_packages 12 | 13 | try: 14 | readme = open('./README.rst', 'r').read() 15 | except IOError: 16 | readme = '' 17 | 18 | setup( 19 | name='imagebot', 20 | version='0.1.0', 21 | description='Kit for a simple bot that uploads images to twitter', 22 | long_description=readme, 23 | keywords='twitter bot image', 24 | author='imagebot', 25 | author_email='fitnr@fakeisthenewreal', 26 | url='https://github.com/fitnr/imagebotkit', 27 | license='MIT', 28 | classifiers=[ 29 | 'Development Status :: 4 - Beta', 30 | 'Intended Audience :: Developers', 31 | 'License :: OSI Approved :: MIT License', 32 | 'Natural Language :: English', 33 | 'Operating System :: Unix', 34 | 'Programming Language :: Python :: 2.7', 35 | 'Programming Language :: Python :: 3.4', 36 | 'Programming Language :: Python :: Implementation :: PyPy', 37 | 'Operating System :: OS Independent', 38 | ], 39 | packages=find_packages(), 40 | include_package_data=False, 41 | test_suite='tests', 42 | install_requires=[ 43 | 'twitter_bot_utils>=0.9.7.1,<1' 44 | ], 45 | entry_points={ 46 | 'console_scripts': [ 47 | 'imagebot=imagebotkit.cli:main', 48 | ], 49 | }, 50 | ) 51 | -------------------------------------------------------------------------------- /imagebotkit/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkitkit 6 | 7 | # Licensed under the MIT license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | 11 | import argparse 12 | import twitter_bot_utils as tbu 13 | from . import imagebotkit 14 | 15 | def main(): 16 | parent = tbu.args.parent() 17 | parser = argparse.ArgumentParser('imagebot', usage='%(prog)s [options] screen_name path [path ...]', parents=(parent,)) 18 | 19 | parser.add_argument('screen_name', type=str, help='Twitter user') 20 | parser.add_argument('path', nargs='+', help='files, or directories to scan for images') 21 | parser.add_argument('-r', '--record', type=str, help='text file with list of files to not post. Posted images will be added') 22 | parser.add_argument('-C', '--count', type=int, choices=(1, 2, 3, 4), default=1, help='number of images to post (default: 1)') 23 | parser.add_argument('--status', type=str, default=' ', help='tweet text') 24 | 25 | args = parser.parse_args() 26 | 27 | logger = tbu.args.add_logger(args.screen_name, args.verbose) 28 | logger.debug('imagebotkit: starting with %s', args.screen_name) 29 | 30 | image_paths = imagebotkit.pick_image(args.path, args.count, args.record) 31 | logger.debug('imagebotkit: got images %s', image_paths) 32 | 33 | api_args = ['key', 'secret', 'consumer_key', 'consumer_secret'] 34 | 35 | kwargs = {k: getattr(args, k) for k in api_args if getattr(args, k, None)} 36 | 37 | if not args.dry_run: 38 | twitter = tbu.api.API(args.screen_name, config=args.config_file, **kwargs) 39 | 40 | media_strings = imagebotkit.post_images(twitter, image_paths) 41 | 42 | twitter.update_status(status=args.status, media_ids=media_strings) 43 | 44 | imagebotkit.update_record(image_paths, args.record) 45 | 46 | 47 | if __name__ == '__main__': 48 | main() 49 | -------------------------------------------------------------------------------- /tests/tests_ibk.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkit 6 | 7 | # Licensed under the MIT license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | import unittest 11 | import os 12 | from imagebotkit import imagebotkit 13 | 14 | class testImageBotKit(unittest.TestCase): 15 | 16 | def setUp(self): 17 | self.fixtures = os.path.join(os.path.dirname(__file__), 'fixtures') 18 | 19 | def testValidImage(self): 20 | assert imagebotkit.valid_image('foo.png') is True 21 | assert imagebotkit.valid_image('foo.jpeg') is True 22 | assert imagebotkit.valid_image('foo.pdf') is False 23 | 24 | def generate_record(self): 25 | names = (os.path.join(self.fixtures, x, 'a.png')+"\n" for x in ('a', 'b')) 26 | with open('record.txt', 'w') as f: 27 | f.writelines(names) 28 | return names 29 | 30 | def testPast_posts(self): 31 | self.generate_record() 32 | past_posts = imagebotkit.past_posts('record.txt') 33 | assert os.path.join(self.fixtures, 'a', 'a.png') in past_posts 34 | os.remove('record.txt') 35 | 36 | def testPick_image(self): 37 | previous = self.generate_record() 38 | 39 | glob = os.path.join(self.fixtures, '[ab]/') 40 | picked = imagebotkit.pick_image(glob, 1, os.path.join(self.fixtures, "record.txt")) 41 | assert picked[0] not in previous 42 | os.remove('record.txt') 43 | 44 | def testPick_single_image(self): 45 | path = os.path.join(self.fixtures, 'a', 'a.png') 46 | picked = imagebotkit.pick_image(path) 47 | 48 | assert picked[0] == path 49 | 50 | def testUpdateRecord(self): 51 | paths = ['a.png', 'b.png', 'c.png'] 52 | fn = "foo.txt" 53 | imagebotkit.update_record(paths, fn) 54 | 55 | with open(fn) as f: 56 | contents = f.read() 57 | 58 | for p in paths: 59 | assert p in contents 60 | 61 | os.remove(fn) 62 | 63 | if __name__ == '__main__': 64 | unittest.main() 65 | -------------------------------------------------------------------------------- /imagebotkit/imagebotkit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # This file is part of imagebot. 5 | # https://github.com/fitnr/imagebotkitkit 6 | 7 | # Licensed under the MIT license: 8 | # http://opensource.org/licenses/GPL-3.0 9 | # Copyright (c) 2015, imagebot 10 | 11 | import os.path 12 | from os import linesep, walk 13 | from glob import glob 14 | from random import random 15 | from tweepy.error import TweepError 16 | 17 | VALID_IMAGE_EXTS = ( 18 | '.jpg', 19 | '.jpeg', 20 | '.gif', 21 | '.png', 22 | '.webp' 23 | ) 24 | 25 | try: 26 | basestring 27 | except NameError: 28 | basestring = str 29 | 30 | def valid_image(filename): 31 | return os.path.splitext(filename)[1] in VALID_IMAGE_EXTS 32 | 33 | def past_posts(record): 34 | past_images = set() 35 | if record and os.path.exists(record): 36 | with open(record) as f: 37 | past_images = set(x.strip() for x in f.readlines()) 38 | 39 | return past_images 40 | 41 | 42 | def pick_image(fileglobs, count=None, record=None): 43 | ''' 44 | :fileglobs list paths or globs 45 | :count int Number of paths to return. Default: 1 46 | :record str Name of file containing paths to exclude 47 | ''' 48 | count = count or 1 49 | 50 | past_images = past_posts(record) 51 | prospectives = set() 52 | if isinstance(fileglobs, basestring): 53 | fileglobs = (fileglobs,) 54 | 55 | for globule in fileglobs: 56 | globule = os.path.expandvars(os.path.expanduser(globule)) 57 | 58 | if os.path.isfile(globule): 59 | prospectives.update([globule]) 60 | 61 | else: 62 | for path in glob(globule): 63 | for dirpath, _, files in walk(path): 64 | images = (os.path.join(dirpath, fn) for fn in files if valid_image(fn)) 65 | prospectives.update(images) 66 | 67 | prospectives = prospectives.difference(past_images) 68 | 69 | possibles = sorted(list(prospectives), key=lambda _: random()) 70 | 71 | return possibles[0:count] 72 | 73 | def post_images(api, paths): 74 | media_ids = [] 75 | 76 | for path in paths: 77 | try: 78 | media = api.media_upload(path) 79 | print(media) 80 | except TweepError: 81 | raise 82 | 83 | media_ids.append(media.media_id_string) 84 | 85 | return media_ids 86 | 87 | def update_record(image_paths, record): 88 | with open(record, 'a') as f: 89 | f.writelines((i + linesep for i in image_paths)) 90 | --------------------------------------------------------------------------------