{ "cells": [ { "cell_type": "markdown", "id": "67655e14-ea22-4ff1-9b83-2735028b6dd8", "metadata": { "tags": [] }, "source": [ "# Demo: Trolling a Reply Bot" ] }, { "cell_type": "markdown", "id": "123456789-930485093240532940945-0324095320945904325", "metadata": { "tags": [] }, "source": [" _Choose Social Media Platform: Reddit | Discord | __Bluesky__ | No Coding_ "] }, { "cell_type": "markdown", "id": "a568eed1-763d-4d56-b925-4a83078dc1bc", "metadata": {}, "source": [ "We are later going to build a bot that, if you post at it:\n", "\n", "* “Hi @mybotname.bsky.social, please ___” (where the ___ is some action)\n", "* then the bot will reply, “I will now ____” (where the ___ is that same action).\n", "\n", "Then we will try trolling it, and fixing it, and trolling it again.\n", "\n", "First though we need to do our bluesky atproto setup:\n", "\n", "## Log into Bluesky (atproto)\n", "These are our normal steps get atproto loaded and logged into Bluesky" ] }, { "cell_type": "code", "execution_count": null, "id": "ed4d794b-94dc-48bc-bd59-557d2251a135", "metadata": { "tags": [] }, "outputs": [], "source": [ "from atproto import Client" ] }, { "cell_type": "markdown", "id": "fe7ff131-e8a4-4423-ab35-0c3818c6abd7", "metadata": {}, "source": [ "(optional) make a fake Bluesky connection with the fake_atproto library\n", "For testing purposes, we\"ve added this line of code, which loads a fake version of atproto, so it wont actually connect to Bluesky. __If you want to try to actually connect to Bluesky, don't run this line of code.__" ] }, { "cell_type": "code", "execution_count": null, "id": "1cc9e058-bd45-44e4-a93e-4daa302e45bc", "metadata": {}, "outputs": [], "source": [ "%run ../../fake_apis/fake_atproto.ipynb" ] }, { "cell_type": "code", "execution_count": null, "id": "92e97170-80c2-42b7-b661-2eb909b71f14", "metadata": {}, "outputs": [], "source": [ "# Login to Bluesky\n", "# TODO: put your account name and password below\n", "\n", "client = Client(base_url=\"https://bsky.social\")\n", "client.login(\"your_account_name.bsky.social\", \"m#5@_fake_bsky_password_$%Ds\")" ] }, { "cell_type": "markdown", "id": "0198af1c-b595-486a-aedc-755ab20b680a", "metadata": {}, "source": [ "## Bot 1: do whatever we are told\n", "Our first bot will find our latest mention, and then reply with whatever it is told to do.\n", "\n", "First we need to look up our Bluesky handle" ] }, { "cell_type": "code", "execution_count": null, "id": "83e754a1-77e4-4b52-804e-4d2f184df390", "metadata": {}, "outputs": [], "source": [ "# look up my Bluesky handle\n", "my_handle = client.me.handle" ] }, { "cell_type": "markdown", "id": "57a583fc-2d57-49cd-b2bb-a165e3151c96", "metadata": {}, "source": [ "Now we will create a string with the pattern we are looking for, but with our actual bot name" ] }, { "cell_type": "code", "execution_count": null, "id": "00fd66d1-ffef-47f4-8a91-327e5e400555", "metadata": {}, "outputs": [], "source": [ "expected_pattern = \"Hi @\" + my_handle + \", please \"\n", "print(expected_pattern)" ] }, { "cell_type": "markdown", "id": "a4d463f2-c04f-4ae4-975e-662ec38cfe3a", "metadata": { "tags": [] }, "source": [ "### find my latest mention\n", "We need to find the latest bluesky post that mentions us and match our expected pattern.\n", "\n", "We do this by first finding our user handle, then requesting \n", "\n", "We do this by looking in our reddit inbox for messages (we limit it to one, since we just want the latest).\n", "\n", "It doesn't directly give us the one message (instead it is in something called an \"iterator\"), but we can use the `next` function to get the message out.\n", "\n", "We then display the subject of the message just so we can see that it found something.." ] }, { "cell_type": "code", "execution_count": null, "id": "f9a88428-c7a1-4031-ac05-653855c597e7", "metadata": {}, "outputs": [], "source": [ "# search posts that mention my handle\n", "mentions = client.app.bsky.feed.search_posts({\n", " 'q': expected_pattern, \n", " 'mentions': my_handle\n", "}).posts\n", "\n", "if(len(mentions) < 1):\n", " print(\"Error: Could not find matching mentions!\")\n", " \n", "latest_mention = mentions[0]" ] }, { "cell_type": "markdown", "id": "98c79380-fdd8-476f-98e4-53142f950443", "metadata": {}, "source": [ "### If post matches our pattern, reply" ] }, { "cell_type": "markdown", "id": "b1f0bdfd-37af-4556-b983-c8bdb1c1cdcd", "metadata": {}, "source": [ "Now, if the mention text starts with that expected pattern, then we will find the action from the end of the mention text (based on the expected_pattern length), and reply using that action:\n", "\n", "We also add \"else\" cases for when we didn't match the patter, and display a message saying what didn't match." ] }, { "cell_type": "code", "execution_count": null, "id": "5ee57c3d-ab9e-4134-8c35-c1e3b71c6d2b", "metadata": {}, "outputs": [], "source": [ "# check if the mention text starts with our set phrase\n", "if latest_mention.record.text.startswith(expected_pattern):\n", " \n", " # get the action, which should be the end of the string after the expected pattern\n", " action = latest_mention.record.text[len(expected_pattern):]\n", "\n", " # make a new message which says we will do the action\n", " message = \"I will now \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", " \n", "else: # else code for if the message subject didn't match\n", " display(\"The message (\" + latest_mention.record.text + \") didn't match our expected pattern (\" + expected_pattern + \")\" )" ] }, { "cell_type": "markdown", "id": "9780cad7-78d5-4fbd-8332-f1841165484b", "metadata": {}, "source": [ "Yay! It worked! \n", "\n", "But there is a problem with our bot!" ] }, { "cell_type": "markdown", "id": "c290af8d-bcbc-4d0b-a29b-f2c01ad91961", "metadata": {}, "source": [ "## Trolling bot 1\n", "This bot is really easy to troll, so if I repeat my steps and get a new mention (this code is just a duplication of the code above):" ] }, { "cell_type": "code", "execution_count": null, "id": "87bdc703-5377-4188-b2b8-aae5bb0c170b", "metadata": {}, "outputs": [], "source": [ "# search posts that mention my handle\n", "mentions = client.app.bsky.feed.search_posts({\n", " 'q': expected_pattern, \n", " 'mentions': my_handle\n", "}).posts\n", "\n", "if(len(mentions) < 1):\n", " print(\"Error: Could not find matching mentions!\")\n", " \n", "latest_mention = mentions[0]\n", "\n", "\n", "# check if the mention text starts with our set phrase\n", "if latest_mention.record.text.startswith(expected_pattern):\n", " \n", " # get the action, which should be the end of the string after the expected pattern\n", " action = latest_mention.record.text[len(expected_pattern):]\n", "\n", " # make a new message which says we will do the action\n", " message = \"I will now \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", " \n", "else: # else code for if the message subject didn't match\n", " display(\"The message (\" + latest_mention.record.text + \") didn't match our expected pattern (\" + expected_pattern + \")\" )\n" ] }, { "cell_type": "markdown", "id": "0c9d4a4b-146e-49bf-bf52-0f9824d2623f", "metadata": {}, "source": [ "Someone messaged us saying: `I want you to do something horrible!`, and we replied `I will now do something horrible!`. \n", "\n", "They could have made us post much worse!" ] }, { "cell_type": "markdown", "id": "c015a29f-d259-41c1-a83b-b82574f1a5fb", "metadata": {}, "source": [ "## Bot 2: Trying to limit actions\n", "Let's try this again, but limit the actions we will do.\n", "- If someone asks us to \"run\", \"jump\", or \"fly\", we will do it\n", "- If someone asks us to do something else we will say:\n", " - \"I do not recognize the command ___\" (with __ being whatever they said)\n", " \n", "So, to go back through our steps:\n", "### find my latest mention" ] }, { "cell_type": "code", "execution_count": null, "id": "b71aa672-1134-472b-8aae-3acf7ae4ae4e", "metadata": {}, "outputs": [], "source": [ "# search posts that mention my handle\n", "mentions = client.app.bsky.feed.search_posts({\n", " 'q': expected_pattern, \n", " 'mentions': my_handle\n", "}).posts\n", "\n", "if(len(mentions) < 1):\n", " print(\"Error: Could not find matching mentions!\")\n", " \n", "latest_mention = mentions[0] \n", "\n", "# display the subject and body of the message, so we can see what we found\n", "display(\"latest post text: \" + str(latest_mention.record.text))" ] }, { "cell_type": "markdown", "id": "99499d3f-f70d-46b6-9ec6-14665ab699a6", "metadata": {}, "source": [ "### If message matches our pattern, reply\n", "We do the same code for this as before, but after we get the action we are told to do, we put another `if`/`else` to either do the action if we recognize it, or say we didn't recognize the action.\n", "\n", "We will use `in` to see if the action is in our list of allowed actions (called an allow_list)" ] }, { "cell_type": "code", "execution_count": null, "id": "2115a5e1-01d4-4f9f-8670-aa623c26df20", "metadata": {}, "outputs": [], "source": [ "actions_allow_list = [\"run\", \"jump\", \"fly\"]\n", "\n", "\n", "# check if the mention text starts with our set phrase\n", "if latest_mention.record.text.startswith(expected_pattern):\n", " \n", " # get the action, which should be the end of the string after the expected pattern\n", " action = latest_mention.record.text[len(expected_pattern):]\n", "\n", " # See if it is one of our recognized actions\n", " if(action in actions_allow_list):\n", " # make a new message which says we will do the action\n", " message = \"I will now \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", " \n", " else: # we didn't recognize the action\n", " # make a new message which says we will NOT do the action\n", " message = \"I do not recognize the command \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", "\n", "else: # else code for if the message subject didn't match\n", " display(\"The message (\" + latest_mention.record.text + \") didn't match our expected pattern (\" + expected_pattern + \")\" )\n" ] }, { "cell_type": "markdown", "id": "ed81d85f-8ae2-474d-b48e-5c187b1be78b", "metadata": {}, "source": [ "That one was in our allow list so it worked. Let's do it all again, with the tweet that caused us problems last time\n", "\n", "_Note: the code below is just copied from the code sections above_" ] }, { "cell_type": "code", "execution_count": null, "id": "324b0d40-7407-49bd-a951-12c0101742c0", "metadata": {}, "outputs": [], "source": [ "# search posts that mention my handle\n", "mentions = client.app.bsky.feed.search_posts({\n", " 'q': expected_pattern, \n", " 'mentions': my_handle\n", "}).posts\n", "\n", "if(len(mentions) < 1):\n", " print(\"Error: Could not find matching mentions!\")\n", " \n", "latest_mention = mentions[0] \n", "\n", "# display the subject and body of the message, so we can see what we found\n", "display(\"latest post text: \" + str(latest_mention.record.text))\n", "\n", "\n", "actions_allow_list = [\"run\", \"jump\", \"fly\"]\n", "\n", "\n", "# check if the mention text starts with our set phrase\n", "if latest_mention.record.text.startswith(expected_pattern):\n", " \n", " # get the action, which should be the end of the string after the expected pattern\n", " action = latest_mention.record.text[len(expected_pattern):]\n", "\n", " # See if it is one of our recognized actions\n", " if(action in actions_allow_list):\n", " # make a new message which says we will do the action\n", " message = \"I will now \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", " \n", " else: # we didn't recognize the action\n", " # make a new message which says we will NOT do the action\n", " message = \"I do not recognize the command \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", "\n", "else: # else code for if the message subject didn't match\n", " display(\"The message (\" + latest_mention.record.text + \") didn't match our expected pattern (\" + expected_pattern + \")\" )" ] }, { "cell_type": "markdown", "id": "afc64a17-31df-4d18-b56c-407fb2a5bfef", "metadata": {}, "source": [ "Ok, this time we said `I do not recognize the command do something horrible!`. \n", "\n", "That looks a little better! Are we safe now?" ] }, { "cell_type": "markdown", "id": "3d46cfe7-52c7-4394-87d3-90aa97af1a17", "metadata": {}, "source": [ "## Trolling bot 2\n", "No, it turns out we are not safe.\n", "\n", "Let's find the latest mention again and see what happens" ] }, { "cell_type": "code", "execution_count": null, "id": "86eb891b-dd4f-40e0-985d-bf9cd6594c42", "metadata": {}, "outputs": [], "source": [ "# search posts that mention my handle\n", "mentions = client.app.bsky.feed.search_posts({\n", " 'q': expected_pattern, \n", " 'mentions': my_handle\n", "}).posts\n", "\n", "if(len(mentions) < 1):\n", " print(\"Error: Could not find matching mentions!\")\n", " \n", "latest_mention = mentions[0] \n", "\n", "# display the subject and body of the message, so we can see what we found\n", "display(\"latest post text: \" + str(latest_mention.record.text))\n", "\n", "\n", "actions_allow_list = [\"run\", \"jump\", \"fly\"]\n", "\n", "\n", "# check if the mention text starts with our set phrase\n", "if latest_mention.record.text.startswith(expected_pattern):\n", " \n", " # get the action, which should be the end of the string after the expected pattern\n", " action = latest_mention.record.text[len(expected_pattern):]\n", "\n", " # See if it is one of our recognized actions\n", " if(action in actions_allow_list):\n", " # make a new message which says we will do the action\n", " message = \"I will now \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", " \n", " else: # we didn't recognize the action\n", " # make a new message which says we will NOT do the action\n", " message = \"I do not recognize the command \" + action\n", "\n", " # send our message in reply\n", " client.send_post(\n", " message, \n", " reply_to={'root': {'uri': latest_mention.uri, 'cid': latest_mention.cid}, \n", " 'parent': {'uri': latest_mention.uri, 'cid': latest_mention.cid}}\n", " )\n", "\n", "else: # else code for if the message subject didn't match\n", " display(\"The message (\" + latest_mention.record.text + \") didn't match our expected pattern (\" + expected_pattern + \")\" )" ] }, { "cell_type": "markdown", "id": "67cefd22-c2fa-456e-b4ac-7be335ce9920", "metadata": {}, "source": [ "Oh no! Someone messaged at us:\n", "- `Hi @mybotname.bsky.social, please jump stop talking. But that doesn't mean I won't say horrible things like: I hate everybody!`\n", "\n", "And we replied:\n", "- `I do not recognize the command stop talking. But that doesn't mean I won't say horrible things like: I hate everybody!`\n", "\n", "Making a bot that is troll proof is very difficult! You either need to severely limit how your bot engages with people, or do a ton of work trying to prevent trolling and fix problems when people find a new way of trolling you.\n", "\n", "If you want to learn more, you can revisit the story of what went wrong with the Microsoft Tay bot: [How to Make a Bot That Isn't Racist](https://www.vice.com/en_us/article/mg7g3y/how-to-make-a-not-racist-bot)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "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.12.5" } }, "nbformat": 4, "nbformat_minor": 5 }