Browse Source

adding section 5 and 6, and moving the notebook to content/bot-example

master
manetta 4 years ago
parent
commit
7adb76ec3e
  1. 33
      content/Section 4 - Bot Logic/1-introduction.md
  2. 2
      content/Section 5 - Infrapunctural Imaginaries (exercise)/1-introduction.md
  3. 2
      content/Section 5 - Infrapunctural Imaginaries (exercise)/2-step-1.md
  4. 2
      content/Section 5 - Infrapunctural Imaginaries (exercise)/3-step-2.md
  5. 2
      content/Section 5 - Infrapunctural Imaginaries (exercise)/4-step-3.md
  6. 2
      content/Section 5 - Infrapunctural Imaginaries (exercise)/5-step-4.md
  7. 148
      content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/mastodon-bot-checkpoint.ipynb
  8. 0
      content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/mastodon-bot-post.lurk-checkpoint.ipynb
  9. 249
      content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/twitter-bot-checkpoint.ipynb
  10. 2
      content/Section 6 - Critical Interventions Through Bots (exercise)/1-introduction.md
  11. 2
      content/Section 6 - Critical Interventions Through Bots (exercise)/2-example-bot.md
  12. 3
      content/Section 6 - Critical Interventions Through Bots (exercise)/3-situated-bot-code.md
  13. 137
      content/bot-example/mastodon-bot.ipynb
  14. 2
      pelicanconf.py

33
content/Section 4 - Bot Logic/1-introduction.md

@ -0,0 +1,33 @@
Title: Introduction: Bot Logic
Slug: 01-s4-introduction
Date: 2020-11-01 12:00
Summary:
## Bots as infrapunctures
Infrapuncture is a comely term at a moment in time when there is a lot of discussion around the political roles of automated agents in digital infrastructures. Before we move any further, perhaps a short definition of bots is necessary. When we say bots, we refer to code that automatises certain behaviours and which often acts as an interface between platform and human users.
Many online communities engage with bots, for example the editor community of English Wikipedia, which consists of both humans and bots. The interactions between them go beyond the maintenance of Wikipedia. Instead, affective relations are formed wherein the bots are anthropomorphised. So writing a bot implies not only to understand the API (Application Programming Inferface) of the platform, what determines the possibilities of interaction, but also the social norms established within the community of editors and users of Wikipedia.
And that's of course just one example. Bots act differently depending on the platform on which they are running.
## Bot logic
What kind of puncturing logics might bots enable in digital platforms?
Bot logic is phrased as a response to platform logic, which Jonas Andersson Schwarz describes as "digital platforms enacting a twofold logic of micro-level technocentric control and macro-level geopolitical domination, while at the same time having a range of generative outcomes, arising between these two levels".
'Bot logic' refers to the situational effect of bots upon a socio-technical ecology and their potential to infiltrate and co-exist with server-side conditions. Perhaps we can move our attention to these few points. When referring to platform logic in the points below, we refer to commercial infrastructures, not federated and free software platforms such as those present in the Fediverse, which have a different kind of dynamics[^theses].
* Where platform logic accumulates, bot logic disperses
* On commercial platforms, the engagement of users equals economic value that is translated through data capture and organisation. Metadata is extracted from users that then through pattern matching can be used to target users for advertisements. While bots can and do participate in this economy, they can also enable its sabotage. In the case of buying bot followers, this can be a means to generate noise in the collected dataset and blur the perception of the user as a set of behaviours that the platform has.
* Where platform logic centralises, bot logic fragments
* Platforms such as Twitter or Facebook use a centralised system, in the sense that the servers on which our information is stored are owned by their company. Bots, on the other hand, do not require a lot of computational power in order to run. They can be simply run from the computers of the persons who wrote the code themselves. In fact, bots really point to the materiality of the structures on which they run, as researcher Stuart Geiger also points out when he talks about 'bespoke code' as code that extends or transforms the operations of software platforms, but "runs on top of or alongside existing systems instead of being more directly integrated into and run on software-side codebases".
* Where platform logic creates distance between user and infrastructure, bot logic develops an intimate knowledge of the platform
* If we consider means of communication as means of production (Williams, 2005), there is a certain alienation that happens on commercial centralised platforms, where the user has no stake in the development of the material conditions of the platform on which they communicate. From this point of view, the making of bots implies a closeness to the platform that is indicated through the understanding of both the sociological and technical systems that determine the usership of a platform. In order to code a bot, you need to know what kind of actions are allowed and how the bot would be received by the community.
* Where platform logic reinforces habitual behaviour, bot logic encourages new habit formation
* If we think about a commercial platform as a structure or surface on which actions can take place, these actions are often predefined by the affordances of the platform. However, as was mentioned in the beginning, bots are the automation of certain actions and behaviours. To be able to define these behaviours as a user can mean an alteration of the socialities embedded in a platform.
All of these points were written with commercial platforms in mind, however, exciting developments are happening in federated platforms such as Mastodon, where users are part of defining features and possibilities of interaction. There, the norms of the platform and the way they are codified into the technical structure are more often revised and reformulated together with the user base. This in itself creates a different space for bots, which are still active contributors in the way sociality is imagined on these platforms. However, on platforms like Mastodon, bots don't only comply to the terms of services of the API but also to the code of conduct, for example.

2
content/Section 5 - Infrapunctural Imaginaries (exercise)/1-introduction.md

@ -1,5 +1,5 @@
Title: Infrapunctural Imaginaries (exercise)
Slug: 01-s4-introduction
Slug: 01-s5-introduction
Date: 2020-11-01 12:00
Summary: While keeping in mind that different groups use different tools all differently :-), it is important to take some time to focus on a specific context, to zoom in and work with a specific context in mind. What digital tool does a group use to communicate with each other? What are the norms of the conversations and who decides what is normal?

2
content/Section 5 - Infrapunctural Imaginaries (exercise)/2-step-1.md

@ -1,5 +1,5 @@
Title: Step 1: Group formation
Slug: 02-s4-step-1
Slug: 02-s5-step-1
Date: 2020-11-01 12:01
Summary: Start of the excercise.

2
content/Section 5 - Infrapunctural Imaginaries (exercise)/3-step-2.md

@ -1,5 +1,5 @@
Title: Step 2: Norms
Slug: 03-s4-step-2
Slug: 03-s5-step-2
Date: 2020-11-01 12:02
Summary: Continuation of the excercise.

2
content/Section 5 - Infrapunctural Imaginaries (exercise)/4-step-3.md

@ -1,5 +1,5 @@
Title: Step 3: Infrapunctures
Slug: 04-s4-step-3
Slug: 04-s5-step-3
Date: 2020-11-01 12:03
Summary: Continuation of the excercise.

2
content/Section 5 - Infrapunctural Imaginaries (exercise)/5-step-4.md

@ -1,5 +1,5 @@
Title: Step 4: Dialogue Writing
Slug: 05-s4-step-4
Slug: 05-s5-step-4
Date: 2020-11-01 12:04
Summary: Last step of the excercise.

148
content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/mastodon-bot-checkpoint.ipynb

@ -0,0 +1,148 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Defaulting to user installation because normal site-packages is not writeable\n",
"Requirement already satisfied: Mastodon.py in /home/mb/.local/lib/python3.7/site-packages (1.5.1)\n",
"Requirement already satisfied: python-magic in /usr/local/lib/python3.7/dist-packages (from Mastodon.py) (0.4.15)\n",
"Requirement already satisfied: pytz in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (2020.1)\n",
"Requirement already satisfied: python-dateutil in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (2.8.1)\n",
"Requirement already satisfied: blurhash>=1.1.4 in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (1.1.4)\n",
"Requirement already satisfied: requests>=2.4.2 in /usr/lib/python3/dist-packages (from Mastodon.py) (2.21.0)\n",
"Requirement already satisfied: decorator>=4.0.0 in /usr/lib/python3/dist-packages (from Mastodon.py) (4.3.0)\n",
"Requirement already satisfied: six in /usr/lib/python3/dist-packages (from Mastodon.py) (1.12.0)\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install Mastodon.py"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from mastodon import Mastodon"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Register your app! This only needs to be done once. Uncomment the code and substitute in your information.\n",
"\n",
"# Mastodon.create_app(\n",
"# 'bagofwords',\n",
"# api_base_url = 'https://botsin.space',\n",
"# to_file = 'mastodon-bot.secret'\n",
"#)\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'9goIgIuH7OOdJJP5CzBgNQ0s8_ZoGuK1_8hA1h3dTxk'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Then login. This can be done every time, or use persisted.\n",
"\n",
"mastodon = Mastodon(\n",
" client_id = 'mastodon-bot.secret',\n",
" api_base_url = 'https://botsin.space'\n",
")\n",
"\n",
"mastodon.log_in(\n",
" 'mail@manettaberends.nl',\n",
" 'language',\n",
" to_file = 'mastodon-bot-usercred.secret'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"ename": "MastodonAPIError",
"evalue": "('Mastodon API returned error', 403, 'Forbidden', 'Your login is missing a confirmed e-mail address')",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mMastodonAPIError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-8-4d950a9b40ce>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mapi_base_url\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'https://botsin.space'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m )\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mmastodon\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtoot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'Testing to toot for the very very first time, excitingly using #mastodonpy !'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<decorator-gen-186>\u001b[0m in \u001b[0;36mtoot\u001b[0;34m(self, status)\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.7/site-packages/mastodon/Mastodon.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(function, self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mmajor\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_major\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mminor\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_minor\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mpatch\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_patch\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 101\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mMastodonVersionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Version check failed (Need version \"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mversion\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\", patch is \"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_patch\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\")\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 102\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 103\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"\\n\\n *Added: Mastodon v\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mcreated_ver\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\", last changed: Mastodon v\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mlast_changed_ver\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"*\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 104\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdecorate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunction\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwrapper\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.7/site-packages/mastodon/Mastodon.py\u001b[0m in \u001b[0;36mtoot\u001b[0;34m(self, status)\u001b[0m\n\u001b[1;32m 1785\u001b[0m \u001b[0mReturns\u001b[0m \u001b[0ma\u001b[0m\u001b[0;31m \u001b[0m\u001b[0;31m`\u001b[0m\u001b[0mtoot\u001b[0m \u001b[0mdict\u001b[0m\u001b[0;31m`\u001b[0m\u001b[0m_\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mnew\u001b[0m \u001b[0mstatus\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1786\u001b[0m \"\"\"\n\u001b[0;32m-> 1787\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstatus_post\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstatus\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1788\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1789\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mapi_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"1.0.0\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2.8.0\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__DICT_VERSION_STATUS\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m<decorator-gen-185>\u001b[0m in \u001b[0;36mstatus_post\u001b[0;34m(self, status, in_reply_to_id, media_ids, sensitive, visibility, spoiler_text, language, idempotency_key, content_type, scheduled_at, poll, quote_id)\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.7/site-packages/mastodon/Mastodon.py\u001b[0m in \u001b[0;36mwrapper\u001b[0;34m(function, self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0mmajor\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_major\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mminor\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_minor\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mpatch\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_patch\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 101\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mMastodonVersionError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Version check failed (Need version \"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mversion\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\", patch is \"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mstr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmastodon_patch\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\")\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 102\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mself\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0margs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 103\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfunction\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__doc__\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"\\n\\n *Added: Mastodon v\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mcreated_ver\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\", last changed: Mastodon v\"\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mlast_changed_ver\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m\"*\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 104\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mdecorate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfunction\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwrapper\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.7/site-packages/mastodon/Mastodon.py\u001b[0m in \u001b[0;36mstatus_post\u001b[0;34m(self, status, in_reply_to_id, media_ids, sensitive, visibility, spoiler_text, language, idempotency_key, content_type, scheduled_at, poll, quote_id)\u001b[0m\n\u001b[1;32m 1774\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1775\u001b[0m \u001b[0mparams\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__generate_params\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mparams_initial\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;34m'idempotency_key'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 1776\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__api_request\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'POST'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'/api/v1/statuses'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mparams\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mheaders\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mheaders\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0muse_json\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0muse_json\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 1777\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 1778\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mapi_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"1.0.0\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2.8.0\"\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m__DICT_VERSION_STATUS\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;32m~/.local/lib/python3.7/site-packages/mastodon/Mastodon.py\u001b[0m in \u001b[0;36m__api_request\u001b[0;34m(self, method, endpoint, params, files, headers, access_token_override, base_url_override, do_ratelimiting, use_json, parse)\u001b[0m\n\u001b[1;32m 3427\u001b[0m \u001b[0mresponse_object\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstatus_code\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3428\u001b[0m \u001b[0mresponse_object\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mreason\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m-> 3429\u001b[0;31m error_msg)\n\u001b[0m\u001b[1;32m 3430\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3431\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mparse\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mMastodonAPIError\u001b[0m: ('Mastodon API returned error', 403, 'Forbidden', 'Your login is missing a confirmed e-mail address')"
]
}
],
"source": [
"# To post, create an actual API instance.\n",
"\n",
"mastodon = Mastodon(\n",
" access_token = 'mastodon-bot-usercred.secret',\n",
" api_base_url = 'https://botsin.space'\n",
")\n",
"\n",
"mastodon.toot('Testing to toot for the very very first time, excitingly using #mastodonpy !')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

0
content/Section 6 - Critical Interventions Through Bots (exercise)/mastodon-bot.ipynb → content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/mastodon-bot-post.lurk-checkpoint.ipynb

249
content/Section 6 - Critical Interventions Through Bots (exercise)/.ipynb_checkpoints/twitter-bot-checkpoint.ipynb

@ -0,0 +1,249 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Making a Twitter Bot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This Jupyter Notebooks shows the basics of making a Twitter bot with Python.\n",
"\n",
"This is a *very* heavily modified version of a [tutorial posted on the CUNY Academic Commons Blog](https://emerging.commons.gc.cuny.edu/2013/10/making-twitter-bot-python-tutorial/) by [Robin Camilla Davis](https://twitter.com/robincamille). I have taken some of the content and structure of the static blog post and ported it into a Jupyter Notebook Python environment. \n",
"\n",
"Here is Robin's introduction:\n",
"\n",
"> If there’s one thing this budding computational linguist finds delightful, it’s computers that talk to us. From [SmarterChild](http://en.wikipedia.org/wiki/SmarterChild) to [horse_ebooks](http://www.robincamille.com/2013-09-25-horse_ebooks/) to [Beetlejuice](http://muffinlabs.com/content/twitter-bot-info.html), I love the weirdness of machines that seem to have a voice, especially when it’s a Twitter bot that adds its murmur to a tweetstream of accounts mostly run by other humans.\n",
"\n",
"> As fun midnight project a few weeks ago, I cobbled together [@MechanicalPoe](https://twitter.com/MechanicalPoe), a Twitter bot that tweets Poe works line by line on the hour from a long .txt file. This slow-tweeting of text is by no means new—[@SlowDante](https://twitter.com/slowdante) is pretty popular, and so is[@CDarwin](http://www.metaburbia.com/darwin/), among many others. In case you want to make your own, here are the quick ‘n’ easy steps I took. This is just one way of doing it—shop around and see what others have done, too.\n",
"\n",
"\n",
"## Contents\n",
"\n",
"* [Steps Towards an Ecology of Bots](#Steps-Towards-an-Ecology-of-Bots)\n",
"* [Building a Basic Bot](#Building-a-Basic-Bot)\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Steps Towards an Ecology of Bots"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Creating a Twitter bot means creating a Twitter account AND a Twitter app. The account will belong to the bot and the app will be the mechanism you use to access the Twitter API and control the bot **WITH PYTHON.**\n",
"\n",
"Creating a Twitter bot is somewhat complicated because of quirks in the way Twitter manages verification. If you don’t already have a Twitter account, the process of creating a Twitter bot is pretty painless. If you already have a Twitter account, you have to do the *Twitter verification dance*. Managing more than one Twitter account is a huge *pain in the ass* because you need a unique email address and mobile phone number for each account. \n",
"\n",
"**Step 0)** *Note: This step is only for people who already have Twitter accounts.* Log into Twitter and visit [your account's mobile devices settings page](https://twitter.com/settings/devices). If you have a phone number associated with that account click “Delete my phone” to remove your mobile from number from this account. You are going to need that phone number for creating your bot account. Alternatively, you can get a free phone number from [Google Voice](http://voice.google.com) and use that for all the mobile phone verification BS.\n",
"\n",
"You are also going to need another email address for your new Twitter account. Twitter won’t let you create multiple accounts with the same email address. If you have a 2nd email address you’ll need to use it in step 1 below. If you don’t have another email address it is easy to [create one with gmail](https://accounts.google.com/signup). Alternatively, you can fake a new unique email address with Gmail by adding a `+` and something after your username. For example: `myemail+sometext@gmail.com`. Any messages addressed to this email will still show up in your inbox, but it behaves like a different email address. Give it a try!\n",
"\n",
"**Step 1)** Create a new Twitter account by visiting the [Twitter signup page](https://twitter.com/signup). This will be the Twitter account for your bot, so pick your username wisely. You will need to pick a name that hasn’t already been selected. Twitter *should* ask you for a mobile phone verification when you are creating the account, if it doesn’t you’ll have to add the phone number to your profile in [the new account's mobile devices settings page](https://twitter.com/settings/devices).\n",
"\n",
"**Step 2)** OK. Now that you have a Twitter **account** for your bot, you need to create a Twitter **application** so you can access the Twitter API **WITH PYTHON**. Twitter doesn’t allow people to connect to their API willy-nilly. Creating applications is Twitter's way of tracking (and controlling) who is programmatically accessing their network and data. In the world of big social media sites, heavily regulated access to APIs, networks, and data is a fact of life. \n",
"\n",
"Fortunately, creating an application on Twitter is very easy. Visit Twitter’s [Application Management page](https://apps.twitter.com/) and click the “Create New App” button. You’ll be shown the form shown below. Pick a name for your application and a description, this information is just for you it should be named something different (and more descriptive) than the account you just created. For the website, put the name of the course `http://annettevee.com/2015fall_computationalmedia/` into the website field.\n",
"\n",
"![New App information](images/create-application.png)\n",
"\n",
"**Step 3)** The last step for this part of the Twitter bot making process is to create the Access Token so your Python code can access the API. With your app created, you should see a dashboard page will all kinds of technical jargon about your Twitter app. You can ignore most of it for now and instead focus on the tab that says “Keys and Access Tokens.” The keys and access tokens behave like passwords so your computer can talk to Twitter’s computer. Twitter uses a protocol called [OAuth](http://oauth.net) for [“providing authorized access to its API](https://dev.twitter.com/oauth). You don’t really need to wor\n",
"\n",
"- Click the \"Keys and Access Tokens” tab\n",
"- Click the “Create my access token button” at the bottom of the page.\n",
"\n",
"Once you have created your access tokens there will be **four** pieces of information you will need to put into your Python script so it can access the Twitter API:\n",
"\n",
"1. Consumer Key\n",
"2. Consumer Secret\n",
"3. Access Token\n",
"4. Access Token Secret\n",
"\n",
"![Keys and Access Tokens Dashboard](images/access-keys.png)\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"Some other things to consider:\n",
"\n",
"- When you create a Twitter App you agree to the [developer agreement and policy](https://dev.twitter.com/overview/terms/agreement-and-policy) which has some complete bullshit within it. Basically, Twitter reserves the right to lock down your application and your account if their machine learning algorithms detect what they think is bad behavior.\n",
"- Creating to many Accounts can lock-out your mobile phone number and prevent you from creating new accounts or apps. This comes from **personal experience** and there is NO INDICATION if and when that happens. AND Twitter support is TOTALLY WORTHLESS. This is when creating another cell phone number with Google Voice is helpful.\n",
"- You are not alone in thinking this is a HUGE PAIN IN THE ASS.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Building a Basic Bot"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If you skim the code below, you'll notice *there isn't a lot of code* that is because a bulk of the work is being done by a Python library called [Tweepy](http://tweepy.readthedocs.org/en/latest/). Tweepy handles all the annoying plumbing work involved with connecting to Twitter and accessing specific functions like authentication, search or updating your status. You could access the Twitter API directly by issuing raw HTTP request to Twitters [REST endpoints](https://dev.twitter.com/rest/public), but why do that when someone has already a nice and easy to use interface to Twitter?\n",
"\n",
"The code below is for a very simple Twitter bot that just Tweets lines of Edgar Allen Poe's poetry. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Boilerplate"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Import the python libraries we need to \n",
"import time\n",
"import tweepy\n",
"\n",
"\n",
"# Be sure to paste your keys and tokens in here because it won't work otherwise!\n",
"CONSUMER_KEY = \"INSERT KEY HERE\"\n",
"CONSUMER_SECRET = \"INSERT SECRET HERE\"\n",
"ACCESS_KEY = \"INSERT ACCESS KEY\"\n",
"ACCESS_SECRET = \"INSERT SECRET HERE\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### The Oauth Dance"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This is the code the initates a connection to Twitter via the API using the Keys and Access Tokens we created earlier. Once we've gotten through the \"Oauth Dance\" we'll have an object, `the_twitter_api` that we can use to programmatically perform actions on Twitter. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# Send our keys and tokens to Twitter\n",
"credentials = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)\n",
"credentials.set_access_token(ACCESS_KEY, ACCESS_SECRET)\n",
"\n",
"# Authenticate with Twitter to get access\n",
"the_twitter_api = tweepy.API(credentials)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Tweeting"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We are going to make a bot that tweets lines from classic texts. \n",
"\n",
"> I chose some texts from [Project Gutenberg](http://www.gutenberg.org/) and copied them into separate .txt files. (Maybe don’t choose a long-winded writer.) I ran a script over them to split them up by sentence and mark sentences longer than 140 characters. ([Link to chunking script.](https://github.com/robincamille/kicks-and-giggles/blob/master/poe-twitterbot-chunker.py)) There are other scripts to break up long sentences intelligently, but I wanted to exert some editorial control over where the splits occurred in the texts, so the script I wrote writes ‘SPLIT’ next to long sentences to alert me as I went over the ~600 lines by hand. I copied my chunked texts into one .txt file and marked the beginnings and ends of each individual text. ([Link to the finalized .txt file.](https://github.com/robincamille/kicks-and-giggles/blob/master/poe-lines.txt))\n",
"\n",
"So I am going to cheat a little bit here and just use the data files Robin already prepared. I downloaded Robin's pre-prepared text into a file in the `data/` directory.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"# open Robin's Edgar Allen Poe data file and read every line into memory\n",
"with open('data/thats-so-raven.txt','r') as filename:\n",
" lines = filename.readlines()\n",
"\n",
"# Tweet each line, then wait one minute and tweet another.\n",
"# Note: this design means the bot runs continuously\n",
"for line in lines: \n",
" the_twitter_api.update_status(status=line)\n",
" print(line)\n",
" time.sleep(60) # Sleep for 1 minute"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That's it you're done! That's all it takes to create a very simple Twitter bot."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next Steps"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here are some other bot examples to play with:\n",
"* Copora Tutorial Bot - A bot that uses [Corpora](https://github.com/dariusk/corpora) to generate text to tweet. [TODO] \n",
"* Mayor of Pittsburgh Bot - A bot that translates the Mayor's tweets into Pittsburghese. [TODO]\n",
"\n",
"To make your own bot, you can start with this [python code for a very minimal bot](minimal-bot.ipynb) and modify it to cause whatever havoc or happiness you'd like.\n",
"\n",
"\n",
"### Deploying you Botnet\n",
"\n",
"Making a bot is the easy part, making it run all the time requires a server that can continually run the bots. We have been creating and modifying these bots in Jupyter Notebooks which are fun and easy to use, but not great for deploying bots in the wild.\n",
"\n",
"If you have a server, the best way to deploy a bot you've written in a notebook is to save the Notebook as a python script (File -> Download As -> Python (.py) and run the scrip or set up a [CRON JOB](http://www.unixgeeks.org/security/newbie/unix/cron-1.html) to execute the python script periodically (if you've written the script to just execute one and quit rather than run continuously)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.4.2"
}
},
"nbformat": 4,
"nbformat_minor": 0
}

2
content/Section 6 - Critical Interventions Through Bots (exercise)/1-introduction.md

@ -1,5 +1,5 @@
Title: Critical Interventions through Bots (exercise)
Slug: 01-s5-introduction
Slug: 01-s6-introduction
Date: 2020-11-01 12:00
Summary: Start of the excercise.

2
content/Section 6 - Critical Interventions Through Bots (exercise)/2-example-bot.md

@ -1,5 +1,5 @@
Title: Bot example
Slug: 02-s5-bot-example
Slug: 02-s6-bot-example
Date: 2020-11-01 12:01
Summary: Continuation of the excercise.

3
content/Section 6 - Critical Interventions Through Bots (exercise)/3-situated-bot-code.md

@ -1,5 +1,5 @@
Title: Situated code
Slug: 03-s5-situated-code
Slug: 03-s6-situated-code
Date: 2020-11-01 12:02
Summary: Last step of the excercise.
@ -14,3 +14,4 @@ Describe the characters that take part of the conversation in one line.
Start writing a dialogue script.
You can use the following document to depart from: [insert link to document here].

137
content/bot-example/mastodon-bot.ipynb

@ -0,0 +1,137 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Mastodon-bot Example"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Defaulting to user installation because normal site-packages is not writeable\n",
"Requirement already satisfied: Mastodon.py in /home/mb/.local/lib/python3.7/site-packages (1.5.1)\n",
"Requirement already satisfied: six in /usr/lib/python3/dist-packages (from Mastodon.py) (1.12.0)\n",
"Requirement already satisfied: requests>=2.4.2 in /usr/lib/python3/dist-packages (from Mastodon.py) (2.21.0)\n",
"Requirement already satisfied: python-dateutil in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (2.8.1)\n",
"Requirement already satisfied: blurhash>=1.1.4 in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (1.1.4)\n",
"Requirement already satisfied: python-magic in /usr/local/lib/python3.7/dist-packages (from Mastodon.py) (0.4.15)\n",
"Requirement already satisfied: decorator>=4.0.0 in /usr/lib/python3/dist-packages (from Mastodon.py) (4.3.0)\n",
"Requirement already satisfied: pytz in /home/mb/.local/lib/python3.7/site-packages (from Mastodon.py) (2020.1)\n"
]
}
],
"source": [
"import sys\n",
"!{sys.executable} -m pip install Mastodon.py"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from mastodon import Mastodon\n",
"# https://github.com/halcy/Mastodon.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"instance = 'INSERTYOURINSTANCEURLHERE'\n",
"username = 'INSERTEMAILADDRESSHERE'\n",
"password = 'INSERTPASSWORDSHERE'"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Register your app! This only needs to be done once. Uncomment the code and substitute in your information.\n",
"\n",
"Mastodon.create_app(\n",
" 'bots-as-infrapuncture',\n",
" api_base_url = instance,\n",
" to_file = 'mastodon-bot.secret'\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Then login. This can be done every time, or use persisted.\n",
"\n",
"mastodon = Mastodon(\n",
" client_id = 'mastodon-bot.secret',\n",
" api_base_url = instance\n",
")\n",
"\n",
"mastodon.log_in(\n",
" username,\n",
" password,\n",
" to_file = 'mastodon-bot-usercred.secret'\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# To post, create an actual API instance.\n",
"\n",
"mastodon = Mastodon(\n",
" access_token = 'mastodon-bot-usercred.secret',\n",
" api_base_url = instance\n",
")\n",
"\n",
"mastodon.toot('Testing to toot for the very very first time, excitingly using #mastodonpy !')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

2
pelicanconf.py

@ -21,7 +21,7 @@ TRANSLATION_FEED_ATOM = None
AUTHOR_FEED_ATOM = None
AUTHOR_FEED_RSS = None
STATIC_PATHS = [ 'extra/favicon.ico', 'images', 'attachments' ]
STATIC_PATHS = [ 'extra/favicon.ico', 'images', 'attachments', 'bot-example' ]
THEME = 'themes/default'
THEME_STATIC_DIR = 'theme'

Loading…
Cancel
Save