# Buggy code
def url_handler(bot, trigger):
    """Checks for malicious URLs"""
    check = True  # Enable URL checking
    strict = False  # Strict mode: kick on malicious URL
    positives = 0  # Number of engines saying it's malicious
    total = 0  # Number of total engines
    use_vt = True  # Use VirusTotal
    check = bot.config.safety.enabled_by_default
    if check is None:
        # If not set, assume default
        check = True
    # DB overrides config:
    setting = bot.db.get_channel_value(trigger.sender, "safety")
    if setting is not None:
        if setting == "off":
            return  # Not checking
        elif setting in ["on", "strict", "local", "local strict"]:
            check = True
        if setting == "strict" or setting == "local strict":
            strict = True
        if setting == "local" or setting == "local strict":
            use_vt = False

    if not check:
        return  # Not overridden by DB, configured default off

    try:
        netloc = urlparse(trigger.group(1)).netloc
    except ValueError:
        return  # Invalid IPv6 URL

    if any(regex.search(netloc) for regex in known_good):
        return  # Whitelisted

    apikey = bot.config.safety.vt_api_key
    try:
        if apikey is not None and use_vt:
            payload = {"resource": unicode(trigger), "apikey": apikey, "scan": "1"}

            if trigger not in bot.memory["safety_cache"]:
                r = requests.post(vt_base_api_url + "report", data=payload)
                r.raise_for_status()
                result = r.json()
                fetched = time.time()
                if all(k in result for k in ["positives", "total"]):
                    # cache result only if it contains a scan report
                    # TODO: handle checking back for results from queued scans
                    data = {
                        "positives": result["positives"],
                        "total": result["total"],
                        "fetched": fetched,
                    }
                    bot.memory["safety_cache"][trigger] = data
                    if len(bot.memory["safety_cache"]) >= (2 * cache_limit):
                        _clean_cache(bot)
            else:
                LOGGER.debug("using cache")
                result = bot.memory["safety_cache"][trigger]
            positives = result.get("positives", 0)
            total = result.get("total", 0)
    except requests.exceptions.RequestException:
        # Ignoring exceptions with VT so MalwareDomains will always work
        LOGGER.debug("[VirusTotal] Error obtaining response.", exc_info=True)
    except InvalidJSONResponse:
        # Ignoring exceptions with VT so MalwareDomains will always work
        LOGGER.debug("[VirusTotal] Malformed response (invalid JSON).", exc_info=True)

    if unicode(netloc).lower() in malware_domains:
        # malwaredomains is more trustworthy than some VT engines
        # therefore it gets a weight of 10 engines when calculating confidence
        positives += 10
        total += 10

    if positives > 1:
        # Possibly malicious URL detected!
        confidence = "{}%".format(round((positives / total) * 100))
        msg = "link posted by %s is possibly malicious " % formatting.bold(trigger.nick)
        msg += "(confidence %s - %s/%s)" % (confidence, positives, total)
        warning = formatting.bold(formatting.color("WARNING:", "red"))
        bot.say(warning + " " + msg)
        if strict:
            bot.kick(trigger.nick, trigger.sender, "Posted a malicious link")


# Traceback (urllib.error.HTTPError)
[2020-12-22 13:35:56,136] sopel.modules.safety INFO     - Downloading malwaredomains db from https://mirror1.malwaredomains.com/files/justdomains
[2020-12-22 13:35:56,332] sopel.bot            ERROR    - Error in safety setup: HTTP Error 404: Not Found
Traceback (most recent call last):
File "/mnt/c/Users/dgw/github/sopel/sopel/bot.py", line 310, in setup_plugins
plugin.setup(self)
File "/mnt/c/Users/dgw/github/sopel/sopel/plugins/handlers.py", line 265, in setup
self._module.setup(bot)
File "/mnt/c/Users/dgw/github/sopel/sopel/modules/safety.py", line 98, in setup
_download_malwaredomains_db(loc)
File "/mnt/c/Users/dgw/github/sopel/sopel/modules/safety.py", line 116, in _download_malwaredomains_db
urlretrieve(url, path)
File "/usr/lib/python3.6/urllib/request.py", line 248, in urlretrieve
with contextlib.closing(urlopen(url, data)) as fp:
File "/usr/lib/python3.6/urllib/request.py", line 223, in urlopen
return opener.open(url, data, timeout)
File "/usr/lib/python3.6/urllib/request.py", line 532, in open
response = meth(req, response)
File "/usr/lib/python3.6/urllib/request.py", line 642, in http_response
'http', request, response, code, msg, hdrs)
File "/usr/lib/python3.6/urllib/request.py", line 570, in error
return self._call_chain(*args)
File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
result = func(*args)
File "/usr/lib/python3.6/urllib/request.py", line 650, in http_error_default
raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 404: Not Found

# Fixed code
def url_handler(bot, trigger):
    """Checks for malicious URLs"""
    check = True  # Enable URL checking
    strict = False  # Strict mode: kick on malicious URL
    positives = 0  # Number of engines saying it's malicious
    total = 0  # Number of total engines
    use_vt = True  # Use VirusTotal
    check = bot.config.safety.enabled_by_default
    if check is None:
        # If not set, assume default
        check = True
    # DB overrides config:
    setting = bot.db.get_channel_value(trigger.sender, "safety")
    if setting is not None:
        if setting == "off":
            return  # Not checking
        elif setting in ["on", "strict", "local", "local strict"]:
            check = True
        if setting == "strict" or setting == "local strict":
            strict = True
        if setting == "local" or setting == "local strict":
            use_vt = False

    if not check:
        return  # Not overridden by DB, configured default off

    try:
        netloc = urlparse(trigger.group(1)).netloc
    except ValueError:
        return  # Invalid IPv6 URL

    if any(regex.search(netloc) for regex in known_good):
        return  # Whitelisted

    apikey = bot.config.safety.vt_api_key
    try:
        if apikey is not None and use_vt:
            payload = {"resource": unicode(trigger), "apikey": apikey, "scan": "1"}

            if trigger not in bot.memory["safety_cache"]:
                r = requests.post(vt_base_api_url + "report", data=payload)
                r.raise_for_status()
                result = r.json()
                fetched = time.time()
                if all(k in result for k in ["positives", "total"]):
                    # cache result only if it contains a scan report
                    # TODO: handle checking back for results from queued scans
                    data = {
                        "positives": result["positives"],
                        "total": result["total"],
                        "fetched": fetched,
                    }
                    bot.memory["safety_cache"][trigger] = data
                    if len(bot.memory["safety_cache"]) >= (2 * cache_limit):
                        _clean_cache(bot)
            else:
                LOGGER.debug("using cache")
                result = bot.memory["safety_cache"][trigger]
            positives = result.get("positives", 0)
            total = result.get("total", 0)
    except requests.exceptions.RequestException:
        # Ignoring exceptions with VT so domain list will always work
        LOGGER.debug("[VirusTotal] Error obtaining response.", exc_info=True)
    except InvalidJSONResponse:
        # Ignoring exceptions with VT so domain list will always work
        LOGGER.debug("[VirusTotal] Malformed response (invalid JSON).", exc_info=True)

    if unicode(netloc).lower() in malware_domains:
        positives += 1
        total += 1

    if positives >= 1:
        # Possibly malicious URL detected!
        confidence = "{}%".format(round((positives / total) * 100))
        msg = "link posted by %s is possibly malicious " % formatting.bold(trigger.nick)
        msg += "(confidence %s - %s/%s)" % (confidence, positives, total)
        warning = formatting.bold(formatting.color("WARNING:", "red"))
        bot.say(warning + " " + msg)
        if strict:
            bot.kick(trigger.nick, trigger.sender, "Posted a malicious link")