Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: ThatSINEWAVE/LinkWarden-Bot
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: Release9
Choose a base ref
...
head repository: ThatSINEWAVE/LinkWarden-Bot
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Feb 27, 2024

  1. Update README.md

    ThatSINEWAVE authored Feb 27, 2024

    Verified

    This commit was signed with the committer’s verified signature.
    larsbeck Lars Beckmann
    Copy the full SHA
    9536ce0 View commit details
  2. Updated seen_links.json

    ThatSINEWAVE authored Feb 27, 2024
    Copy the full SHA
    2bce109 View commit details
  3. Updated main.py

    ThatSINEWAVE authored Feb 27, 2024
    Copy the full SHA
    32693e9 View commit details
  4. Updated main.py

    ThatSINEWAVE authored Feb 27, 2024
    Copy the full SHA
    c0c9950 View commit details
  5. Update README.md

    ThatSINEWAVE authored Feb 27, 2024
    Copy the full SHA
    4b04eab View commit details

Commits on Feb 28, 2024

  1. Updated utils.py

    ThatSINEWAVE authored Feb 28, 2024
    Copy the full SHA
    1371ad0 View commit details
  2. Updated main.py

    ThatSINEWAVE authored Feb 28, 2024
    Copy the full SHA
    82cf442 View commit details
  3. Updated commands.py

    ThatSINEWAVE authored Feb 28, 2024
    Copy the full SHA
    609770f View commit details
  4. Updated README.md

    ThatSINEWAVE authored Feb 28, 2024
    Copy the full SHA
    2fc89d0 View commit details

Commits on Mar 1, 2024

  1. Updated README.md

    ThatSINEWAVE authored Mar 1, 2024
    Copy the full SHA
    cfab19a View commit details
  2. Copy the full SHA
    b4a65b0 View commit details
  3. Copy the full SHA
    bb1dd70 View commit details
  4. Updated README.md

    ThatSINEWAVE authored Mar 1, 2024
    Copy the full SHA
    739c389 View commit details

Commits on Mar 3, 2024

  1. Created FUNDING.yml

    ThatSINEWAVE authored Mar 3, 2024
    Copy the full SHA
    40dc00c View commit details

Commits on Mar 15, 2024

  1. Update README.md

    ThatSINEWAVE authored Mar 15, 2024
    Copy the full SHA
    59c7f85 View commit details
  2. Update FUNDING.yml

    ThatSINEWAVE authored Mar 15, 2024
    Copy the full SHA
    6cb5cfc View commit details

Commits on Apr 6, 2024

  1. Updated README.md

    ThatSINEWAVE authored Apr 6, 2024
    Copy the full SHA
    a3c3517 View commit details

Commits on Apr 10, 2024

  1. Copy the full SHA
    dfc43ee View commit details
  2. Updated .md files

    Updated CODE_OF_CONDUCT.md
    Added CONTRIBUTING.md
    Added SECURITY.md
    ThatSINEWAVE committed Apr 10, 2024
    Copy the full SHA
    83c0737 View commit details
  3. Added new placeholder .md files (WILL UPDATE LATER)

    Added CHANGELOG.md file
    Added TODO.md file
    These placeholder files will be updated later with actual information
    ThatSINEWAVE committed Apr 10, 2024
    Copy the full SHA
    b8b45bd View commit details

Commits on Apr 17, 2024

  1. Delete CHANGELOG.md

    ThatSINEWAVE committed Apr 17, 2024
    Copy the full SHA
    84b0b8d View commit details
  2. Delete TODO.md

    ThatSINEWAVE committed Apr 17, 2024
    Copy the full SHA
    b469f4d View commit details

Commits on Apr 23, 2024

  1. Added Discord invite

    ThatSINEWAVE committed Apr 23, 2024
    Copy the full SHA
    b0efaf3 View commit details

Commits on May 2, 2024

  1. Create .gitignore

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    c926c4f View commit details
  2. Update .gitignore

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    9b86326 View commit details
  3. Update requirements.txt

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    1f1c391 View commit details
  4. Updated README.md

    ThatSINEWAVE authored May 2, 2024
    Copy the full SHA
    0491d86 View commit details
  5. Update seen_links.json

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    c98b214 View commit details
  6. Copy the full SHA
    c7b7b42 View commit details
  7. Copy the full SHA
    0134dd6 View commit details
  8. Update config.py

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    a0fa423 View commit details
  9. Update config.py

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    b236a5f View commit details
  10. Copy the full SHA
    3b59edb View commit details
  11. Fixed a few typos

    ThatSINEWAVE committed May 2, 2024
    Copy the full SHA
    1c0fc62 View commit details
  12. Copy the full SHA
    537d55c View commit details
  13. Copy the full SHA
    d9d2d44 View commit details
  14. Copy the full SHA
    fafbc0c View commit details

Commits on May 9, 2024

  1. Bump requests from 2.26.0 to 2.31.0

    Bumps [requests](https://github.com/psf/requests) from 2.26.0 to 2.31.0.
    - [Release notes](https://github.com/psf/requests/releases)
    - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
    - [Commits](psf/requests@v2.26.0...v2.31.0)
    
    ---
    updated-dependencies:
    - dependency-name: requests
      dependency-type: direct:production
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored May 9, 2024
    Copy the full SHA
    9489d09 View commit details
  2. Merge pull request #2 from ThatSINEWAVE/dependabot/pip/requests-2.31.0

    Bump requests from 2.26.0 to 2.31.0
    ThatSINEWAVE authored May 9, 2024
    Copy the full SHA
    4cd0417 View commit details

Commits on May 21, 2024

  1. ---

    updated-dependencies:
    - dependency-name: requests
      dependency-type: direct:production
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    dependabot[bot] authored May 21, 2024
    Copy the full SHA
    55d8421 View commit details
  2. Merge pull request #4 from ThatSINEWAVE/dependabot/pip/requests-2.32.0

    Bump requests from 2.31.0 to 2.32.0
    ThatSINEWAVE authored May 21, 2024
    Copy the full SHA
    a7c1faa View commit details
Showing with 472 additions and 86 deletions.
  1. +2 −0 .github/FUNDING.yml
  2. +3 −0 .gitignore
  3. +119 −0 CODE_OF_CONDUCT.md
  4. +109 −0 CONTRIBUTING.md
  5. +26 −7 README.md
  6. +19 −0 SECURITY.md
  7. +68 −3 commands.py
  8. +2 −0 config.py
  9. +115 −64 main.py
  10. +2 −3 requirements.txt
  11. +2 −4 seen_links.json
  12. +5 −5 utils.py
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github: ThatSINEWAVE
ko_fi: thatsinewave
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

/__pycache__
/.idea
119 changes: 119 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<div align="center">

# Code of Conduct - LinkWarden-Bot

</div>

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to make participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
advances
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate,
threatening, offensive, or harmful.

Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will
communicate reasons for moderation decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at <EMAIL_COMING_SOON>.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org/), version
[1.4](https://www.contributor-covenant.org/version/1/4/code-of-conduct/code_of_conduct.md) and
[2.0](https://www.contributor-covenant.org/version/2/0/code_of_conduct/code_of_conduct.md)
109 changes: 109 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<div align="center">

# Contributing to LinkWarden-Bot

</div>

First off, thanks for taking the time to contribute! ❤️

All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉

> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
## Table of Contents

- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
- [Your First Code Contribution](#your-first-code-contribution)
- [Improving The Documentation](#improving-the-documentation)
- [Styleguides](#styleguides)
- [Commit Messages](#commit-messages)
- [Join The Project Team](#join-the-project-team)

## Code of Conduct

This project and everyone participating in it is governed by the
[LinkWarden-Bot Code of Conduct](https://github.com/ThatSINEWAVE/LinkWarden-Botblob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to <EMAIL_COMING_SOON>.

## I Have a Question

> If you want to ask a question, we assume that you have read the available [Documentation]().
Before you ask a question, it is best to search for existing [Issues](https://github.com/ThatSINEWAVE/LinkWarden-Bot/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.

If you then still feel the need to ask a question and need clarification, we recommend the following:

- Open an [Issue](https://github.com/ThatSINEWAVE/LinkWarden-Bot/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.

We will then take care of the issue as soon as possible.

## I Want To Contribute

> ### Legal Notice <!-- omit in toc -->
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
### Reporting Bugs

#### Before Submitting a Bug Report

A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.

- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/ThatSINEWAVE/LinkWarden-Botissues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?

#### How Do I Submit a Good Bug Report?

> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <EMAIL_COMING_SOON>.
We use GitHub issues to track bugs and errors. If you run into an issue with the project:

- Open an [Issue](https://github.com/ThatSINEWAVE/LinkWarden-Bot/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.

Once it's filed:

- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).

### Suggesting Enhancements

This section guides you through submitting an enhancement suggestion for LinkWarden-Bot, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.

#### Before Submitting an Enhancement

- Make sure that you are using the latest version.
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](https://github.com/ThatSINEWAVE/LinkWarden-Bot/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.

#### How Do I Submit a Good Enhancement Suggestion?

Enhancement suggestions are tracked as [GitHub issues](https://github.com/ThatSINEWAVE/LinkWarden-Bot/issues).

- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
- **Explain why this enhancement would be useful** to most LinkWarden-Bot users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
33 changes: 26 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# Discord Link Checker Bot
<div align="center">

# LinkWarden - Discord Link Scanner Bot

This Discord bot, designed for personal and community use, enhances server security by scrutinizing links shared within Discord channels. It leverages the VirusTotal API, WHOIS lookups, and urlscan.io scans to provide comprehensive real-time analysis, helping maintain a safe online environment for members.

</div>

## Features

- **Real-time Link Analysis**: Automatically checks links shared in Discord against the VirusTotal database, performs WHOIS lookups, and submits URLs to urlscan.io for scanning.
@@ -10,9 +14,17 @@ This Discord bot, designed for personal and community use, enhances server secur
- **Ease of use**: The bot will embed a screenshot of the link sent for checking right in the chat in both scanning modes, simple or detailed.
- **Automatic Link Scans**: The bot will look for links sent across the server and scan them automatically if they are not from a trusted domain.
- **Customizable Trusted Domain List** - With the addition of `trusted_domains.json` you can customize what domains should be scanned.
- **Role Specific Command** - The bot wont execute commands form users that do not have the specific role listed in the `config.py` file.
- **Role Specific Command** - The bot won't execute commands form users that do not have the specific role listed in the `config.py` file.
- **Link Source** - The bot will embed the message link where the scanned link was found in making moderation easier.
- **Link History** - The bot logs all links that are sent to be scanned and keeps them for future refrence
- **Link History** - The bot logs all links that are sent to be scanned and keeps them for future reference
- **Customizable Status** - Custom status messages built-in.
- **Clean Console** - Custom print messages for every action and interaction with the bot.

<div align="center">

## [Support my work on Ko-Fi](https://ko-fi.com/thatsinewave)

</div>

## Getting Started

@@ -24,6 +36,12 @@ Before you can use the bot, you'll need to set it up with your Discord server an
- Python 3.6 or higher.
- API keys for VirusTotal and urlscan.io (available from their respective websites).

<div align="center">

# [Join my discord server](https://discord.gg/2nHHHBWNDw)

</div>

### Installation

1. Clone this repository to your local machine.
@@ -43,13 +61,13 @@ ALLOWED_ROLE_IDS = [MOD_ROLE_ID_GOES_HERE]
### Dependencies

This bot requires the following Python packages:
- discord.py
- py-cord
- requests
- python-whois
These can be installed using pip:

```python
pip install discord.py requests python-whois
pip install py-cord requests python-whois
```

Or you can install them using the requirements.txt file.
@@ -79,7 +97,7 @@ The bot is straightforward to use with a simple command structure. It supports t

### Modes Explained
- **SIMPLE**: Displays a concise embed with the top 10 warnings from VirusTotal, a summary of WHOIS information, and a link to the urlscan.io report.
- **DETAILED**: Presents a comprehensive report, marking each vendor with a color-coded dot as per the legend below, along with detailed WHOIS information and a urlscan.io security report.
- **DETAILED**: Presents a comprehensive report, marking each vendor with a color-coded dot as per the legend below, along with detailed WHOIS information and an urlscan.io security report.

### Status Dots Legend

@@ -100,7 +118,8 @@ The bot is straightforward to use with a simple command structure. It supports t

### Contributors

- **Cazaira** - helped refine and make the `trusted_domains.json` file stronger against false positives
- **Cazaira** - helped refine and make the `trusted_domains.json` file stronger against false positives.
- **[Nitrrine](https://github.com/Nitrrine)** - Discovered a critical bug that caused the bot to crash on certain links.

## License

19 changes: 19 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div align="center">

# Project Security

</div>

## Security Policy
We attach great importance to the security of our users data, but we are humans and not infallible.
That's why we rely on you, the open source contributors, to inform us about actual and possible security vulnerabilities.
Please follow the guideline below to get in touch with us, even if you're not sure, if your issue is regarding the data security.

## Reporting a Vulnerability
**Please do not open GitHub issues for security vulnerabilities, as GitHub issues are publicly accessible!**

Instead, contact us per mail (<EMAIL_COMING_SOON>).
We guarantee a response within two workdays and a security patch as fast as possible.

Thanks for your cooperation and your understanding.

71 changes: 68 additions & 3 deletions commands.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import re
import discord
import requests
import json
from discord.commands import Option
from utils import get_whois_info, get_analysis_report, submit_to_urlscan, get_urlscan_result
from config import VIRUSTOTAL_API_KEY, ALLOWED_ROLE_IDS # Import ALLOWED_ROLE_IDS
from config import VIRUSTOTAL_API_KEY, ALLOWED_ROLE_IDS, SCAN_CHANNEL_ID, MAX_URL_LENGTH, ALLOWED_CHARS_REGEX


async def checklink(ctx, link: Option(str, "Enter the link to check"), mode: Option(str, "Choose 'simple' or 'detailed' mode", choices=["simple", "detailed"]) = "simple"):
if not any(role.id in ALLOWED_ROLE_IDS for role in ctx.author.roles):
await ctx.respond("You do not have the required role to use this command.")
# Add http:// prefix if missing
if not link.startswith("http://") and not link.startswith("https://"):
link = "http://" + link

# Check if the URL contains only allowed characters
if not re.match(ALLOWED_CHARS_REGEX, link):
await ctx.respond(f"The provided URL contains invalid characters. Only standard English characters are allowed.")
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=DENIED, REASON=INVALID_CHARACTERS")
return

# Check if the URL length exceeds the maximum allowed
if len(link) > MAX_URL_LENGTH:
await ctx.respond(f"The provided URL is too long to be processed safely. Maximum allowed length is {MAX_URL_LENGTH} characters.")
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=DENIED, REASON=URL_TOO_LONG")
return

# Send an initial message indicating that the analysis is starting
initial_response = await ctx.respond(f"🔍 Analysis in progress for `{link}` in **{mode} mode**. Please wait...")
initial_message = await initial_response.original_response()
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=IN_ANALYSIS, MODE={mode}, REASON=REQUESTED_BY_USER")

headers = {"x-apikey": VIRUSTOTAL_API_KEY}
params = {'url': link}
@@ -83,16 +98,66 @@ async def checklink(ctx, link: Option(str, "Enter the link to check"), mode: Opt

# Update the initial message to indicate that the analysis is complete
await initial_message.edit(content=f"✅ Analysis for `{link}` in **{mode} mode** was completed. Please check the message below.")
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=COMPLETED, MODE={mode}, REASON=REQUESTED_BY_USER")

# Send the analysis report embed
await ctx.followup.send(embed=embed)
else:
# In case the analysis report couldn't be retrieved, update the message accordingly
await initial_message.edit(content=f"❌ Failed to retrieve the analysis report for `{link}`. Please try again later.")
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=FAILED, MODE={mode}, REASON=FAILED_TO_SUBMIT_URL")
else:
# If the link could not be submitted to VirusTotal, update the initial message
await initial_message.edit(content=f"❌ Failed to submit the URL `{link}` to VirusTotal for scanning.")
print(f"[MANU-SCAN] URL={link}, SENT_BY_USER={ctx.author}, STATUS=FAILED, MODE={mode}, REASON=FAILED_TO_RETRIEVE_REPORT")


async def checkhistory(ctx):
# Check if the user has any of the allowed roles
if not any(role.id in ALLOWED_ROLE_IDS for role in ctx.author.roles):
await ctx.respond("You do not have the required role to use this command.")
print(f"[HISTORY] REQUESTED_BY={ctx.author}, REQUESTED_IN={SCAN_CHANNEL_ID}, STATUS=DENIED, REASON=INVALID_ROLES")
return

try:
# Load seen links from file
with open('seen_links.json', 'r') as file:
seen_links = json.load(file)

# Sort the links by count, from highest to lowest
sorted_links = sorted(seen_links.items(), key=lambda item: item[1], reverse=True)

# Prepare an embed message for nicer presentation
embed = discord.Embed(title="Seen Links History",
description="This is the list of links that have been checked along with how many times they were seen, ordered from most to least seen.",
color=0x3498db)
print(f"[HISTORY] REQUESTED_BY={ctx.author}, REQUESTED_IN={SCAN_CHANNEL_ID}, STATUS=ALLOWED, REASON=VALID_ROLES")

if sorted_links:
# Limiting to show a maximum number of links due to embed field value limit
max_links_to_show = 25
links_shown = 0
for link, count in sorted_links:
if links_shown < max_links_to_show:
# Adding backticks around the link to make it unclickable
field_name = f"`{link}`"
if len(field_name) > 256:
field_name = field_name[:253] + "..."
embed.add_field(name=field_name, value=f"Link seen {count} times", inline=False)
links_shown += 1
else:
break # Stop adding more links to avoid hitting embed limits
if links_shown < len(sorted_links):
embed.set_footer(text=f"Showing the top {max_links_to_show} by usage")
else:
embed.description = "No links have been seen yet."

# Send the embed message to the user
await ctx.respond(embed=embed)
except FileNotFoundError:
await ctx.respond("The seen links history is currently empty.")


def setup_commands(bot, guild_ids):
bot.slash_command(guild_ids=guild_ids, description="Checks the provided link for security threats.")(checklink)
bot.slash_command(guild_ids=guild_ids, description="Check the history of scanned URLs")(checkhistory)
2 changes: 2 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -4,3 +4,5 @@
guild_ids = [YOUR_GUILD_ID]
SCAN_CHANNEL_ID = CHANNEL_ID_WHERE_AUTO_SCANS_GO
ALLOWED_ROLE_IDS = [MOD_ROLE_ID_HERE]
MAX_URL_LENGTH = 1000
ALLOWED_CHARS_REGEX = r'^[a-zA-Z0-9\-\.\/\?=&%#_:]*$'
179 changes: 115 additions & 64 deletions main.py
Original file line number Diff line number Diff line change
@@ -4,10 +4,9 @@
import requests
import json
from discord.ext import commands
from config import TOKEN, guild_ids
from commands import setup_commands
from utils import get_whois_info, get_analysis_report, submit_to_urlscan, get_urlscan_result
from config import VIRUSTOTAL_API_KEY, SCAN_CHANNEL_ID, ALLOWED_ROLE_IDS
from config import VIRUSTOTAL_API_KEY, SCAN_CHANNEL_ID, MAX_URL_LENGTH, TOKEN, guild_ids, ALLOWED_CHARS_REGEX
from urllib.parse import urlparse

# Enable intents
@@ -18,6 +17,7 @@

bot = commands.Bot(command_prefix="/", intents=intents)
link_queue = asyncio.Queue()
current_queued_links = set()

# Load trusted domains
with open('trusted_domains.json', 'r') as f:
@@ -47,27 +47,70 @@ async def on_message(message):
urls = re.findall(url_regex, message.content)

for url in urls:
# Check if the URL length exceeds the maximum allowed
if len(url) > MAX_URL_LENGTH:
print(f"[QUEUE] URL={url}, SENT_BY={message.author}, STATUS=SKIPPED, REASON=URL_TOO_LONG")
continue # Skip processing this URL

parsed_url = urlparse(url)
domain = parsed_url.netloc

# Removing 'www.' prefix and port numbers from the domain
domain = domain.replace('www.', '').split(':')[0]

# Immediately mark URL as seen to prevent race conditions
if domain not in trusted_domains:
print(f"Adding {url} to the queue.") # Debug print
await link_queue.put((url, message)) # Modify this line to include the message
update_seen_links(url) # Update seen_links.json instead of adding to the set
if url not in current_queued_links:
current_queued_links.add(url) # Mark as queued before async operation

try:
with open('seen_links.json', 'r') as file:
seen_links = json.load(file)
except FileNotFoundError:
seen_links = {}

seen_before = seen_links.get(url, 0) > 0

if not seen_before or (seen_before):
print(f"[QUEUE] URL={url}, SENT_BY={message.author}, STATUS=ADDED, REASON=NOT_PROCESSED")
await link_queue.put((url, message))
update_seen_links(url)
else:
current_queued_links.discard(url) # Remove from set if not queuing
else:
print(f"[QUEUE] URL={url}, SENT_BY={message.author}, STATUS=SKIPPED, REASON=IN_PROCESS")
else:
print(f"Skipping {url} as it's from a trusted domain.") # Debug print
print(f"[QUEUE] URL={url}, SENT_BY={message.author}, STATUS=SKIPPED, REASON=TRUSTED_DOMAIN")
current_queued_links.discard(url) # Ensure cleanup if not adding to queue

await bot.process_commands(message)


async def checklink_scan(channel, link, message): # Include the message parameter
mode = "simple" # Set mode to simple
async def checklink_scan(channel, link, message):
# Add http:// prefix if missing
if not link.startswith("http://") and not link.startswith("https://"):
link = "http://" + link

initial_message = await channel.send(f"🔍 Starting analysis for `{link}` in **{mode} mode**. Please wait...")
# Check if the URL contains only allowed characters
if not re.match(ALLOWED_CHARS_REGEX, link):
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=SKIPPED, REASON=INVALID_CHARACTERS")
return

# Check if the URL length exceeds the maximum allowed
if len(link) > MAX_URL_LENGTH:
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=SKIPPED, REASON=URL_TOO_LONG")
return # Skip processing this URL

with open('seen_links.json', 'r') as file:
seen_links = json.load(file)

# Check if the link is in seen_links and prepare the seen_text
if link in seen_links:
seen_text = f"Link was seen {seen_links[link]} times"
else:
seen_text = "Link was seen for the first time"

mode = "simple" # Set mode to simple
initial_message = await channel.send(f"🔍 Starting analysis for `{link}` in **{mode} mode**. Please wait...")
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=IN_ANALYSIS, MODE={mode}, REASON=SENT_BY_USER")
headers = {"x-apikey": VIRUSTOTAL_API_KEY}
params = {'url': link}
vt_response = requests.post('https://www.virustotal.com/api/v3/urls', headers=headers, data=params)
@@ -114,7 +157,7 @@ async def checklink_scan(channel, link, message): # Include the message paramet

summary_text = "🔴 Caution: This link may be harmful." if malicious_count > 0 else "🟢 This link appears to be safe."
embed = discord.Embed(title=f"Link Security Report - {mode.capitalize()} Mode",
description=f"{summary_text}\n\nDetailed results for `{link}`.\nOriginal message: [Click here]({message.jump_url})",
description=f"{summary_text}\n\nDetailed results for `{link}`.\nOriginal message: [Jump to message]({message.jump_url})\n{seen_text}",
color=0xFF0000 if malicious_count > 0 else 0x00FF00)
embed.add_field(name="WHOIS Information", value=whois_info, inline=False)

@@ -130,89 +173,97 @@ async def checklink_scan(channel, link, message): # Include the message paramet
if screenshot_url:
embed.set_image(url=screenshot_url)

await initial_message.edit(content=f"✅ Analysis for `{link}` in **{mode} mode** was completed. Please check the message below.")
await initial_message.edit(
content=f"✅ Analysis for `{link}` in **{mode} mode** was completed. Please check the message below.")
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=COMPLETED, MODE={mode}")
await channel.send(embed=embed)
else:
await initial_message.edit(content=f"❌ Failed to retrieve the analysis report for `{link}`. Please try again later.")
await initial_message.edit(
content=f"❌ Failed to retrieve the analysis report for `{link}`. Please try again later.")
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=FAILED, MODE={mode}, REASON=FAILED_TO_RETRIEVE_REPORT")
else:
await initial_message.edit(content=f"❌ Failed to submit the URL `{link}` to VirusTotal for scanning.")


@bot.slash_command(description="Check the history of scanned URLs", guild_ids=guild_ids)
async def checkhistory(ctx):
# Check if the user has any of the allowed roles
if not any(role.id in ALLOWED_ROLE_IDS for role in ctx.author.roles):
await ctx.respond("You do not have the required role to use this command.")
return

try:
# Load seen links from file
with open('seen_links.json', 'r') as file:
seen_links = json.load(file)

# Prepare an embed message for nicer presentation
embed = discord.Embed(title="Seen Links History",
description="This is the list of links that have been checked along with how many times they were seen.",
color=0x3498db) # You can change the color

if seen_links:
# Limiting to show a maximum number of links due to embed field value limit
max_links_to_show = 25
links_shown = 0
for link, count in seen_links.items():
if links_shown < max_links_to_show:
# Adding backticks around the link to make it unclickable
embed.add_field(name=f"`{link}`", value=f"Seen {count} times", inline=False)
links_shown += 1
else:
break # Stop adding more links to avoid hitting embed limits
if links_shown < len(seen_links):
embed.set_footer(text=f"and more links... (showing only the first {max_links_to_show})")
else:
embed.description = "No links have been seen yet."

# Send the embed message to the user
await ctx.respond(embed=embed)
except FileNotFoundError:
await ctx.respond("The seen links history is currently empty.")
print(f"[AUTO-SCAN] URL={link}, SENT_BY_USER={message.author}, STATUS=FAILED, MODE={mode}, REASON=FAILED_TO_SUBMIT_URL")


async def process_link_queue():
while True:
link, message = await link_queue.get() # Modify this line to unpack message
link, message = await link_queue.get()

# Use SCAN_CHANNEL_ID from the config
scan_channel = bot.get_channel(SCAN_CHANNEL_ID)

if scan_channel:
await checklink_scan(scan_channel, link, message) # Pass the message here
await checklink_scan(scan_channel, link, message)
# Use discard instead of remove to avoid KeyError
current_queued_links.discard(link)

# Check the size of the queue after the scan is completed
if link_queue.qsize() > 0: # Check if there are more links in the queue
if link_queue.qsize() > 0:
# Access the queue's internal storage to read items without removing them
temp_list = list(link_queue._queue)

embed = discord.Embed(title="Current Link Queue", color=0x00FF00)
embed.description = "\n".join([f"{idx + 1}. `{item[0]}`" for idx, item in enumerate(temp_list)]) # Modify to access link

# Add a note about the cooldown in the footer
embed.description = "\n".join([f"{idx + 1}. `{item[0]}`" for idx, item in enumerate(temp_list)])
embed.set_footer(text="Note: There is a 1-minute cooldown between scans.")

# Truncate if exceeds max length limit
if len(embed.description) > 4096 - len(embed.footer.text) - 2: # Adjusting for footer length
if len(embed.description) > 4096 - len(embed.footer.text) - 2:
embed.description = embed.description[:4093 - len(embed.footer.text) - 2] + "..."

await scan_channel.send(embed=embed)
else:
print(f"Could not find the scan channel with ID {SCAN_CHANNEL_ID}")
print(f"[ERROR] STATUS=FAILED, REASON=CHANNEL_{SCAN_CHANNEL_ID}_COULD_NOT_BE_FOUND")

await asyncio.sleep(60)


async def cycle_status_messages():
status_messages = [
discord.Game(name="with dangerous links"),
discord.Activity(type=discord.ActivityType.listening, name="your conversations"),
discord.Game(name="Project CW"),
discord.Activity(type=discord.ActivityType.watching, name="your chat"),
discord.Game(name="with viruses"),
discord.Activity(type=discord.ActivityType.competing, name="tournaments"),
discord.Game(name="dead"),
discord.Activity(type=discord.ActivityType.listening, name="PCW Menu Music"),
discord.Game(name="poker with scammers"),
discord.Activity(type=discord.ActivityType.watching, name="#cw-discussion"),
discord.Game(name="with fire"),
discord.Activity(type=discord.ActivityType.competing, name="leaderboard"),
discord.Game(name="with source code"),
discord.Activity(type=discord.ActivityType.listening, name="your feedback"),
discord.Game(name="*Bug Hunting*"),
discord.Activity(type=discord.ActivityType.watching, name="scammers"),
discord.Game(name="hide and seek"),
discord.Activity(type=discord.ActivityType.competing, name="the chat"),
discord.Game(name="with APIs"),
discord.Activity(type=discord.ActivityType.listening, name="podcasts"),
discord.Game(name="Brimstone"),
discord.Activity(type=discord.ActivityType.watching, name="out for NSFW Servers"),
discord.Game(name="nothing, im a bot"),
discord.Activity(type=discord.ActivityType.competing, name="life"),
discord.Game(name="with keyboard"),
discord.Activity(type=discord.ActivityType.listening, name="SINEWAVE"),
discord.Game(name="<REDACTED>"),
discord.Activity(type=discord.ActivityType.watching, name="#off-topic"),
discord.Game(name="with leaks"),
discord.Activity(type=discord.ActivityType.competing, name="time"),
]

await asyncio.sleep(60) # 1-minute delay before processing the next link
while True:
for status in status_messages:
await bot.change_presence(activity=status)
await asyncio.sleep(30) # Adjust the sleep time as needed


@bot.event
async def on_ready():
print(f'{bot.user} has connected to Discord!')
await bot.loop.create_task(process_link_queue())
print(f'[BOT] BOT={bot.user}, STATUS=CONNECTED')
asyncio.create_task(process_link_queue())
asyncio.create_task(cycle_status_messages())


# Setup commands
setup_commands(bot, guild_ids)
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
py-cord==2.4.1
discord.py==2.3.2
requests==2.26.0
py-cord==2.5.0
python-whois==0.7.3
requests==2.32.0
6 changes: 2 additions & 4 deletions seen_links.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
{
"https://example1.com": 1,
"https://example2.com": 1,
"https://example3.com": 1
}
"https://example.com": 1
}
10 changes: 5 additions & 5 deletions utils.py
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ def get_whois_info(domain):
whois_text += f"Expiration Date: {w.expiration_date}\n"
return whois_text
except Exception as e:
print(f"Failed to fetch WHOIS info: {e}")
print(f"[WHOIS] RESPONSE={e}")
return "WHOIS information could not be retrieved."


@@ -28,10 +28,10 @@ def get_analysis_report(analysis_url, headers, retries=4, delay=15):
if report['data']['attributes']['status'] == 'completed':
return report
else:
print(f"Analysis not completed yet, attempt {attempt + 1}/{retries}. Retrying in {delay} seconds...")
print(f"[VIRUSTOTAL] ATTEMPT={attempt + 1}/{retries} STATUS=RETRY_IN_{delay}_SECONDS")
time.sleep(delay)
else:
print(f"Failed to fetch the report, status code: {report_response.status_code}")
print(f"[VIRUSTOTAL] REASON=FAILED_TO_FETCH_RESULTS, STATUS= {report_response.status_code}")
break
return None

@@ -44,7 +44,7 @@ def submit_to_urlscan(link):
scan_uuid = response.json().get('uuid')
return scan_uuid # Return the uuid instead of the result URL
else:
print(f"Failed to submit to urlscan.io, status code: {response.status_code}")
print(f"[URLSCAN] RESPONSE={response.status_code}")
return None


@@ -58,6 +58,6 @@ def get_urlscan_result(scan_uuid, retries=4, delay=15):
scan_data = response.json()
return scan_data
else:
print(f"Attempt {attempt + 1}/{retries}: Failed to fetch the scan result, status code: {response.status_code}")
print(f"[URLSCAN] ATTEMPT={attempt + 1}/{retries}, REASON=FAILED_TO_FETCH_RESULTS, STATUS={response.status_code}")

return None