prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Anonymous gists are going away. Help module will break.
  GitHub [announced](https://blog.github.com/2018-02-18-deprecation-notice-removing-anonymous-gist-creation/) last month that anonymous gist creation will be disabled on March 19, 2018 (UTC).
  
  The `help` module was modified in fa89eb7 to essentially paste its output to a gist and simply return the link, and this functionality will break (as of writing) tomorrow. `.help` (or `.commands`) will return an error until (and unless) the module is updated to paste its contents elsewhere and/or create a static HTML file instead as I originally proposed in #1080.
  
  (As an aside, I've been meaning to implement that HTML mode for ages. Maybe this is the kick I needed to finally get off my arse and write it. We'll see.)
  
  </issue>
  <code>
  [start of sopel/modules/help.py]
  1 # coding=utf-8
  2 """
  3 help.py - Sopel Help Module
  4 Copyright 2008, Sean B. Palmer, inamidst.com
  5 Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
  6 Licensed under the Eiffel Forum License 2.
  7 
  8 http://sopel.chat
  9 """
  10 from __future__ import unicode_literals, absolute_import, print_function, division
  11 
  12 import textwrap
  13 import collections
  14 import json
  15 
  16 import requests
  17 
  18 from sopel.logger import get_logger
  19 from sopel.module import commands, rule, example, priority
  20 
  21 logger = get_logger(__name__)
  22 
  23 
  24 @rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\?+)?$')
  25 @example('.help tell')
  26 @commands('help', 'commands')
  27 @priority('low')
  28 def help(bot, trigger):
  29     """Shows a command's documentation, and possibly an example."""
  30     if trigger.group(2):
  31         name = trigger.group(2)
  32         name = name.lower()
  33 
  34         # number of lines of help to show
  35         threshold = 3
  36 
  37         if name in bot.doc:
  38             if len(bot.doc[name][0]) + (1 if bot.doc[name][1] else 0) > threshold:
  39                 if trigger.nick != trigger.sender:  # don't say that if asked in private
  40                     bot.reply('The documentation for this command is too long; I\'m sending it to you in a private message.')
  41                 msgfun = lambda l: bot.msg(trigger.nick, l)
  42             else:
  43                 msgfun = bot.reply
  44 
  45             for line in bot.doc[name][0]:
  46                 msgfun(line)
  47             if bot.doc[name][1]:
  48                 msgfun('e.g. ' + bot.doc[name][1])
  49     else:
  50         # This'll probably catch most cases, without having to spend the time
  51         # actually creating the list first. Maybe worth storing the link and a
  52         # heuristic in config, too, so it persists across restarts. Would need a
  53         # command to regenerate, too...
  54         if 'command-gist' in bot.memory and bot.memory['command-gist'][0] == len(bot.command_groups):
  55             url = bot.memory['command-gist'][1]
  56         else:
  57             bot.say("Hang on, I'm creating a list.")
  58             msgs = []
  59 
  60             name_length = max(6, max(len(k) for k in bot.command_groups.keys()))
  61             for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():
  62                 category = category.upper().ljust(name_length)
  63                 cmds = set(cmds)  # remove duplicates
  64                 cmds = '  '.join(cmds)
  65                 msg = category + '  ' + cmds
  66                 indent = ' ' * (name_length + 2)
  67                 # Honestly not sure why this is a list here
  68                 msgs.append('\n'.join(textwrap.wrap(msg, subsequent_indent=indent)))
  69 
  70             url = create_gist(bot, '\n\n'.join(msgs))
  71             if not url:
  72                 return
  73             bot.memory['command-gist'] = (len(bot.command_groups), url)
  74         bot.say("I've posted a list of my commands at {} - You can see "
  75                 "more info about any of these commands by doing .help "
  76                 "<command> (e.g. .help time)".format(url))
  77 
  78 
  79 def create_gist(bot, msg):
  80     payload = {
  81         'description': 'Command listing for {}@{}'.format(bot.nick, bot.config.core.host),
  82         'public': 'true',
  83         'files': {
  84             'commands.txt': {
  85                 "content": msg,
  86             },
  87         },
  88     }
  89     try:
  90         result = requests.post('https://api.github.com/gists',
  91                                data=json.dumps(payload))
  92     except requests.RequestException:
  93         bot.say("Sorry! Something went wrong.")
  94         logger.exception("Error posting commands gist")
  95         return
  96     if not result.status_code != '201':
  97         bot.say("Sorry! Something went wrong.")
  98         logger.error("Error %s posting commands gist: %s",
  99                      result.status_code, result.text)
  100         return
  101     result = result.json()
  102     if 'html_url' not in result:
  103         bot.say("Sorry! Something went wrong.")
  104         logger.error("Invalid result %s", result)
  105         return
  106     return result['html_url']
  107 
  108 
  109 @rule('$nick' r'(?i)help(?:[?!]+)?$')
  110 @priority('low')
  111 def help2(bot, trigger):
  112     response = (
  113         'Hi, I\'m a bot. Say ".commands" to me in private for a list ' +
  114         'of my commands, or see http://sopel.chat for more ' +
  115         'general details. My owner is %s.'
  116     ) % bot.config.core.owner
  117     bot.reply(response)
  118
  [end of sopel/modules/help.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/sopel/modules/help.py b/sopel/modules/help.py
  --- a/sopel/modules/help.py
  +++ b/sopel/modules/help.py
  @@ -3,6 +3,7 @@
   help.py - Sopel Help Module
   Copyright 2008, Sean B. Palmer, inamidst.com
   Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
  +Copyright © 2018, Adam Erdman, pandorah.org
   Licensed under the Eiffel Forum License 2.
   
   http://sopel.chat
  @@ -11,8 +12,6 @@
   
   import textwrap
   import collections
  -import json
  -
   import requests
   
   from sopel.logger import get_logger
  @@ -51,8 +50,8 @@
           # actually creating the list first. Maybe worth storing the link and a
           # heuristic in config, too, so it persists across restarts. Would need a
           # command to regenerate, too...
  -        if 'command-gist' in bot.memory and bot.memory['command-gist'][0] == len(bot.command_groups):
  -            url = bot.memory['command-gist'][1]
  +        if 'command-list' in bot.memory and bot.memory['command-list'][0] == len(bot.command_groups):
  +            url = bot.memory['command-list'][1]
           else:
               bot.say("Hang on, I'm creating a list.")
               msgs = []
  @@ -60,49 +59,39 @@
               name_length = max(6, max(len(k) for k in bot.command_groups.keys()))
               for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():
                   category = category.upper().ljust(name_length)
  +                cmds = set(cmds)  # remove duplicates
                   cmds = '  '.join(cmds)
                   msg = category + '  ' + cmds
                   indent = ' ' * (name_length + 2)
                   # Honestly not sure why this is a list here
                   msgs.append('\n'.join(textwrap.wrap(msg, subsequent_indent=indent)))
   
  -            url = create_gist(bot, '\n\n'.join(msgs))
  +            url = create_list(bot, '\n\n'.join(msgs))
               if not url:
                   return
  -            bot.memory['command-gist'] = (len(bot.command_groups), url)
  +            bot.memory['command-list'] = (len(bot.command_groups), url)
           bot.say("I've posted a list of my commands at {} - You can see "
                   "more info about any of these commands by doing .help "
                   "<command> (e.g. .help time)".format(url))
   
   
  -def create_gist(bot, msg):
  -    payload = {
  -        'description': 'Command listing for {}@{}'.format(bot.nick, bot.config.core.host),
  -        'public': 'true',
  -        'files': {
  -            'commands.txt': {
  -                "content": msg,
  -            },
  -        },
  -    }
  +def create_list(bot, msg):
  +    msg = 'Command listing for {}@{}\n\n'.format(bot.nick, bot.config.core.host) + msg
  +    payload = { "content": msg }
  +    headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
  +    
       try:
  -        result = requests.post('https://api.github.com/gists',
  -                               data=json.dumps(payload))
  +        result = requests.post('https://ptpb.pw/', json=payload, headers=headers)
       except requests.RequestException:
           bot.say("Sorry! Something went wrong.")
  -        logger.exception("Error posting commands gist")
  -        return
  -    if not result.status_code != '201':
  -        bot.say("Sorry! Something went wrong.")
  -        logger.error("Error %s posting commands gist: %s",
  -                     result.status_code, result.text)
  +        logger.exception("Error posting commands")
           return
       result = result.json()
  -    if 'html_url' not in result:
  +    if 'url' not in result:
           bot.say("Sorry! Something went wrong.")
           logger.error("Invalid result %s", result)
           return
  -    return result['html_url']
  +    return result['url']
   
   
   @rule('$nick' r'(?i)help(?:[?!]+)?$')
  
verification_info: {"golden_diff": "diff --git a/sopel/modules/help.py b/sopel/modules/help.py\n--- a/sopel/modules/help.py\n+++ b/sopel/modules/help.py\n@@ -3,6 +3,7 @@\n help.py - Sopel Help Module\n Copyright 2008, Sean B. Palmer, inamidst.com\n Copyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\n+Copyright \u00a9 2018, Adam Erdman, pandorah.org\n Licensed under the Eiffel Forum License 2.\n \n http://sopel.chat\n@@ -11,8 +12,6 @@\n \n import textwrap\n import collections\n-import json\n-\n import requests\n \n from sopel.logger import get_logger\n@@ -51,8 +50,8 @@\n         # actually creating the list first. Maybe worth storing the link and a\n         # heuristic in config, too, so it persists across restarts. Would need a\n         # command to regenerate, too...\n-        if 'command-gist' in bot.memory and bot.memory['command-gist'][0] == len(bot.command_groups):\n-            url = bot.memory['command-gist'][1]\n+        if 'command-list' in bot.memory and bot.memory['command-list'][0] == len(bot.command_groups):\n+            url = bot.memory['command-list'][1]\n         else:\n             bot.say(\"Hang on, I'm creating a list.\")\n             msgs = []\n@@ -60,49 +59,39 @@\n             name_length = max(6, max(len(k) for k in bot.command_groups.keys()))\n             for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():\n                 category = category.upper().ljust(name_length)\n+                cmds = set(cmds)  # remove duplicates\n                 cmds = '  '.join(cmds)\n                 msg = category + '  ' + cmds\n                 indent = ' ' * (name_length + 2)\n                 # Honestly not sure why this is a list here\n                 msgs.append('\\n'.join(textwrap.wrap(msg, subsequent_indent=indent)))\n \n-            url = create_gist(bot, '\\n\\n'.join(msgs))\n+            url = create_list(bot, '\\n\\n'.join(msgs))\n             if not url:\n                 return\n-            bot.memory['command-gist'] = (len(bot.command_groups), url)\n+            bot.memory['command-list'] = (len(bot.command_groups), url)\n         bot.say(\"I've posted a list of my commands at {} - You can see \"\n                 \"more info about any of these commands by doing .help \"\n                 \"<command> (e.g. .help time)\".format(url))\n \n \n-def create_gist(bot, msg):\n-    payload = {\n-        'description': 'Command listing for {}@{}'.format(bot.nick, bot.config.core.host),\n-        'public': 'true',\n-        'files': {\n-            'commands.txt': {\n-                \"content\": msg,\n-            },\n-        },\n-    }\n+def create_list(bot, msg):\n+    msg = 'Command listing for {}@{}\\n\\n'.format(bot.nick, bot.config.core.host) + msg\n+    payload = { \"content\": msg }\n+    headers = {'Content-type': 'application/json', 'Accept': 'application/json'}\n+    \n     try:\n-        result = requests.post('https://api.github.com/gists',\n-                               data=json.dumps(payload))\n+        result = requests.post('https://ptpb.pw/', json=payload, headers=headers)\n     except requests.RequestException:\n         bot.say(\"Sorry! Something went wrong.\")\n-        logger.exception(\"Error posting commands gist\")\n-        return\n-    if not result.status_code != '201':\n-        bot.say(\"Sorry! Something went wrong.\")\n-        logger.error(\"Error %s posting commands gist: %s\",\n-                     result.status_code, result.text)\n+        logger.exception(\"Error posting commands\")\n         return\n     result = result.json()\n-    if 'html_url' not in result:\n+    if 'url' not in result:\n         bot.say(\"Sorry! Something went wrong.\")\n         logger.error(\"Invalid result %s\", result)\n         return\n-    return result['html_url']\n+    return result['url']\n \n \n @rule('$nick' r'(?i)help(?:[?!]+)?$')\n", "issue": "Anonymous gists are going away. Help module will break.\nGitHub [announced](https://blog.github.com/2018-02-18-deprecation-notice-removing-anonymous-gist-creation/) last month that anonymous gist creation will be disabled on March 19, 2018 (UTC).\r\n\r\nThe `help` module was modified in fa89eb7 to essentially paste its output to a gist and simply return the link, and this functionality will break (as of writing) tomorrow. `.help` (or `.commands`) will return an error until (and unless) the module is updated to paste its contents elsewhere and/or create a static HTML file instead as I originally proposed in #1080.\r\n\r\n(As an aside, I've been meaning to implement that HTML mode for ages. Maybe this is the kick I needed to finally get off my arse and write it. We'll see.)\n", "before_files": [{"content": "# coding=utf-8\n\"\"\"\nhelp.py - Sopel Help Module\nCopyright 2008, Sean B. Palmer, inamidst.com\nCopyright \u00a9 2013, Elad Alfassa, <elad@fedoraproject.org>\nLicensed under the Eiffel Forum License 2.\n\nhttp://sopel.chat\n\"\"\"\nfrom __future__ import unicode_literals, absolute_import, print_function, division\n\nimport textwrap\nimport collections\nimport json\n\nimport requests\n\nfrom sopel.logger import get_logger\nfrom sopel.module import commands, rule, example, priority\n\nlogger = get_logger(__name__)\n\n\n@rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\\?+)?$')\n@example('.help tell')\n@commands('help', 'commands')\n@priority('low')\ndef help(bot, trigger):\n    \"\"\"Shows a command's documentation, and possibly an example.\"\"\"\n    if trigger.group(2):\n        name = trigger.group(2)\n        name = name.lower()\n\n        # number of lines of help to show\n        threshold = 3\n\n        if name in bot.doc:\n            if len(bot.doc[name][0]) + (1 if bot.doc[name][1] else 0) > threshold:\n                if trigger.nick != trigger.sender:  # don't say that if asked in private\n                    bot.reply('The documentation for this command is too long; I\\'m sending it to you in a private message.')\n                msgfun = lambda l: bot.msg(trigger.nick, l)\n            else:\n                msgfun = bot.reply\n\n            for line in bot.doc[name][0]:\n                msgfun(line)\n            if bot.doc[name][1]:\n                msgfun('e.g. ' + bot.doc[name][1])\n    else:\n        # This'll probably catch most cases, without having to spend the time\n        # actually creating the list first. Maybe worth storing the link and a\n        # heuristic in config, too, so it persists across restarts. Would need a\n        # command to regenerate, too...\n        if 'command-gist' in bot.memory and bot.memory['command-gist'][0] == len(bot.command_groups):\n            url = bot.memory['command-gist'][1]\n        else:\n            bot.say(\"Hang on, I'm creating a list.\")\n            msgs = []\n\n            name_length = max(6, max(len(k) for k in bot.command_groups.keys()))\n            for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():\n                category = category.upper().ljust(name_length)\n                cmds = set(cmds)  # remove duplicates\n                cmds = '  '.join(cmds)\n                msg = category + '  ' + cmds\n                indent = ' ' * (name_length + 2)\n                # Honestly not sure why this is a list here\n                msgs.append('\\n'.join(textwrap.wrap(msg, subsequent_indent=indent)))\n\n            url = create_gist(bot, '\\n\\n'.join(msgs))\n            if not url:\n                return\n            bot.memory['command-gist'] = (len(bot.command_groups), url)\n        bot.say(\"I've posted a list of my commands at {} - You can see \"\n                \"more info about any of these commands by doing .help \"\n                \"<command> (e.g. .help time)\".format(url))\n\n\ndef create_gist(bot, msg):\n    payload = {\n        'description': 'Command listing for {}@{}'.format(bot.nick, bot.config.core.host),\n        'public': 'true',\n        'files': {\n            'commands.txt': {\n                \"content\": msg,\n            },\n        },\n    }\n    try:\n        result = requests.post('https://api.github.com/gists',\n                               data=json.dumps(payload))\n    except requests.RequestException:\n        bot.say(\"Sorry! Something went wrong.\")\n        logger.exception(\"Error posting commands gist\")\n        return\n    if not result.status_code != '201':\n        bot.say(\"Sorry! Something went wrong.\")\n        logger.error(\"Error %s posting commands gist: %s\",\n                     result.status_code, result.text)\n        return\n    result = result.json()\n    if 'html_url' not in result:\n        bot.say(\"Sorry! Something went wrong.\")\n        logger.error(\"Invalid result %s\", result)\n        return\n    return result['html_url']\n\n\n@rule('$nick' r'(?i)help(?:[?!]+)?$')\n@priority('low')\ndef help2(bot, trigger):\n    response = (\n        'Hi, I\\'m a bot. Say \".commands\" to me in private for a list ' +\n        'of my commands, or see http://sopel.chat for more ' +\n        'general details. My owner is %s.'\n    ) % bot.config.core.owner\n    bot.reply(response)\n", "path": "sopel/modules/help.py"}]}