{ "info": { "author": "Leonard Richardson", "author_email": "leonardr@segfault.org", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Programming Language :: Python :: 2", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Artistic Software", "Topic :: Text Processing" ], "description": "# Botfriend\n\nBotfriend is a Python framework for managing a lot of creative bots\nthat post to a number of different services.\n\nI think the primary features of Botfriend are these:\n\n* Minimal Python coding -- just write the interesting part of your bot.\n* Simple configuration based on YAML.\n* Easy scheduling of posts.\n* Each bot can post to Twitter and/or Mastodon. (Tumblr support is planned.)\n* Built-in access to art supplies through [Olipy](https://github.com/leonardr/olipy).\n\nBotfriend is a Python library that runs on a server. If you're not\ncomfortable with setting up a cron job, or writing Python code, I\nrecommend you instead check out [Cheap Bots, Done\nQuick](http://cheapbotsdonequick.com/) or [Cheap Bots, Toot\nSweet!](https://cheapbotstootsweet.com/), as a simpler way to express\nyour creativity.\n\n# The Story\n\nI wrote Botfriend to manage about [thirty different Twitter\nbots](https://www.crummy.com/features/) that I created. I found myself\nconstantly copying and pasting, writing the same code over and\nover. Every bot does something different, but they all have certain\nbasic needs: connecting to various services and APIs, deciding when to\npost something, managing backlogs of content, and so on. There's no\nreason each bot needs its own version of this code. The only part of a\nbot that needs new code is the creative bit.\n\nMy other big problem was, I've come to dislike Twitter. It's a great\nplatform for creative bots, but every time I created a bot, I felt\nguilty about increasing the value of an platform I think is making the\nworld worse. (Also, Twitter has started suspending my bots for no reason.)\n\nI didn't want to give up my botmaking hobby, so I started\ninvestigating the world of [Mastodon](https://joinmastodon.org/)\nbots. This created another problem: it's a big pain to rewrite thirty\nbots to post to a different service. If I was going to do that much\nwork, I wanted the end product to be a reusable library that could\nsave everyone time.\n\nSo I went through my thirty bots, rewrote everything, and moved all of\nthe reusable code into Botfriend. Now my bots are a lot smaller and\neasier to manage. All of the tedious code is in one place, and I can\nfocus on the fun part of bot-writing.\n\nConsider a bot like [A Dull Bot](https://botsin.space/@ADullBot)\n([source](https://github.com/leonardr/botfriend/tree/master/bots.sample/a-dull-bot)). Creating\nthis bot was a fair amount of work. But all of the work went into the\nfun part: creating an accurate software model of the typewriter from\n_The Shining_. There's no code for making sure the bot posts once an\nhour, or for pushing the typewritten text through the Twitter or\nMastodon APIs. Botfriend takes care of all that stuff.\n\nIf you want to save code on your own bot projects, port your bots from\nTwitter to Mastodon, or just expand the reach of your bots, I hope\nyou'll consider Botfriend.\n\n# Setup\n\nI recommend you run Botfriend in a Python virtual environment. Here's\nhow to create a virtual environment called `env` and install Botfriend\ninto it.\n\n```\n$ virtualenv env\n$ source env/bin/activate\n$ pip install botfriend\n```\n\nYou'll interact with Botfriend exclusively through command-line\nscripts. From this point on I'll be giving lots of example command\nlines. All of my examples assume you've entered the Botfriend virtual\nenvironment by running this command beforehand:\n\n```\n$ source env/bin/activate\n```\n\n# A simple example: Number Jokes\n\nBy default, Botfriend expects you to put the source code for the bots\nin a directory `bots/`, located in the same directory as your virtual\nenvironment. So if your virtual environment is located in\n`/home/myusername/botfriend/env`, Botfriend will expect your bots to\nlive underneath `/home/myusername/botfriend/bots`.\n\nThe Botfriend database itself will be stored in the bot directory as\n`botfriend.sqlite`.\n\nIf you want to store your Botfriend data somewhere other than `bots/`,\nevery Botfriend script takes a `--config` argument that points to your\nbot directory. But most of the time, `bots/` is fine.\n\nEach individual bot will live in a subdirectory of your bot directory,\nnamed after the bot. Let's get started with a simple example, called\n`number-jokes`.\n\n```\n$ mkdir bots\n$ mkdir bots/number-jokes\n```\n\nEach bot needs to contain two special files: `__init__.py` for source\ncode and `bot.yaml` for configuration.\n\n## `__init__.py`: Coming up with the joke\n\nImagine walking up to to a comedian and saying \"Tell me a joke!\" A\nhuman comedian probably won't appreciate it, but this is what bots\nlive for. For a Botfriend bot, `__init__.py` is where the comedian\ncomes up with their jokes.\n\nTo get started, we'll make a simple bot that makes up observational\nhumor about numbers.\n\nTo get started, open up `bots/number-jokes/__init__.py` in a text\neditor and write this in there:\n\n```\nimport random\nfrom botfriend.bot import TextGeneratorBot\n\nclass NumberJokes(TextGeneratorBot):\n\n def generate_text(self):\n \"\"\"Tell a joke about numbers.\"\"\"\n num = random.randint(1,10)\n arguments = dict(\n num=num,\n plus_1=num+1,\n plus_3=num+3\n )\n setup = \"Why is %(num)d afraid of %(plus_1)d? \"\n punchline = \"Because %(plus_1)d ate %(plus_3)d!\"\n return (setup + punchline) % arguments\n\nBot = NumberJokes\n```\n\nBotfriend provides a lot of utilities to help you write a good\n`__init__.py`, but there's only one hard-and-fast rule: by the end of\nthe file, you have to have a class called `Bot`. The Botfriend scripts\nare going to load your `Bot` class, instantiate it, and use it to\ndo... whatever the bot does.\n\nSome bots do a lot of work to come up with a single \"joke\". They might\ndraw pictures, do database queries, make API calls, all sorts of\ncomplicated things. As befits an example, `NumberJokes` here does\nalmost no work. It just picks a random number and puts it into a string.\n\n## `bot.yaml`: Telling the joke\n\nLike most comedians, bots are constantly coming up with jokes. But if\nno one ever hears the joke, what's the point? The `bot.yaml` file\nexplains how a Botfriend bot should _tell_ its jokes to the public.\n\nOpen up the file `bots/number-jokes/bot.yaml` and write this in there:\n\n```\nname: \"Number Jokes\"\nschedule: 60\npublish:\n file:\n filename: \"number-jokes.txt\"\n```\n\nLike `__init__.py`, `bot.yaml` can get really complicated, but most of\nthe time it's pretty simple. This file is saying:\n\n* The name of the bot is \"Number Jokes\".\n\n* The bot should 'tell a joke' once an hour.\n\n* This bot tells jokes by writting them to the file\n `number-jokes.txt`. (This is relative to the bot directory, so it's going\n to be in `bots/number-jokes/number-jokes.txt`.)\n\nNow you're ready to make your bot tell some jokes, using some basic\nBotfriend scripts.\n\n# The basic scripts\n\n## `botfriend.post`\n\nThe `botfriend.post` script makes each of your bots come up with a\njoke and tell it. Run it now:\n\n```\n$ botfriend.post\n# Number Jokes | file | Published 2019-01-20 | Why is 4 afraid of 5\u2026 \n```\n\nNow look at the file you configured in `bot.yaml`. You told Number\nJokes to post its jokes to `bots/number-jokes/number-jokes.txt`. That file\ndidn't exist before you ran `botfriend.post`, but now it does exist,\nand it's got a joke in it:\n\n```\n$ cat bots/number-jokes/number-jokes.txt\n2019-01-20 10:23:44 | Why is 4 afraid of 5? Because 5 ate 7!\n```\n\nHilarious, right? You'll be running this script a lot, probably as\npart of an automated process. On my site I run `botfriend.post` every\nfive minutes. (I show an example of how to do this at the end of this\ndocument.)\n\nIf your bot isn't scheduled to tell a joke, `botfriend.post` will do\nnothing. Run it again now -- nothing will happen.\n\n```\n$ botfriend.post\n```\n\nNumber Jokes told a joke the first time you ran it, and (as you told\nit in `bot.yaml`) it's only supposed to tell one joke an hour. So, no\nnew joke.\n\nIf you were to wait an hour and run `botfriend.post` again, you'd get\nanother joke. But don't wait -- keep going through this tutorial!\n\n## Running a script on just one bot\n\nBy specifying a directory name on the command line, you can make\n`botfriend.post` (and most other Botfriend scripts) operate on just\none bot, not all of your bots. Right now, it doesn't make a\ndifference, because you only have one bot, but here's how to do it:\n\n```\n$ botfriend.post number-jokes\n```\n\n## Forcing a bot to post\n\nYou can use `--force` to make a bot tell a joke even if its schedule\nwouldn't normally allow it.\n\n```\n$ botfriend.post number-jokes --force\n# LOG 2019-01-20 | Number Jokes | file | Published 2019-01-20 | Why is 9 afraid of 10\u2026 \n```\n\nNow `bots/number-jokes/number-jokes.txt` contains two jokes.\n\n```\n$ cat bots/number-jokes/number-jokes.txt\n2019-01-20 10:23:44 | Why is 4 afraid of 5? Because 5 ate 7!\n2019-01-20 10:26:12 | Why is 9 afraid of 10? Because 10 ate 11!\n```\n\n## `botfriend.dashboard`\n\nThis script is good for getting an overview of your bots. It shows\nwhat they've been up to lately and when they're scheduled to post again.\n\n```\n$ botfriend.dashboard number-jokes\n# Number Jokes | Most recent post: Why is 9 afraid of 10? Because 10 ate 12!\n# Number Jokes | file posted 0m ago (2019-01-20 10:26:12)\n# Number Jokes | Next post in 59m\n```\n\n## `botfriend.bots`\n\nIf you have a lot of bots, it can be annoying to remember all their\nnames. The `botfriend.bots` script just lists all the bots known to\nBotfriend.\n\n```\n$ botfriend.bots\n# number-jokes\n```\n\nStill only one bot so far.\n\n## `botfriend.test.stress`\n\nIt's difficult to test a bot that does random things. You might have a\nbug that makes the bot crash only one time in a thousand. Or your bot\nmight never crash, but sometimes take a long time to run.\n\nThis is why we have the `botfriend.test.stress` script, which asks a\nbot to come up with ten thousand jokes in a row. The jokes aren't\npublished anywhere; the goal is just to give a good test of all the\npossible cases that might happen inside your bot.\n\nSince Number Jokes is really simple, it can generate ten thousand jokes\nwith no problem, although some of them are repeats:\n\n```\n$ botfriend.test.stress number-jokes\n# Why is 2 afraid of 3? Because 3 ate 5!\n# Why is 7 afraid of 8? Because 8 ate 10!\n# Why is 1 afraid of 2? Because 2 ate 4!\n# Why is 4 afraid of 5? Because 5 ate 7!\n# Why is 4 afraid of 5? Because 5 ate 7!\n# Why is 7 afraid of 8? Because 8 ate 10!\n# ... etc. etc. ...\n```\n\nIf you've got a complicated bot, it can be a good idea to run\n`botfriend.test.stress` on it a couple of times before using it for real.\n\n# How to publish\n\nThere are a few more interesting features of Botfriend, but let's take\na minute to talk about the boring features. It's easy to make a bot\nwrite its posts to a file, but nobody's going to see that. What you\nreally need is to get some Twitter or Mastodon credentials. (Specific\ninstructions are below.)\n\nOnce you have some credentials, open up your bot's `bot.yaml` file,\nadd your credentials to the `publish` configuration setting. This will\ngive your bot additional ways to publish its posts.\n\nHere's an example. This is what the configuration for Number Jokes\nwould look like if it had Twitter and Mastodon connections set up, in\naddition to writing everything to a file.\n\n```\nname: Number Jokes\nschedule: 60\npublish:\n file:\n filename: number-jokes.txt\n mastodon:\n api_base_url: 'https://botsin.space/'\n client_id: cc13bf3de67fb399475c315e4a9bf5dd4dfb7ea0f3a521fca72a9c8bf02075ab\n client_secret: 0946d2634d7b6aa1ea93af4b183fccf14e9df2e2b55db8fcdb0c8a5f267ff312\n access_token: c76eb18c5c0dc7c1fe09b53ac175b3b9ed081b0e43ea4d60e94ee721b83c1eda\n twitter:\n consumer_key: t7CfbbNLB3jfoAKI\n consumer_secret: 2teOyqqgFpFpytFanuOXzfjvR3vEmYH3\n access_token: 3341062559-SbUlEDFCDn6k6vHHDWGwqlK0wyZ0fKRegaZMyS9lwBa4L5VXY5fdl\n access_token_secret: ALc2CPkkrSBf33swYluxEgdC0GNueQK3x6D4pEr8GGDpqrmed\n```\n\n(These credentials won't work -- I made them up to resemble real\nTwitter and Mastodon credentials.)\n\n## Publish to a file\n\nThis is the simplest publication technique, and it's really only good\nfor testing and for keeping a log. The `file` publisher takes one\nconfiguration setting: `filename`, the name of the file to write to.\n\n```\npublish:\n file:\n filename: \"anniversary.txt\"\n```\n\n## Publish to Twitter\n\nTo get your bot on Twitter, you need to create a Twitter account for\nthe bot, log in as the bot, and then get four different values:\n`consumer_key`, `consumer_secret`, `access_token` and\n`access_token_secret`. These four values, when inserted into\n`bot.yaml`, give you the ability to post to a specific Twitter account\nusing the Twitter API.\n\nGetting those four values can be tricky, and Twitter periodically\nchanges up the rules and the processes. [Bot Wiki links to various\ntutorials](https://botwiki.org/resource/tutorial/how-to-make-a-twitter-bot-the-definitive-guide/) for setting this stuff up.\n\nOnce you have these four values, put them into `bot.yaml`, and your bot\nwill be able to post to its Twitter account.\n\n## Publish to a Mastodon instance\n\nTo connect your bot to Mastodon, you create a Mastodon account for the\nbot, log in as the bot, and then get four values.\n\nFirst, `api_base_url`-- this is easy, it's just the URL to the\nMastodon instance you used to create the account. I like to use\n[botsin.space](https://botsin.space/), a Mastodon instance created\nespecially for bots.\n\nThen you need to get `client_id`, `client_secret`, and `access_token`.\nYou can get these values by logging in as your bot, going to the\nSettings page, clicking \"Development\", and creating a new\napplication. (If that's not working for you, try [Darius Kazemi's\ninstructions](https://tinysubversions.com/notes/mastodon-bot/index.html).)\n\nOnce you have these four values, put them into `bot.yaml`, and your bot\nwill be able to post to its Mastodon account.\n\nOkay, now back to the cool bots you can write with Botfriend.\n\n## `botfriend.test.publisher`: test your publishing credentials\n\nThe `botfriend.test.publisher` script tries out all of your bots'\npublishing credentials to make sure they work. If a bot is having\ntrouble posting, the problem will show up here.\n\nFor every bot with a publishing technique that works, you'll get a\nline that starts with `GOOD`. For every publishing technique that's\nbroken, you'll get a line that starts with `FAIL`.\n\nIn this example, writing to a file works fine, but since the Twitter\nand Mastodon credentials are made up, Twitter and Mastodon won't\nactually accept them.\n\n```\n$ botfriend.test.publisher\n# GOOD Number Jokes file\n# FAIL Number Jokes twitter: [{u'message': u'Bad Authentication data.', u'code': 215}]\n# FAIL Number Jokes mastodon: {u'error': u'The access token is invalid'}\n```\n\n# Bots that keep a backlog: Boat Names\n\nSome comedians can come up with original jokes on the fly, over and\nover again. Others keep a Private Joke File: a list of jokes assembled\nahead of time which they can dip into as necessary.\n\nInstead of writing a bunch of generator code in a Botfriend bot, you\ncan generate a _backlog_ of posts, however you like. It's easy to\ncreate a bot that simply posts items from its backlog, in order, one\nat a time.\n\nIf your style is more writing than programming, you can just write the\nbacklog in a text editor. This way you can create a Botfriend bot\nwithout writing any code at all.\n\nLet's make a simple backlog bot that posts interesting names for\nboats, taken from the website [Ten Thousand Boat\nNames](http://www.10000boatnames.com/). (What I'm about to describe is\nexactly how my real bot [Boat Names](https://botsin.space/@BoatNames)\nworks.)\n\nFirst, make a directory for the bot:\n\n```\n$ mkdir bots/boat-names\n```\n\nThis bot is so simple that you don't need any code to program its\nbehavior. Just create an empty `__init__.py` file, so that Botfriend\nknows this is a bot and not some random directory.\n\n```\n$ touch bots/boat-names/__init__.py\n```\n\nJust like Number Jokes, Boat Names needs a `bot.yaml` to tell it where\nto post and how often. Create `bots/boat-names/bot.yaml` and put this\ntext in there:\n\n```\nname: Boat Names\npublish:\n file: boat-names.txt\nschedule:\n mean: 480\n stdev: 15\n```\n\nThe schedule here is a little different than in the first example\nbot. The first example's posts will come exactly one hour apart. This\nbot posts every eight hours (480 minutes), _on average_, but there is\nsome random variation--usually up to fifteen or thirty minutes in\neither direction.\n\nNow, running `botfriend.bots` will list both of your bots.\n\n```\n$ botfriend.bots\n# boat-names\n# number-jokes\n```\n\nRunning `botfriend.post` will tell both bots to post something if they\nwant, but Boat Names can never post anything, because it has no\nbacklog and no logic for generating new posts. It can't come up with\nits own jokes -- you have to help it.\n\n## `botfriend.backlog.load`\n\nThe `botfriend.backlog.load` script lets you add items to a bot's\nbacklog from a file. The simplest way to do this is with a text file\ncontaining one post per line.\n\nLet's create a backlog file. This can go anywhere, but I\nrecommend keeping it in the same directory as the rest of the bot, in\ncase something goes wrong and you need to recreate it. Open up a file\n`bots/boat-names/backlog.txt` and put a few boat names in it:\n\n```\nHonukele\nLA PARISIENNE\nStryss\nCozy Cat\nHull # 14\nAlways On Vacation\nSea Deuce\nBay Viewer\nTanden\nClean Livin'\nGoodnight Moon\nSPECIAL OCASSION\nInnocent Dream\n```\n\nNow you can load the backlog:\n\n```\n$ botfriend.backlog.load boat-names --file=bots/boat-names/backlog.txt\n# LOG | Backlog load script | Appended 13 items to backlog.\n# LOG | Backlog load script | Backlog size now 13 items\n```\n\nOnce there are items in the backlog, `botfriend.post` will work:\n\n```\n# botfriend.post\n# LOG | Boat Names | file | Published 2019-01-20 03:15 | Honukele\n```\n\n## `botfriend.backlog.show`\n\nThe `botfriend.backlog.show` script will summarize a bot's current\nbacklog. It'll show you how many items are in the backlog and what's\ncoming up next.\n\n```\n$ botfriend.backlog.show boat-names\n# Boat Names | 12 posts in backlog\n# Boat Names | LA PARISIENNE\n```\n\n## `botfriend.backlog.clear`\n\nThe `botfriend.backlog.clear` script will completely erase a bot's\nbacklog.\n\n```\n$ bin/backlog.clear boat-names\n# Boat Names | About to clear the backlog for Boat Names.\n# Boat Names | Sleeping for 2 seconds to give you a chance to Ctrl-C.\n\n$ bin/backlog.show boat-names\n# Boat Names | No backlog.\n```\n\n# Bots that keep state: Web Words\n\nSometimes a bot needs to do something that takes a long time, or\nsomething that might be annoying if it happened\nfrequently. Botfriend allows this difficult or annoying thing to be\ndone rarely. The results are stored in the bot's _state_ for later\nreference.\n\nLet's create one more example bot. This one's called \"Web Words\". Its\njob is to download random web pages and pick random phrases from them.\n\n```\n$ mkdir bots/web-words\n```\n\nWe're going to split the \"download a random web page\" part of the bot\nfrom the \"pick a random phrase\" part. The \"pick a random phrase\" part\nwill run every time the bot is asked to post something. The \"download\na random web page\" part will only run once a day, because it involves\nmaking a bunch of HTTP requests to random domain names. But once you\nhave a web page downloaded, it's quick and easy to pull a random chunk\nout of it.\n\nThis time let's start with the `bot.yaml` file:\n\n```\nname: \"Web Words\"\nschedule: 60\nstate_update_schedule: 1440\npublish:\n file:\n filename: \"web-words.txt\"\n```\n\nThis bot will post according to its `schedule`, once an hour (60\nminutes). But there's another thing that's going to happen once a day\n(every 1440 minutes): a \"state update\".\n\nHere's the code for Web Words. Put this in\n`bots/web-words/__init__.py`:\n\n```\nimport random\nimport re\nfrom olipy import corpora\nimport requests\nfrom botfriend.bot import TextGeneratorBot\n\nclass WebWords(TextGeneratorBot):\n \"\"\"A bot that pulls random words from a random webpage.\"\"\"\n\n def update_state(self):\n \"\"\"Choose random domain names until we find one that hosts a web page\n larger than ten kilobytes.\n \"\"\"\n new_state = None\n while not new_state:\n\n # Make up a random URL.\n word = random.choice(corpora.words.english_words['words'])\n domain = random.choice([\"com\", \"net\", \"org\"])\n url = \"http://www.%s.%s/\" % (word, domain)\n\n try:\n self.log.info(\"Trying to get new state from %s\" % url)\n response = requests.get(url, timeout=5)\n potential_new_state = response.content\n if len(potential_new_state) < 1024 * 10:\n # This is probably a generic domain parking page.\n self.log.info(\"That was too small, trying again.\")\n continue\n new_state = response.content\n self.log.info(\"Success!\")\n except Exception, e:\n self.log.info(\"That didn't work, trying again.\")\n return new_state\n\n def generate_text(self):\n \"\"\"Choose some words at random from a webpage.\"\"\"\n webpage = self.model.state\n\n # Choose a random point in the web page that's not right at the end.\n total_size = len(webpage)\n near_the_end = int(total_size * 0.9)\n starting_point = random.randint(0, near_the_end)\n\n # Find some stuff in the webpage that looks like words, rather than HTML.\n some_words = re.compile(\"([A-Za-z\\s]{10,})\")\n\n match = some_words.search(webpage[starting_point:])\n if not match:\n # Because we didn't find anything, we're choosing not to post\n # anything right now.\n return None\n data = match.groups()[0].strip()\n return data\n\nBot = WebWords\n```\n\nThe first time you tell Botfriend to post something for this bot,\nBotfriend will call the `update_state()` method. This method may try\nseveral times to find a web page it can use, but it will eventually\nsucceed.\n\n```\n$ botfriend.post web-words\nWeb Words | Trying to get new state from http://www.stenographical.com/\nWeb Words | That was too small, trying again.\nWeb Words | Trying to get new state from http://www.bronchologic.org/\nWeb Words | That didn't work, trying again.\nWeb Words | Trying to get new state from http://www.dentonomy.org/\nWeb Words | That was too small, trying again.\nWeb Words | Trying to get new state from http://www.crummy.com/\nWeb Words | Success!\nWeb Words | file | Published 2019-01-10 01:45 | e experimental group\n```\n\nOnce the state is in place, running `botfriend.post` again won't\ndownload a whole new web page every time. Instead, Web Words will\nchoose another random string from the webpage it's already downloaded.\n\n```\n$ botfriend.post web-words --force\nWeb Words | file | Published 2019-01-10 01:46 an old superstition\n```\n\nThis bot's state expires in one day (this was set in its\n`bot.yaml`). 24 hours after `update_state()` is called for the first\ntime, running `botfriend.post` will cause Botfriend to call that\nmethod again. A brand new web page will be downloaded, and for the\nnext 24 hours all of the Web Words posts will come from that new web page.\n\n## `botfriend.state.show` - Showing the state\n\nThis script simply prints out a bot's current state.\n\n```\n$ botfriend.state.show web-words\n```\n\n## `botfriend.state.refresh` - Refreshing the state\n\nYou can use this script to forcibly refresh a bot's state by calling\n`update_state()`, even if the bot's configured `state_update_schedule`\nsays it's not time to call that method yet.\n\n```\n$ botfriend.state.refresh web-words\n# Web Words | Trying to get new state from http://www.choristoblastoma.org/\n# ...\n```\n\n## `botfriend.state.set` - Setting the state to a specific value\n\nYou can use this script to set a bot's state to a specific value,\nrather than setting the state by calling the `update_state()`\nmethod. Here, instead of telling Web Words to pick random strings from\na web page, we're telling it to pick random strings from its own\nsource code.\n\n```\n$ bin/state.set web-words --file=bots/web-words/__init__.py\n\n$ bin/post web-words --force\nWeb Words | file | Published 2019-01-21 1:56 | olipy import corpora\n```\n\n## `botfriend.state.clear` - Clearing the state\n\nThis script will completely erase a bot's script, making it as though\n`update_state()` has never been called.\n\n```\n$ bin/state.clear web-words\n```\n\n# More examples\n\nThe [the `botfriend` source\nrepository](https://github.com/leonardr/botfriend/) includes complete\nsource code for about ten bots, including the three covered in this\ndocument and several actual bots that I run. To try them out, check\nout the repository and copy the contents of its `bots.sample`\ndirectory into your `bots` directory.\n\n```\n$ git clone git@github.com:leonardr/botfriend.git\n$ cp -r botfriend/bots.sample/* bots\n$ ls bots\n# a-dull-bot boat-names frances-daily postcards\n# ama botfriend.sqlite __init__.py roller-derby\n# anniversary crowd-board-games link-relations serial-entrepreneur\n# best-of-rhp euphemism number-jokes web-words\n```\n\nEvery bot directory contains a `README.md` file explaining how that\nbot works and which special features of Botfriend (if any) it uses.\n\nHere are the example bots:\n\n* [A Dull\nBot](https://github.com/leonardr/botfriend/tree/master/bots.sample/a-dull-bot) - A simple text generation bot.\n* [I Am A\nBot. AMA!](https://github.com/leonardr/botfriend/tree/master/bots.sample/ama) - A bot that keeps complex state for the sake of not repeating a joke.\n* [Anniversary\nMaterials](https://github.com/leonardr/botfriend/tree/master/bots.sample/anniversary) - A text generation bot that uses a lot of different types of input.\n* [Best of\nRHP](https://github.com/leonardr/botfriend/tree/master/bots.sample/best-of-rhp) - A Twitter-specific bot that does nothing but selectively retweet another account.\n* [Boat Names](https://github.com/leonardr/botfriend/tree/master/bots.sample/boat-names) - A simple bot that posts from a backlog. The second example bot described in this document.\n* [Crowd Board\nGames](https://github.com/leonardr/botfriend/tree/master/bots.sample/crowd-board-games) - A bot that parses an RSS feed and creates a post for every entry.\n* [Euphemism\nBot](https://github.com/leonardr/botfriend/tree/master/bots.sample/euphemism) - A text generation bot that builds its posts from a grammar.\n* [Frances Daily](https://github.com/leonardr/botfriend/tree/master/bots.sample/frances-daily) - A bot whose posts are scheduled for specific dates and times, rather than randomly or every-so-often. On some days, there are no posts at all.\n* [Link Relations](https://github.com/leonardr/botfriend/tree/master/bots.sample/link-relations) - A bot that periodically scrapes a web page and posts about anything new it finds.\n* [Number Jokes](https://github.com/leonardr/botfriend/tree/master/bots.sample/number-jokes) - The first example bot described in this help document, a simple text generator bot.\n* [Roy's\nPostcards](https://github.com/leonardr/botfriend/tree/master/bots.sample/postcards) - A bot that posts images as well as text.\n* [Deathbot\n3000](https://github.com/leonardr/botfriend/tree/master/bots.sample/roller-derby) - A backlog-based bot that loads its backlog in a custom format and formats it dynamically, rather than loading in strings and posting the strings.\n* [Serial\nEntrepreneur](https://github.com/leonardr/botfriend/tree/master/bots.sample/serial-entrepreneur) - A complex text generator bot.\n* [Web Words](https://github.com/leonardr/botfriend/tree/master/bots.sample/web-words) - The third example bot described in this help document. A bot that keeps randomly selected web pages as state.\n\n# Posting on a regular basis\n\nOnce you have a few bots, you'll need to run the `botfriend.post` script\nregularly to keep new content flowing. The best way to do this is to\nset up a cron job to schedule the `botfriend.post` script to run every few\nminutes. Don't worry about posting too often. Bots that need to post\nsomething will post when they're ready. Bots that don't need to post\nanything right when `botfriend.post` is run, will be quiet, and bide their time.\n\nHere's what my cron script looks like:\n\n```\n#!/bin/bash\nsource $HOME/scripts/botfriend/env/bin/activate\nbotfriend.post\n```\n\nHere's how I use a cron job to run it every five minutes\n\n```\n*/5 * * * * /home/leonardr/scripts/botfriend/cron 2> /home/leonardr/scripts/botfriend_err\n```\n\nAny errors that happen during the run are appended to a file,\n`botfriend_err`, which I can check periodically.\n\nThat's pretty much it. The rest of this document is just talking about\nsome advanced features of Botfriend, which you probably won't need\nyour first time out.\n\n# Sophisticated configuration\n\nLet's take another look at the `bot.yaml` file for the \"Number Jokes\" bot:\n\n```\nname: Number Jokes\nschedule: 60\npublish:\n file:\n filename: number-jokes.txt\n```\n\nThe `name` option should be self-explanatory -- it's the human-readable name of\nthe bot. Now let's take a detailed look at the other two options.\n\n## `schedule`\n\nThe `schedule` configuration option controls how often your bot should\npost. There are basically three strategies.\n\n1. Set `schedule` to a number of minutes. (This is what Number Jokes\ndoes.) Your bot will post at exact\nintervals, with that number of minutes between posts.\n2. Give `schedule` a `mean` number of minutes. Your bot will post at\nrandomly determined intervals, with _approximately_ that number of\nminutes between posts, but with a fair amount of random variation.\n3. To fine-tune the randomness, you can specify a `stdev` to go along\nwith the mean. This sets the standard deviation used when calculating\nwhen the next post should be. Set it to a low number, and posts will\nnearly `mean` minutes apart. Set it to a high number, and the posting\nschedule will vary widely.\n\nYou can omit `schedule` if your bot schedules all of its posts ahead\nof time (like [Frances\nDaily](https://github.com/leonardr/botfriend/tree/master/bots.sample/frances-daily)\ndoes).\n\n### `state_update_schedule`\n\nThere's a related option, `state_update_schedule`, which you only need\nto set if your bot keeps internal state, like Web Words does. This\noption works the same way as `schedule`, but instead of controlling\nhow often the bot should post, it controls how often your\n`update_state()` method is called.\n\n\n## Other configuration settings\n\nCertain types of bots have other specific configuration settings. A\nsubclass of `RetweetBot`, like [Best of\nRHP](https://github.com/leonardr/botfriend/tree/master/bots.sample/best-of-rhp),\nwill use a special configuration setting called `retweet-user`. This\ncontrols which Twitter account the bot retweets. Your bot can define\nits own custom configuration options--the configuration object is\nparsed as YAML and passed into the `Bot` constructor as the `config`\nargument. You can look in there and pick out whatever information you\nwant.\n\n## Defaults\n\nIf you put a file called `default.yaml` in your Botfriend directory\n(next to `botfriend.sqlite`), all of your bots will inherit the values\nin that file.\n\nAlmost all my bots use the same Mastodon and Twitter client keys (but\ndifferent application keys), and all my Mastodon bots are hosted at\nbotsin.space. I keep these configuration settings in `default.yaml` so\nI don't have to repeat them in every single `bot.yaml` file. My\n`default.yaml` looks like this:\n\n```\npublish:\n mastodon: {api_base_url: 'https://botsin.space/', client_id: a, client_secret: b}\n twitter: {consumer_key: c, consumer_secret: d}\n```\n\nThis way, inside a given `bot.yaml` file, I only have to fill in the\ninformation that's not specified in `default.yaml`:\n\n```\nname: My Bot\npublish:\n mastodon:\n access_token: efg\n twitter:\n access_token: hij\n access_token_secret: klm\nschedule:\n mean: 120\n```\n\n## Programmatic access to an API\n\nSometimes you'll need to use a site's API for more than just posting\nto the site. Every bot has a number of publishers configured through\nits `publish` settings, and the corresponding `Publisher objects are\navailable from inside a `Bot` as `self.publishers`. Once you have a\n`Publisher` object, the raw API client will always be available as\n`Publisher.api`.\n\nSee the [`IAmABot`\nconstructor](https://github.com/leonardr/botfriend/blob/master/bots.sample/ama/__init__.py)\nfor an example. This bot needs a Twitter API client to get its data,\nso it looks through `self.publishers` until it finds the Twitter\npublisher, and grabs its `.api`, storing it for later.\n\n# Conclusion\n\nThere are a lot of features of Botfriend that I've barely touched or\nnot mentioned at all: bots that retweet other Twitter accounts, bots\nthat get their posts by scraping a web page for their content, scripts\nfor republishing posts that weren't posted properly the first time.\n\nBut the features I've covered are the main ones you need to get\nstarted and to see the power of Botfriend. I hope you enjoy it!\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/leonardr/botfriend/", "keywords": "", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "botfriend", "package_url": "https://pypi.org/project/botfriend/", "platform": "", "project_url": "https://pypi.org/project/botfriend/", "project_urls": { "Homepage": "https://github.com/leonardr/botfriend/" }, "release_url": "https://pypi.org/project/botfriend/0.5.0/", "requires_dist": [ "Mastodon.py", "TextBlob", "beautifulsoup4", "feedparser", "nose", "olipy", "pytumblr", "pyyaml", "requests", "sqlalchemy", "tweepy" ], "requires_python": "", "summary": "A server-side framework that makes it easy to manage artistic bots that post to social media.", "version": "0.5.0" }, "last_serial": 4207432, "releases": { "0.5.0": [ { "comment_text": "", "digests": { "md5": "9aadf5c4c25fe4db6468eb2700359f1f", "sha256": "00207ae8f9ee00ff6e6c845fd344e03de7b148678f684872274016999bb813fe" }, "downloads": -1, "filename": "botfriend-0.5.0-py2-none-any.whl", "has_sig": false, "md5_digest": "9aadf5c4c25fe4db6468eb2700359f1f", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 43502, "upload_time": "2018-08-26T01:31:04", "url": "https://files.pythonhosted.org/packages/c8/9b/ce11237fe8d28d44fd4fc30161b702ce12b478b26f63cdcf46141efaf1d5/botfriend-0.5.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1336f4d5606d2c080abdd9ebee3d3d8a", "sha256": "ef8e7a2941e94a173e0dd9893d7e95f8172d18d93600ef97dbb30f88fdb8e838" }, "downloads": -1, "filename": "botfriend-0.5.0.tar.gz", "has_sig": false, "md5_digest": "1336f4d5606d2c080abdd9ebee3d3d8a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 60412, "upload_time": "2018-08-26T01:31:05", "url": "https://files.pythonhosted.org/packages/25/f3/dc4053f08e4f907153c2bad905bb8267ae431b6a359290fee7c5b01d6a2b/botfriend-0.5.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "9aadf5c4c25fe4db6468eb2700359f1f", "sha256": "00207ae8f9ee00ff6e6c845fd344e03de7b148678f684872274016999bb813fe" }, "downloads": -1, "filename": "botfriend-0.5.0-py2-none-any.whl", "has_sig": false, "md5_digest": "9aadf5c4c25fe4db6468eb2700359f1f", "packagetype": "bdist_wheel", "python_version": "py2", "requires_python": null, "size": 43502, "upload_time": "2018-08-26T01:31:04", "url": "https://files.pythonhosted.org/packages/c8/9b/ce11237fe8d28d44fd4fc30161b702ce12b478b26f63cdcf46141efaf1d5/botfriend-0.5.0-py2-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1336f4d5606d2c080abdd9ebee3d3d8a", "sha256": "ef8e7a2941e94a173e0dd9893d7e95f8172d18d93600ef97dbb30f88fdb8e838" }, "downloads": -1, "filename": "botfriend-0.5.0.tar.gz", "has_sig": false, "md5_digest": "1336f4d5606d2c080abdd9ebee3d3d8a", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 60412, "upload_time": "2018-08-26T01:31:05", "url": "https://files.pythonhosted.org/packages/25/f3/dc4053f08e4f907153c2bad905bb8267ae431b6a359290fee7c5b01d6a2b/botfriend-0.5.0.tar.gz" } ] }