Full Code of CyberDiscovery/cyberdisc-bot for AI

master 4a6b6c1e92e9 cached
41 files
493.5 KB
114.7k tokens
119 symbols
1 requests
Download .txt
Showing preview only (512K chars total). Download the full file or copy to clipboard to get everything.
Repository: CyberDiscovery/cyberdisc-bot
Branch: master
Commit: 4a6b6c1e92e9
Files: 41
Total size: 493.5 KB

Directory structure:
gitextract_cksaviai/

├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── emoji_request.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   ├── labels.json
│   ├── labels.yaml
│   └── workflows/
│       ├── build.yml
│       ├── codeql-analysis.yml
│       ├── create-labels.yml
│       ├── label-pull-requests.yml
│       ├── lint.yml
│       ├── stale.yml
│       └── test-results.yml
├── .gitignore
├── .security.txt
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── cdbot/
│   ├── __init__.py
│   ├── __main__.py
│   ├── bot.py
│   ├── cogs/
│   │   ├── admin.py
│   │   ├── cyber.py
│   │   ├── fun.py
│   │   ├── general.py
│   │   ├── help.py
│   │   └── maths.py
│   ├── constants.py
│   ├── data/
│   │   ├── assess.json
│   │   ├── game.json
│   │   ├── legacy/
│   │   │   ├── game.json
│   │   │   └── headquarters2018.json
│   │   └── readme.json
│   └── log.py
├── docker-compose.yml
├── pyproject.toml
└── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
.github
.vscode
azure-pipelines
.gitignore
.security.txt
CODE_OF_CONDUCT.md
deployment.yaml
docker-compose.yml
LICENSE
README.md
requirements-ci.txt
SECURITY.md
tox.ini

================================================
FILE: .github/CODEOWNERS
================================================
# To require the approval of the server staff as well as that of the technical community developer team
*       @CyberDiscovery/server-admin-team


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:
1. Use command '...'
2. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/ISSUE_TEMPLATE/emoji_request.md
================================================
---
name: Emoji Reaction Request
about: Request the addition of an automatic emoji reaction
title: ''
labels: enhancement
assignees: ''

---

**Reaction Trigger**
What text should cause the reaction?

**Reaction Emoji**
What emojis should be reacted to the message?

**Explanation**
Why should we add this? What does this reaction reference?

Do note that references only a couple of people understand may not be suitable.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**
A clear and concise description of what you want to happen.

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.

**Additional context**
Add any other context or screenshots about the feature request here.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10
  reviewers:
  - thebeanogamer


================================================
FILE: .github/labels.json
================================================
[
    {
        "name": "duplicate",
        "color": "26282a",
        "description": "This issue or pull request already exists"
    },
    {
        "name": "enhancement",
        "color": "105a5b",
        "description": "New feature or request"
    },
    {
        "name": "help wanted",
        "color": "008672",
        "description": "Extra attention is needed"
    },
    {
        "name": "meta",
        "color": "07a45d",
        "description": "Related to this repo, rather than the bot"
    },
    {
        "name": "question",
        "color": "66176f",
        "description": "Further information is requested"
    },
    {
        "name": "admin",
        "color": "1e3a21",
        "description": "Changes to the admin cog"
    },
    {
        "name": "actions",
        "color": "ff355e",
        "description": "Changes to GitHub Actions"
    }, {
        "name": "cyber",
        "color": "ff9933",
        "description": "Changes to the cyber cog"
    }, {
        "name": "fun",
        "color": "ffff66",
        "description": "Changes to the fun cog"
    }, {
        "name": "general",
        "color": "66ff66",
        "description": "Changes to the general cog"
    }, {
        "name": "module",
        "color": "50bfe6",
        "description": "Changes to the bot module"
    }, {
        "name": "labels",
        "color": "5946b2",
        "description": "Changes to GitHub labels"
    }, {
        "name": "issue",
        "color": "24601e",
        "description": "Problems with the server or infrastructure"
    }, {
        "name": "stale",
        "color": "ee34D2",
        "description": "Inactive topics"
    },
    {
        "name": "dependencies",
        "color": "035fc7",
        "description": "Update or change dependencies"
    },
    {
        "name": "maths",
        "color": "03aaaa",
        "description": "Changes to the maths cog"
    },
    {
        "name": "bug",
        "color": "ff5d40",
        "description": "Errors in the source code"
    }
]


================================================
FILE: .github/labels.yaml
================================================
meta:
  - .github/*
  - .github/**/*
  - .gitignore
  - .security.txt
  - CODE_OF_CONDUCT.md
  - LICENSE
  - README.md
  - SECURITY.md
  
actions:
  - .github/*.yml
  - .github/**/*.yml
  - azure-pipelines/*
  - azure-pipelines/**/*
  - Dockerfile
  - docker-compose.yml
  - deployment.yaml

labels:
  - .github/labels.json

admin:
  - cdbot/cogs/admin.py

cyber:
  - cdbot/cogs/cyber.py

maths:
  - cdbot/cogs/maths.py

fun:
  - cdbot/cogs/fun.py

general:
  - cdbot/cogs/general.py

module:
  - cdbot/__init__.py
  - cdbot/__main__.py
  - cdbot/bot.py
  - cdbot/constants.py
  - cdbot/log.py
  - cdbot/data/*
  - cdbot/data/**/*
  - cdbot/resources/*
  - cdbot/resources/**/*
  - pyproject.toml
  - poetry.lock

dependencies:
  - poetry.lock


================================================
FILE: .github/workflows/build.yml
================================================
name: Build Docker Container

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Build Docker Container
      run: docker build -t cyberdiscovery/cdbot:latest -t ghcr.io/cyberdiscovery/cdbot:latest .

    - name: Create Sentry release
      uses: getsentry/action-release@v1
      if: github.ref == 'refs/heads/master'
      env:
        SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
        SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
        SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

    - name: Login to Docker Hub
      run: docker login --username ${{ secrets.docker_username }} --password ${{ secrets.docker_password }}
      if: github.ref == 'refs/heads/master'

    - name: Login to Github Container Registry
      run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      if: github.ref == 'refs/heads/master'

    - name: Push to Docker Hub
      run: docker push cyberdiscovery/cdbot:latest
      if: github.ref == 'refs/heads/master'

    - name: Push to Github Container Registry
      run: docker push ghcr.io/cyberdiscovery/cdbot:latest
      if: github.ref == 'refs/heads/master'

    - name: Trigger restart
      run: curl -s ${{ secrets.release_url }}
      if: github.ref == 'refs/heads/master'


================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
name: "Code scanning"


on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]
  schedule:
    - cron: '43 5 * * 0'

jobs:
  CodeQL-Build:

    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2
      with:
        fetch-depth: 2

    - run: git checkout HEAD^2
      if: ${{ github.event_name == 'pull_request' }}
      
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v1
      with:
        languages: python

    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v1


================================================
FILE: .github/workflows/create-labels.yml
================================================
on: issues
name: Create Default Labels
jobs:
  labels:
    name: DefaultLabelsActions
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@1.0.0
      - uses: lannonbr/issue-label-manager-action@2.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/label-pull-requests.yml
================================================
name: "Pull Request Labeler"

on:
  schedule:
  - cron: "*/15 * * * *"

jobs:
  execute:
    runs-on: ubuntu-latest
    steps:
      - uses: jpmcb/prow-github-actions@v1.0.0
        with:
          jobs: 'pr-labeler'
          github-token: "${{ secrets.GITHUB_TOKEN }}"


================================================
FILE: .github/workflows/lint.yml
================================================
name: Lint

on: push

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - uses: actions/setup-python@v2
      with:
        python-version: '3.9'

    - name: Cache Python modules
      uses: actions/cache@v1
      env:
        cache-name: cache-node-modules
      with:
        path: ~/.pip
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/poetry.lock') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ env.cache-name }}-
          ${{ runner.os }}-build-
          ${{ runner.os }}-

    - name: Install Poetry
      run: curl -sSL https://install.python-poetry.org | python3 -

    - name: Disable Virtualenvs
      run: $HOME/.local/bin/poetry config virtualenvs.create false

    - name: Install Python Dependencies
      run: $HOME/.local/bin/poetry install --no-interaction --no-ansi
      env:
        PIP_CACHE_DIR: ~/.pip

    - name: Run flake8
      run: python -m flake8 . | tee flake8.xml

    - name: Upload JUnit
      uses: actions/upload-artifact@v2
      with:
        name: Event File
        path: flake8.xml


================================================
FILE: .github/workflows/stale.yml
================================================
name: "Close stale issues"
on:
  schedule:
  - cron: "0 0 * * *"

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/stale@v1
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
        stale-pr-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days'
        days-before-stale: 30
        days-before-close: 5


================================================
FILE: .github/workflows/test-results.yml
================================================
name: Unit Test Results

on:
  workflow_run:
    workflows: ["Lint"]
    types:
      - completed

jobs:
  unit-test-results:
    name: Unit Test Results
    runs-on: ubuntu-latest
    if: github.event.workflow_run.conclusion != 'skipped'

    steps:
      - name: Download and Extract Artifacts
        env:
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
        run: |
           mkdir -p artifacts && cd artifacts

           artifacts_url=${{ github.event.workflow_run.artifacts_url }}

           gh api "$artifacts_url" -q '.artifacts[] | [.name, .archive_download_url] | @tsv' | while read artifact
           do
             IFS=$'\t' read name url <<< "$artifact"
             gh api $url > "$name.zip"
             unzip -d "$name" "$name.zip"
           done

      - name: Publish Unit Test Results
        uses: EnricoMi/publish-unit-test-result-action@v1
        with:
          commit: ${{ github.event.workflow_run.head_sha }}
          files: "artifacts/**/*.xml"


================================================
FILE: .gitignore
================================================
# Changes to the lockfile should be deliberate
poetry.lock

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# Sublime Text project settings
*.sublime-project

# mkdocs documentation
/site

# mypy
.mypy_cache/

# Virtualenv
.Python
[Bb]in
[Ii]nclude
[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
pyvenv.cfg
.venv
pip-selfcheck.json
tcl
share

# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# deploy ssh key
id_rsa

# Flake output
flake8.xml

# VS Code
.vscode/*

secret-creds.yaml

================================================
FILE: .security.txt
================================================
Contact: thebeanogamer@gmail.com
Encryption: https://keybase.io/thebeanogamer/pgp_keys.asc
Preferred-Languages: en
Canonical: https://raw.githubusercontent.com/CyberDiscovery/cyberdisc-bot/master/.security.txt
Policy: https://github.com/CyberDiscovery/cyberdisc-bot/blob/master/SECURITY.md


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

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

## Our Standards

Examples of behavior that contributes to creating a positive environment include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

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

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.

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, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at thebeanogamer@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]

[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/


================================================
FILE: Dockerfile
================================================
FROM python:3.9-buster

WORKDIR /app
RUN pip install poetry
ADD pyproject.toml poetry.lock /app/
RUN poetry config virtualenvs.create false
RUN poetry install --no-dev --no-interaction --no-ansi
ADD . /app

CMD python -m cdbot


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-2020 Cyber Discovery Community

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Cyber Discovery Discord Bot

[![Build Docker Container](https://github.com/CyberDiscovery/cyberdisc-bot/actions/workflows/build.yml/badge.svg)](https://github.com/CyberDiscovery/cyberdisc-bot/actions/workflows/build.yml) [![Discord](https://img.shields.io/discord/409851296116375565)](https://discord.cyberdiscoverycommunity.uk) [![Lint](https://github.com/CyberDiscovery/cyberdisc-bot/actions/workflows/lint.yml/badge.svg)](https://github.com/CyberDiscovery/cyberdisc-bot/actions/workflows/lint.yml) [![GitHub](https://img.shields.io/github/license/cyberdiscovery/cyberdisc-bot)](https://github.com/CyberDiscovery/cyberdisc-bot/blob/master/LICENSE) ![Python 3.9.x](https://img.shields.io/badge/python-3.9.x-yellow.svg)

The bot for the Cyber Discovery [Community Discord Server](https://discord.cyberdiscoverycommunity.uk)!

## Installation

### Creating a Bot Token

First, head over to [the Discord Developer Portal](https://discordapp.com/developers/applications/) and create an application.
Set a name, then go to the bots tab (on the left) and add a new bot. For testing purposes, it is best to have the bot private, so untick the `Public Bot` option.

You should then get the client ID of your bot and put it into this URL to join it to your Discord server:

```text
https://discordapp.com/oauth2/authorize?&client_id=<insert client id here>&scope=bot&
```

### Docker Compose

You can run Cyber Discovery Bot most easily with Docker Compose. Just set the following environment variables and run `docker-compose up -d`.

* `BOT_TOKEN`
* `QUOTES_BOT_ID`
* `QUOTES_CHANNEL_ID`
* `LOGGING_CHANNEL_ID`

### Bare Metal

You can also locally install the bot on your system. First install the dependencies with [Poetry](https://python-poetry.org/):

```bash
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python
poetry install
```

Or on Windows:

```powershell
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python
poetry install
```

If you don't already have the required Python version (currently 3.8+) installed, install [pyenv](https://github.com/pyenv/pyenv) before running the above commands.

You will then need to set the bot token as an environment variable:

```bash
export BOT_TOKEN="<insert bot token here>"
```

Or on Windows:

```powershell
set BOT_TOKEN="<insert bot token here>"
```

Finally, run the command `poetry run cdbot` in the root of the repository to run the bot on your machine. To access admin commands of the bot, set the environment variable `ROOT_MEMBERS_ID` to the ID of your administrators group.

## Commands

### General Commands

* **`:help`** - Displays information about the usage and syntax of all commands.

### Admin Commands

* **`:readme [push | pull] [channel id] [interval]`** - `pull` will DM the user a copy of the JSON used for #readme. `push` will create the readme channel using the set JSON file.

### Cyber Discovery Commands

* **`:assess [1-14]`** - Displays information about the corresponding level in CyberStart Assess.
* **`:essentials`** - Displays the remaining time until the start of CyberStart Essentials.
* **`:fieldmanual`** - Returns a link to the CyberStart Game field manual.
* **`:flag [base] [level] [challenge]`** - Generate a very legitimate:tm: flag for CyberStart Game.
* **`:game`** - Displays the remaining time until the start of CyberStart Game.
* **`:level [base] [level] [challenge]`** - Display information about challenges from CyberStart Game.

### Fun Commands

* **`:agentj [text]`** - Creates an image of Agent J with the specified text.
* **`:agentq [text]`** - Creates an image of Agent Q with the specified text.
* **`:angryj [text]`** - Creates an image of Angry Agent J with the specified text.
* **`:angrylyne [text]`** - Creates an image of Angry James Lyne with the specified text.
* **`:baldj [text]`** - Creates an image of Bald James Lyne with the specified text.
* **`:jibhat [text]`** - Creates an image of Jibhat with the specified text.
* **`:lmgtfy [-d][-ie] [search]`** - Returns a LMGTFY URL for the given question.  Adding `-d` will delete the message that instigated the command and `-ie` will enable the internet explainer feature on lmgtfy.
* **`:hundred`** - Returns the number of people who have completed all of CyberStart Game.
* **`:quotes [@mention]`** - Will return a random quote from the #quotes channel. Adding an username/mention will result in a random quote from that user being selected.
* **`:quoteboard [1]`** - Return a leaderboard of the number of entries in #quotes sorted by user.
* **`:quotecount [@mention]`** - Returns the number of quotes in the DB. Adding a username/mention will return the number of quotes from that user.
* **`:react [emoji]`** - Reacts to the previous message with the space seperated emojis in the requesting message.
* **`:xkcd [? | 1810]`** - Fetches xkcd comics. If the argument is left blank the latest comic is shown.  A random comic is shown if the argument is a `?`.  Otherwise, a comic number can be used to fetch a specific comic.

### Maths Commands

* **`:challenge [number]`** - Get a KCL maths challenge. If no number is specified, the most recent will be used, else the number will be how many after the most recent should be retrieved.
* **`:latex [latex]`** - Renders LaTeX. Can also be invoked by wrapping a string in `$` or `$$`.


================================================
FILE: SECURITY.md
================================================
# Security Policies and Procedures

This document outlines security procedures and general policies for the **cyberdisc-bot**
project.

* [Reporting a Bug](#reporting-a-bug)
* [Disclosure Policy](#disclosure-policy)
* [Comments on this Policy](#comments-on-this-policy)

## Reporting a Bug

The Cyber Discovery team and community take all security bugs in **cyberdisc-bot** seriously.
Thank you for improving the security of **cyberdisc-bot**. We appreciate your efforts and
responsible disclosure and will make every effort to acknowledge your
contributions.

Report security bugs by emailing an administrator at [daniel@cyberdiscoverycommunity.uk](mailto:daniel@cyberdiscoverycommunity.uk).

## Disclosure Policy

When the community developer team receives a security bug report, they will take the following steps:

* Confirm the problem and determine the cause.
* Audit code to find any potential similar problems.
* Prepare fixes for all affected code bases. These fixes will be
    released as fast as possible to production.

## Comments on this Policy

If you have suggestions on how this process could be improved please submit a
pull request, or add an appropriate issue to the repository in question.



================================================
FILE: cdbot/__init__.py
================================================
"""Initialise cdbot as a package for poetry."""

import sentry_sdk
from git import Repo
from sentry_sdk.integrations.aiohttp import AioHttpIntegration

from .bot import bot, load_extensions
from .constants import BOT_TOKEN, SENTRY_URL


async def main():
    """Entry point for poetry script."""
    sentry_sdk.init(
        SENTRY_URL,
        release=Repo().head.object.hexsha,
        integrations=[AioHttpIntegration()],
    )
    await load_extensions()
    await bot.start(BOT_TOKEN)


================================================
FILE: cdbot/__main__.py
================================================
"""Entry point for the 'python -m cdbot' command."""

from asyncio import run

import cdbot

if __name__ == "__main__":
    run(cdbot.main())


================================================
FILE: cdbot/bot.py
================================================
"""Main script to define bot methods, and start the bot."""

import logging
from os import listdir
from platform import release, system

from discord import Game, Intents
from discord.ext.commands import Bot, when_mentioned_or
from sentry_sdk import configure_scope

from cdbot.log import DiscordHandler

logger = logging.getLogger(__name__)

intents = Intents.default()
intents.members = True
intents.message_content = True

bot = Bot(command_prefix=when_mentioned_or("...", ":"), activity=Game(name=":help"), intents=intents)

logger.addHandler(DiscordHandler(bot))
logger.setLevel(logging.INFO)

bot.muted = []
bot.banned_ids = []
bot.log = logger


@bot.before_invoke
async def register_metadata(ctx):
    """Attach additional data to sentry events."""
    with configure_scope() as scope:
        scope.user = {"id": ctx.author.id, "username": str(ctx.author)}
        scope.set_context("client_os", {"name": system(), "version": release()})
        scope.set_tag("command", ctx.message.content)
        scope.set_tag("channel", str(ctx.channel))


@bot.check
async def block_banned_ids(ctx):
    """Check for if a user is banned."""
    return ctx.author.id not in bot.banned_ids


@bot.check
async def block_muted(ctx):
    """Check for if a user is muted."""
    return ctx.author.id not in bot.muted


async def load_extensions():
    for filename in listdir("./cdbot/cogs"):
        if filename.endswith(".py"):
            # cut off the .py from the file name
            await bot.load_extension(f"cdbot.cogs.{filename[:-3]}")


================================================
FILE: cdbot/cogs/admin.py
================================================
import re

from discord import AuditLogAction, Colour, Embed, Member
from discord.ext.commands import Bot, Cog, Context, command, has_any_role

from cdbot.constants import (
    ADMIN_MENTOR_ROLE_ID,
    ADMIN_ROLES,
    CD_BOT_ROLE_ID,
    LOGGING_CHANNEL_ID,
    NICKNAME_PATTERNS,
    PLACEHOLDER_NICKNAME,
    ROOT_ROLE_ID,
    STATIC_NICKNAME_ROLE_ID,
    SUDO_ROLE_ID
)


def check_bad_name(nick):
    for i in NICKNAME_PATTERNS:
        if re.match(i, nick, re.IGNORECASE):
            return True
    return False


class Admin(Cog):
    """
    Admin functionality
    """

    def __init__(self, bot: Bot):
        self.bot = bot

    @Cog.listener()  # triggered on new/removed nickname
    async def on_member_update(self, member_before: Member, member_after: Member):
        # get corresponding audit log entry to find who initiated member change
        corresponding_audit_entry = None
        # get all audit log entries for Member Updated
        async for entry in self.bot.guilds[0].audit_logs(
            action=AuditLogAction.member_update
        ):
            # if this entry was to the user in question, and was this specific nickname change
            if entry.target == member_before and entry.after.nick == member_after.nick:
                corresponding_audit_entry = entry
                break

        if (
            corresponding_audit_entry is not None
        ):  # successfully found audit log entry before
            # user changed their own nickname; ignore if admin/bot changed it
            admin_role_check = (
                corresponding_audit_entry.user.top_role.name in ADMIN_ROLES
            )
            bot_role_check = (
                corresponding_audit_entry.user.top_role.id == CD_BOT_ROLE_ID
            )
            mentor_role_check = (
                corresponding_audit_entry.user.top_role.id == ADMIN_MENTOR_ROLE_ID
            )
            if not (admin_role_check or bot_role_check or mentor_role_check):
                for i in member_after.roles:
                    if i.id == STATIC_NICKNAME_ROLE_ID:  # user has Static Name role
                        await member_after.edit(
                            nick=member_before.display_name
                        )  # revert nickname
                        return
                    else:  # check for bad words
                        new_nickname = member_after.display_name
                        if check_bad_name(new_nickname):  # bad display name
                            if not check_bad_name(
                                member_after.name
                            ):  # username is okay
                                await member_after.edit(nick=None)  # reset nickname
                            else:
                                # assign placeholder nickname
                                await member_after.edit(nick=PLACEHOLDER_NICKNAME)

    @Cog.listener()  # triggered on username change
    async def on_user_update(self, member_before: Member, member_after: Member):
        new_username = member_after.name
        if check_bad_name(new_username):  # bad username
            # assign placeholder nickname
            await member_after.edit(nick=PLACEHOLDER_NICKNAME)

    @Cog.listener()
    async def on_member_join(self, member: Member):
        username = member.name
        if check_bad_name(username):  # bad username
            # assign placeholder nickname
            await member.edit(nick=PLACEHOLDER_NICKNAME)

    @command()
    @has_any_role(ROOT_ROLE_ID, SUDO_ROLE_ID)
    async def raid(
        self,
        ctx: Context,
        operand: str = ""
    ):
        """
        Allows an admin user to lock down the server in case of a raid.
        This command toggles invite link generation for @everyone and
        revokes all existing invite links.
        """

        everyone = ctx.channel.guild.default_role
        perms = everyone.permissions
        enabled = not perms.create_instant_invite
        logs_channel = self.bot.get_channel(LOGGING_CHANNEL_ID)

        operand = operand.lower()
        boolonoff = ("OFF", "ON")

        action = True
        embed = None

        if not operand:  # status query
            await ctx.send(f"Raid protection currently {boolonoff[enabled]}. Use `:raid [on/off]` to toggle.")
            action = False

        elif operand in ("on", "yes") and not enabled:  # need to turn it on
            enabled = True
            perms.update(create_instant_invite=False)
            embed = Embed(
                color=Colour.blue(),
                title="Raid Protection ON.",
                description=("Raid protection now ON - All invite links were"
                             " deleted and members may not create new ones")
            )
            for invite in await ctx.channel.guild.invites():  # delete links
                await invite.delete()

        elif operand in ("off", "no") and enabled:
            enabled = False
            perms.update(create_instant_invite=True)
            embed = Embed(
                color=Colour.blue(),
                title="Raid Protection OFF.",
                description=("Raid protection now OFF - Members can now create"
                             " new invite links")
            )

        else:  # no changes
            await ctx.send(f"Raid protection {boolonoff[enabled]}, nothing was changed.")
            action = False

        if action:  # if we toggled it
            msg = f"{ctx.author.name} toggled raid protection {boolonoff[enabled]}."
            await everyone.edit(reason=msg, permissions=perms)  # make the perm change
            await ctx.send(msg)  # direct response to invocation

            embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url)
            await logs_channel.send(embed=embed)  # log the event


async def setup(bot):
    await bot.add_cog(Admin(bot))


================================================
FILE: cdbot/cogs/cyber.py
================================================
import datetime
import random
import re
import string
import textwrap
from asyncio import sleep
from io import StringIO
from json import load

from aiohttp import ClientSession
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta
from discord import Colour, Embed, File, Message
from discord.ext.commands import Bot, Cog, Context, command, has_role

from cdbot.constants import (
    BASE_ALIASES,
    CHEATING_VIDEO,
    CMA_LINKS,
    CYBERDISC_ICON_URL,
    ELITECOUNT_ENABLED,
    END_README_MESSAGE,
    HINTS_LIMIT,
    HUNDRED_PERCENT_ROLE_ID,
    README_RECV_ALIASES,
    README_SEND_ALIASES,
    ROOT_ROLE_ID,
    Roles,
    TRUE_HUNDRED_PERCENT_ROLE_ID,
)


async def generatebase64(seed: int) -> str:
    random.seed(seed)
    letters = string.ascii_letters + string.digits + "+/"
    return "".join(random.choices(letters, k=20))


class Cyber(Cog):
    """
    Cyber Discovery/Security related commands.
    """

    match_strings = [
        # Assess dates
        (
            r"^.*\bassess\b.*\b(start|begin|open)\b.*$",
            "CyberStart Assess began on the 2nd June 2020.",
        ),
        (
            r"^.*\bassess\b.*\b(end|finish|close)\b.*$",
            "CyberStart Assess ended on the 31st October 2020.",
        ),
        # Game dates
        (
            r"^.*\bgame\b.*\b(start|begin|open)\b.*$",
            "CyberStart Game began on the 2nd June 2020.",
        ),
        (
            r"^.*\bgame\b.*\b(end|finish|close)\b.*$",
            "CyberStart Game ended on the 30th June 2021.",
        ),
        # Essentials dates
        (
            r"^.*\bessentials\b.*\b(start|begin|open)\b.*$",
            "CyberStart Essentials began on the 18th December 2020.",
        ),
        (
            r"^.*\bessentials\b.*\b(end|finish|close)\b.*$",
            "CyberStart Essentials ended on the 30th June 2021.",
        ),
        # Elite questions
        (
            r"^.*\bhow\b.*\bget\b.*\belite\b.*$",
            "**Quote from the @CyberDiscUK Twitter: **"
            "Selection for CyberStart Elite was based on a combination of Game and Essentials results.",
        ),
        (
            r"^.*\belite\b.*\bstart\b.*$",
            "CyberStart Elite is kill <a:crabrave:770007760200400897>.",
        ),
        # RACTF Questions
        (
            r"^.*\bractf\b.*\b(start|begin|open)\b.*$",
            "RACTF 2021 begins on the 13th August 2021 at 19:00 GMT.",
        ),
        (
            r"^.*\bractf\b.*\b(end|finish|close)\b.*$",
            "RACTF 2021 will end on the 16th August 2021 at 19:00 GMT.",
        ),
    ]

    def __init__(self, bot: Bot):
        self.bot = bot

        self.matches = [
            (re.compile(i[0], re.IGNORECASE), i[1]) for i in self.match_strings
        ]

    @command(aliases=["Manual", "manual", "fm", "rtfm"])
    async def fieldmanual(self, ctx: Context):
        """
        Returns a link to the field manual
        """

        await ctx.send("https://play.cyberstart.com/field-manual")

    @command(aliases=["l", "lc"])
    async def level(
        self, ctx: Context, base: str, level_num: int, challenge_num: int = 0
    ):
        """
        Gets information about a specific CyberStart Game level and challenge.
        """

        # Gather data from CyberStart Game.
        with open("cdbot/data/game.json") as f:
            game_docs = load(f)
        # Temporary change to allow old usage method
        if base.isnumeric():
            challenge_num = level_num
            level_num = int(base)
            base = "hq"
        elif challenge_num == 0:
            await ctx.send("Invalid challenge number!")
            return
        # Find out which base the user is refering to.
        for area in BASE_ALIASES.keys():
            if base.lower() in BASE_ALIASES[area]:
                base = area
                break
        else:
            await ctx.send("Unknown base.")
            return

        # Check to see if that many levels are present
        if not 0 < level_num <= len(game_docs[base]):
            await ctx.send("Invalid level number!")
        # Then, check to see if the requested challenge is present
        elif challenge_num not in range(len(game_docs[base][level_num - 1]) + 1):
            await ctx.send("Invalid challenge number!")

        else:
            # Select the needed challenge
            challenge_raw = game_docs[base][level_num - 1][challenge_num - 1]
            challenge_title = challenge_raw["title"]
            challenge_tip = challenge_raw["tips"]
            challenge_text = challenge_raw["description"]
            embed = Embed(
                title=(
                    f"{base} - Level {level_num} Challenge {challenge_num} - {challenge_title}"
                ),
                description=challenge_text,
                colour=0x4262F4,
            )
            embed.set_author(name="Cyber Discovery", icon_url=CYBERDISC_ICON_URL)
            embed.set_footer(text="  |  ".join(challenge_tip))

            await ctx.send(embed=embed)

    @command()
    async def flag(
        self, ctx: Context, base: str, level_num: int, challenge_num: int = 0
    ):
        """Generate a flag for the specified base, level and challenge."""
        if challenge_num == 0:
            challenge_num = level_num
            level_num = int(base)
            base = "Headquarters"
        if level_num == 13 and challenge_num == 1:
            content = "13.1 is a No Flag Zone™ 🙅⛔⚔️"
        else:
            # Generates random, but unique and identical per challenge, base 64 "flag"
            if random.randint(1, 5) == 5:

                return await ctx.send(
                    "The flag is: "
                    f"||{CHEATING_VIDEO}||"
                )
            else:
                content = (
                    "The flag is:"
                    f"||{await generatebase64(ord(base[0]) + level_num + challenge_num)}||"
                )

        embed = Embed(
            title=(f"{base} - Level {level_num} Challenge {challenge_num}"),
            description=content,
            colour=0x4262F4,
        )
        embed.set_author(name="Cyber Discovery", icon_url=CYBERDISC_ICON_URL)

        await ctx.send(embed=embed)

    @command()
    @has_role(ROOT_ROLE_ID)
    async def readme(
        self,
        ctx: Context,
        operand: str = "",
        channel_id: str = "",
        msg_send_interval: int = 0,
    ):
        """
        Allows generating, sending and manipulation of JSON file containing the info needed
        to create and send the embeds for the #readme channel. Only ROOT_ROLE_ID users have
        the permissions need to use this command.
        """

        operand = operand.lower()

        # The supplied operand is incorrect.
        if not (operand in README_SEND_ALIASES + README_RECV_ALIASES):
            incorrect_operand_embed = Embed(
                colour=0x673AB7,
                description=":shrug: **Invalid readme operand supplied.**",
            )
            await ctx.message.delete()
            await ctx.send(embed=incorrect_operand_embed)

        # User missed out the channel_id for certain commands.
        elif channel_id == "" and operand in README_SEND_ALIASES:
            misssing_channel_embed = Embed(
                colour=0xFF5722,
                description=":facepalm: **Whoops, you missed out the channel ID! Try again.**",
            )
            await ctx.message.delete()
            await ctx.send(embed=misssing_channel_embed)

        # Process the request.
        else:
            # Let's create a series of #readme-capable embeds. If something is uploaded,
            # It will attempt to use that file for the readme, if omitted, it will use
            # the default JSONifed readme file and send that into the channel instead.
            if operand in README_SEND_ALIASES:
                try:
                    # Much pain was had fixing this. Please, get some help and install mypy type checking.
                    channel_id: int = int(
                        channel_id[2:-1] if channel_id[0] == "<" else channel_id
                    )

                    usr_confirmation_embed = Embed(
                        colour=0x4CAF50,
                        description=":white_check_mark: **Creating readme using uploaded config file.**",
                    )

                    # The user has uploaded a config.
                    if ctx.message.attachments != []:
                        json_file_location = [_.url for _ in ctx.message.attachments][0]

                        # GETs the attachment data.
                        async with ClientSession() as session:
                            async with session.get(json_file_location) as response:
                                if response.status == 200:
                                    resp_text = await response.text()

                        json_config = load(StringIO(resp_text))
                        await ctx.send(embed=usr_confirmation_embed)

                    # No config uploaded, just use default config file.
                    else:
                        with open("cdbot/data/readme.json", "rb") as default_json:
                            json_config = load(default_json)

                        usr_confirmation_embed.description = (
                            ":ballot_box_with_check: "
                            "**Creating readme using default config file.**"
                        )
                        await ctx.send(embed=usr_confirmation_embed)

                    await ctx.message.delete()

                    for section in json_config:
                        # Initialise our message and embed variables each loop.
                        # This is to prevent leftover data from being re-sent.
                        msg_content, current_embed = None, None

                        # The part which handles general messages.
                        if "content" in json_config[section]:
                            msg_content = json_config[section]["content"]

                        # We have an embed. Call in the Seahawks.
                        if "embed" in json_config[section]:
                            current_embed = Embed()
                            msg_embed = json_config[section]["embed"]
                            if "title" in msg_embed:
                                current_embed.title = msg_embed["title"]
                            if "description" in msg_embed:
                                current_embed.description = msg_embed["description"]
                            if "color" in msg_embed:
                                current_embed.colour = Colour(
                                    int(msg_embed["color"], 16)
                                )

                            # Parse the fields, if there are any.
                            if "fields" in msg_embed:
                                for current_field in msg_embed["fields"]:
                                    # Add the fields to the current embed.
                                    current_embed.add_field(
                                        name=current_field["name"],
                                        value=current_field["value"],
                                    )

                        # Send the message.
                        requested_channel = self.bot.get_channel(channel_id)

                        if msg_content is not None and current_embed is None:
                            await requested_channel.send(content=msg_content)
                        elif current_embed is not None and msg_content is None:
                            await requested_channel.send(embed=current_embed)
                        else:
                            await requested_channel.send(
                                content=msg_content, embed=current_embed
                            )

                        # User has requested a delay between each message being sent.
                        if 0 < msg_send_interval < 901:
                            await sleep(msg_send_interval)

                    # Send the trailing embed message constant.
                    await requested_channel.send(content=END_README_MESSAGE)

                except (Exception):
                    parse_fail_embed = Embed(
                        colour=0x673AB7,
                        description=":x: **Error parsing JSON file, please ensure its valid!**",
                    )
                    await ctx.message.delete()
                    await ctx.send(embed=parse_fail_embed)

            # Pull the readme JSON constant files and slide it into the user's DMs.
            elif operand in README_RECV_ALIASES:

                # Slide it to the user's DMs.
                requesting_user = await self.bot.fetch_user(ctx.message.author.id)

                await requesting_user.send(
                    content="Hey, here's your config file!",
                    file=File(fp="cdbot/data/readme.json", filename="readme.json"),
                )

                await ctx.message.delete()
                await ctx.send(
                    embed=Embed(
                        colour=0x009688,
                        description=":airplane: **Flying in, check your DMs!**",
                    )
                )

    @command(aliases=["a", "al"])
    async def assess(self, ctx: Context, challenge_num: int):
        """
        Gets information about a specific CyberStart Assess level and challenge.
        """

        NO_HINTS_MSG = f"**:warning: Remember, other people can't give hints after challenge {HINTS_LIMIT}**"

        # Gather Assess data from JSON file.
        with open("cdbot/data/assess.json") as f:
            assess_docs = load(f)

        if not 0 < challenge_num <= len(assess_docs):
            await ctx.send("Invalid challenge number!")

        else:
            # Select the needed challenge
            challenge_raw = assess_docs[challenge_num - 1]
            challenge_title = challenge_raw["title"]
            challenge_difficulty = challenge_raw["difficulty"]
            challenge_text = challenge_raw["description"]

            if challenge_num > HINTS_LIMIT:
                challenge_text = NO_HINTS_MSG + "\n" + challenge_text

            embed = Embed(
                title=f"CyberStart Assess Challenge {challenge_num} - {challenge_title}",
                description=challenge_text,
                colour=0x4262F4,
                url=f"https://assess.joincyberdiscovery.com/challenge-{challenge_num:02d}",
            )
            embed.set_author(name="Cyber Discovery", icon_url=CYBERDISC_ICON_URL)
            embed.set_footer(text=f"Difficulty: {challenge_difficulty}")

            await ctx.send(embed=embed)

    @command()
    async def game(self, ctx: Context):
        """
        Gets the date of, and days and months until, CyberStart Game
        """

        await self.countdown("2nd June 2020", "CyberStart Game", ctx)

    @command()
    async def essentials(self, ctx: Context):
        """
        Gets the date of, and days and months until, CyberStart Essentials
        """

        await self.countdown("15th September 2020", "CyberStart Essentials", ctx)

    @command()
    async def hundred(self, ctx: Context):
        """
        Gets the number of 100% and true 100% users
        """

        game_r = ctx.guild.get_role(HUNDRED_PERCENT_ROLE_ID)
        true_r = ctx.guild.get_role(TRUE_HUNDRED_PERCENT_ROLE_ID)

        await ctx.send(
            f"There are {len(game_r.members)} that have completed CyberStart Game. Out of them, "
            f"{len(true_r.members)} have also completed Essentials and Assess."
        )

    @command()
    async def elitecount(self, ctx: Context):
        """
        Gets the number of elite users
        """
        if ELITECOUNT_ENABLED:
            preferences = {
                "2018": Roles.Elite.VET2018,
                "2019": Roles.Elite.VET2019,
                "2020": Roles.Elite.VET2020,
                "2021": Roles.Elite.VET2021,
            }

            description = textwrap.dedent(
                """
            **Camp Statistics**
            """
            )

            embed = Embed(
                title=f"CyberStart Elite {datetime.datetime.utcnow().year}",
                description=description,
                colour=Colour(0xAE444A),
            )  # A nice red

            embed.set_thumbnail(url=CYBERDISC_ICON_URL)

            for location, role in preferences.items():
                role = ctx.guild.get_role(role)
                embed.add_field(name=location, value=f"**Attendees**: {len(role.members)}", inline=True)

            await ctx.send(embed=embed)
        else:
            await ctx.send(
                ":no_entry_sign: This command is disabled because CyberStart Elite is done for this year"
            )

    async def countdown(self, countdown_target_str: str, stage_name: str, ctx: Context):
        countdown_target = parse(countdown_target_str).date()

        # Get the current date
        today = datetime.date.today()
        time_until_target = relativedelta(countdown_target, today)

        # Given a number of items, determine whether it should be pluralised.
        # Then, return the suffix of 's' if it should be, and '' if it shouldn't.
        def suffix_from_number(num):
            return "" if num == 1 else "s"

        month_or_months = "month" + suffix_from_number(time_until_target.months)
        day_or_days = "day" + suffix_from_number(time_until_target.days)

        month_countdown = f"{time_until_target.months} {month_or_months}"
        day_countdown = f"{time_until_target.days} {day_or_days}"

        # Diable the months component of the countdown when there are no months left
        if time_until_target.months:
            month_and_day_countdown = f"{month_countdown} and {day_countdown}"
        else:
            month_and_day_countdown = day_countdown

        if today > countdown_target:
            await ctx.send(f"{stage_name} has begun!")
            return
        await ctx.send(
            f"{stage_name} begins on the {countdown_target_str}.\n"
            f"That's in {month_and_day_countdown}!"
        )

    @command()
    async def support(self, ctx: Context):
        """
        Returns the support email
        """
        await ctx.send("support@joincyberdiscovery.com")

    @command()
    async def meta(self, ctx: Context):
        """
        Returns the meta link.
        """
        await ctx.send("https://github.com/CyberDiscovery/meta")

    @command()
    async def cdtos(self, ctx: Context):
        """
        Returns the Cyber Discovery terms of service.
        """
        await ctx.send("https://www.joincyberdiscovery.com/terms")

    @command()
    async def cma(self, ctx: Context, *, section: str = None):
        """
        Returns a link to the Computer Misuse Act or a screenshot of one of the first three sections.
        """
        if section is None:
            await ctx.send("https://www.legislation.gov.uk/ukpga/1990/18/contents")
        elif (CMA_URL := CMA_LINKS.get(section)) is not None:
            await ctx.send(CMA_URL)
        else:
            await ctx.send("That section is not in our database. The full Computer Misuse Act can be read at: "
                           "https://www.legislation.gov.uk/ukpga/1990/18/contents")

    @Cog.listener()
    async def on_message(self, message: Message):
        # Check the current command context
        ctx = await self.bot.get_context(message)
        # If message is a command, ignore regex responses.
        if ctx.valid or message.author.bot:
            return

        # Check if the message matches any of the pre-baked regexes
        for regex, response in self.matches:
            if regex.match(message.content):
                await message.channel.send(f"{message.author.mention}  |  {response}")
                break


async def setup(bot):
    await bot.add_cog(Cyber(bot))


================================================
FILE: cdbot/cogs/fun.py
================================================
"""
Set of bot commands designed for general leisure.
"""
import asyncio
import re
import textwrap
from io import BytesIO
from math import ceil
from random import choice, randint
from string import ascii_lowercase
from typing import List
from urllib.parse import urlencode

import asyncpg
from PIL import Image, ImageDraw, ImageFont
from aiohttp import ClientSession
from discord import (
    Colour,
    Embed,
    File,
    HTTPException,
    Message,
    NotFound,
    RawReactionActionEvent,
    embeds,
)
from discord.ext.commands import (
    Bot,
    BucketType,
    Cog,
    Context,
    UserConverter,
    command,
    cooldown,
)
from discord.utils import get

from cdbot.constants import (
    CYBERDISC_ICON_URL,
    EMOJI_LETTERS,
    FAKE_ROLE_ID,
    LOCAL_DEBUGGING,
    LOGGING_CHANNEL_ID,
    PostgreSQL,
    QUOTES_BOT_ID,
    QUOTES_CHANNEL_ID,
    QUOTES_DELETION_QUOTA,
    REACT_EMOTES,
    REACT_TRIGGERS,
    ROOT_ROLE_ID,
    STAFF_ROLE_ID,
    SUDO_ROLE_ID,
    WELCOME_BOT_ID,
    WORD_MATCH_RE,
)

ascii_lowercase += " !?$()"


def convert_emoji(message: str) -> List[str]:
    """Convert a string to a list of emojis."""
    emoji_trans = list(map(iter, EMOJI_LETTERS))
    # Enumerate characters in the message

    emojified = []

    for character in message:
        index = ascii_lowercase.find(character)
        if index == -1:
            continue
        # Yield the next iteration of the letter
        try:
            emojified.append(next(emoji_trans[index]))
        except StopIteration:
            continue

    return emojified


async def emojify(message: Message, string: str):
    """Convert a string to emojis, and add those emojis to a message."""
    for emoji in convert_emoji(string.lower()):
        if emoji is not None:
            await message.add_reaction(emoji)


class FormerUser(UserConverter):
    async def convert(self, ctx, argument):
        try:
            return await ctx.bot.fetch_user(argument)
        except (NotFound, HTTPException):
            return await super().convert(ctx, argument)


class Fun(Cog):
    """
    Commands for fun!
    """

    # Embed sent when users try to ping staff
    ping_embed = (
        Embed(
            colour=0xFF0000,
            description="⚠ **Please make sure you have taken the following into account:** ",
        )
        .set_footer(
            text="To continue with the ping, react \N{THUMBS UP SIGN}, To delete this message and move on,"
            " react \N{THUMBS DOWN SIGN}"
        )
        .add_field(
            name="Cyber Discovery staff will not provide help for challenges.",
            value="If you're looking for help, feel free to ask questions in one of our topical channels.",
        )
        .add_field(
            name="Make sure you have emailed support before pinging here.",
            value="`support@joincyberdiscovery.com` are available to answer any and all questions!",
        )
    )

    def __init__(self, bot: Bot):
        self.bot = bot
        self.staff_role = None
        self.quote_channel = None
        self.fake_staff_role = None

    async def migrate_quotes(self):
        """Create and initialise the `quotes` table with user quotes."""
        async with self.bot.pool.acquire() as connection:
            await connection.execute(
                "CREATE TABLE IF NOT EXISTS quotes (quote_id bigint PRIMARY KEY, author_id bigint)"
            )
        quote_channel = self.bot.get_channel(QUOTES_CHANNEL_ID)
        async for quote in quote_channel.history(limit=None):
            await self.add_quote_to_db(quote)
        print("Quotes successfully imported.")

    @Cog.listener()
    async def on_ready(self):
        guild = self.bot.guilds[0]

        if self.staff_role is None:
            self.staff_role = guild.get_role(STAFF_ROLE_ID)

        if self.fake_staff_role is None:
            self.fake_staff_role = guild.get_role(FAKE_ROLE_ID)

        if not LOCAL_DEBUGGING:
            self.bot.pool = await asyncpg.create_pool(
                host=PostgreSQL.PGHOST,
                port=PostgreSQL.PGPORT,
                user=PostgreSQL.PGUSER,
                password=PostgreSQL.PGPASSWORD,
                database=PostgreSQL.PGDATABASE,
            )
            await self.migrate_quotes()

    @cooldown(1, 60, BucketType.user)
    @cooldown(4, 60, BucketType.channel)
    @cooldown(6, 3600, BucketType.guild)
    @Cog.listener()
    async def on_message(self, message: Message):
        # If a new quote is added, add it to the database.
        if message.channel.id == QUOTES_CHANNEL_ID and (
            message.author.id == QUOTES_BOT_ID or message.mentions is not None
        ):
            await self.add_quote_to_db(message)
            print(f"Message #{message.id} added to database.")

        if self.fake_staff_role in message.role_mentions and not message.author.bot:
            # A user has requested to ping official staff
            sent = await message.channel.send(embed=self.ping_embed, delete_after=30)
            await sent.add_reaction("\N{THUMBS UP SIGN}")
            await sent.add_reaction("\N{THUMBS DOWN SIGN}")

            def check(reaction, user):
                """Check if the reaction was valid."""
                user_is_staff = user.top_role.id in (ROOT_ROLE_ID, SUDO_ROLE_ID)
                return all(
                    (
                        user == message.author or user_is_staff,
                        str(reaction.emoji) in "\N{THUMBS UP SIGN}\N{THUMBS DOWN SIGN}",
                    )
                )

            try:
                # Get the user's reaction
                reaction, _ = await self.bot.wait_for(
                    "reaction_add", timeout=30, check=check
                )

            except asyncio.TimeoutError:
                pass

            else:
                if str(reaction) == "\N{THUMBS UP SIGN}":
                    # The user wants to continue with the ping
                    await self.staff_role.edit(mentionable=True)
                    staff_ping = Embed(
                        title="This user has requested an official staff ping!",
                        colour=0xFF0000,
                        description=message.content,
                    ).set_author(
                        name=f"{message.author.name}#{message.author.discriminator}",
                        icon_url=message.author.avatar_url,
                    )
                    # Send the embed with the user's content
                    await message.channel.send(
                        self.staff_role.mention, embed=staff_ping
                    )
                    await self.staff_role.edit(mentionable=False)
                    # Delete the original message
                    await message.delete()

            finally:
                await sent.delete()

        ctx = await self.bot.get_context(message)

        if ctx.valid or message.author.bot:
            # Don't react to valid commands or messages from bots.
            return

        # Check if the message contains a trigger
        for trigger in REACT_TRIGGERS:
            reg = WORD_MATCH_RE.format(trigger)
            if re.search(reg, message.content, re.IGNORECASE):
                to_react = REACT_TRIGGERS[trigger]
                if to_react in REACT_EMOTES:
                    for emote in to_react.split():

                        if len(emote) > 1:  # We have a string to react with
                            await emojify(message, emote)
                        else:
                            await message.add_reaction(emote)
                elif "{mention}" in to_react:
                    to_react = to_react.replace("{mention}", message.author.mention)
                    await ctx.send(to_react)
                else:
                    await ctx.send(to_react)
                return  # Only one auto-reaction per message

        # Adds waving emoji when a new user joins.
        if all(
            (
                "Welcome to the Cyber Discovery" in message.content,
                message.author.id == WELCOME_BOT_ID,
            )
        ):
            await message.add_reaction("\N{WAVING HAND SIGN}")

    @Cog.listener()
    async def on_raw_reaction_add(self, raw_reaction: RawReactionActionEvent):
        thumbs_down = "\N{THUMBS DOWN SIGN}"
        if all(
            (
                str(raw_reaction.emoji) == thumbs_down,
                raw_reaction.channel_id == QUOTES_CHANNEL_ID,
            )
        ):
            quotes_channel = self.bot.get_channel(QUOTES_CHANNEL_ID)
            logs_channel = self.bot.get_channel(LOGGING_CHANNEL_ID)
            message = await quotes_channel.fetch_message(raw_reaction.message_id)
            reaction = [
                react for react in message.reactions if str(react.emoji) == thumbs_down
            ][0]
            if reaction.count >= QUOTES_DELETION_QUOTA:
                if not LOCAL_DEBUGGING:
                    async with self.bot.pool.acquire() as connection:
                        await connection.execute(
                            "DELETE FROM quotes WHERE quote_id = $1", reaction.message.id
                        )
                mentions = ", ".join([user.mention async for user in reaction.users()])

                embed = Embed(
                    color=Colour.blue(),
                    title="Quote Deleted"
                )
                if reaction.message.embeds:
                    quote_embed = reaction.message.embeds[-1]  # Using last item has same effect as for loop
                    embed.description = quote_embed.description
                    embed.set_author(name=quote_embed.author.name, icon_url=quote_embed.author.icon_url)
                else:  # message doesn't have an embed, MUST be from a user
                    embed.description = message.content
                    embed.set_author(name=message.author.name, icon_url=message.author.avatar_url)
                embed.add_field(name="Deleted By", value=mentions)

                await reaction.message.delete()
                await logs_channel.send(embed=embed)

    @command()
    async def lmgtfy(self, ctx: Context, *args: str):
        """
        Returns a LMGTFY URL for a given user argument.
        """
        # Creates a lmgtfy.com url for the given query.
        request_data = {
            "q": " ".join(arg for arg in args if not arg.startswith("-")),
            "ie": int("-ie" in args),
        }
        url = "https://lmgtfy.com/?" + urlencode(request_data)

        await ctx.send(url)

        if "-d" in args:
            await ctx.message.delete()

    # Ratelimit to one use per user every minute and 4 usages per minute per channel
    @command(aliases=["emojify"])
    @cooldown(1, 60, BucketType.user)
    @cooldown(4, 60, BucketType.channel)
    async def react(self, ctx, *, message: str):
        """
        Emojifies a given string, and reacts to a previous message
        with those emojis.
        """
        if ctx.channel.id == QUOTES_CHANNEL_ID:
            await ctx.send("This command is disabled in this channel!", delete_after=10)
            return
        limit, _, output = message.partition(" ")
        if limit.isdigit():
            limit = int(limit)
        else:
            output = message
            limit = 2
        async for target in ctx.channel.history(limit=limit):
            pass
        await emojify(target, output)

    @command()
    async def xkcd(self, ctx: Context, number: str = None):
        """
        Fetches xkcd comics.
        If number is left blank, automatically fetches the latest comic.
        If number is set to '?', a random comic is fetched.
        """

        # Creates endpoint URI
        if number is None or number == "?":
            endpoint = "https://xkcd.com/info.0.json"
        else:
            endpoint = f"https://xkcd.com/{number}/info.0.json"

        # Fetches JSON data from endpoint
        async with ClientSession() as session:
            async with session.get(endpoint) as response:
                data = await response.json()

        # Updates comic number
        if number == "?":
            number = randint(1, int(data["num"]))  # noqa: B311
            endpoint = f"https://xkcd.com/{number}/info.0.json"
            async with ClientSession() as session:
                async with session.get(endpoint) as response:
                    data = await response.json()
        else:
            number = data["num"]

        # Creates date object (Sorry, but I'm too tired to use datetime.)
        date = f"{data['day']}/{data['month']}/{data['year']}"

        # Creates Rich Embed, populates it with JSON data and sends it.
        comic = Embed()
        comic.title = data["safe_title"]
        comic.set_footer(text=data["alt"])
        comic.set_image(url=data["img"])
        comic.url = f"https://xkcd.com/{number}"
        comic.set_author(
            name="xkcd",
            url="https://xkcd.com/",
            icon_url="https://xkcd.com/s/0b7742.png",
        )
        comic.add_field(name="Number:", value=number)
        comic.add_field(name="Date:", value=date)
        comic.add_field(name="Explanation:", value=f"https://explainxkcd.com/{number}")

        await ctx.send(embed=comic)

    @command()
    async def quotes(self, ctx: Context, member: FormerUser = None):
        """
        Returns a random quotation from the #quotes channel.
        A user can be specified to return a random quotation from that user.
        """
        quote_channel = self.bot.get_channel(QUOTES_CHANNEL_ID)

        async with self.bot.pool.acquire() as connection:
            if member is None:
                # fetchval() returns the first result of a query.
                message_id = await connection.fetchval(
                    "SELECT quote_id FROM quotes ORDER BY random() LIMIT 1"
                )
            else:
                message_id = await connection.fetchval(
                    "SELECT quote_id FROM quotes WHERE author_id=$1 ORDER BY random() LIMIT 1",
                    member.id,
                )

        if message_id is None:
            return await ctx.send("No quotes found.")

        message = await quote_channel.fetch_message(message_id)
        embed = None
        content = message.clean_content
        attachment_urls = [attachment.url for attachment in message.attachments]

        if message.embeds:
            embed = message.embeds[0]
        elif len(attachment_urls) == 1:
            image_url = attachment_urls.pop(0)
            embed = Embed()
            embed.set_image(url=image_url)

        for url in attachment_urls:
            content += "\n" + url

        await ctx.send(content, embed=embed)

    @command()
    async def quotecount(self, ctx: Context, member: FormerUser = None):
        """
        Returns the number of quotes in the #quotes channel.
        A user can be specified to return the number of quotes from that user.
        """
        async with self.bot.pool.acquire() as connection:
            total_quotes = await connection.fetchval("SELECT count(*) FROM quotes")

            if member is None:
                await ctx.send(f"There are {total_quotes} quotes in the database")
            else:
                user_quotes = await connection.fetchval(
                    "SELECT count(*) FROM quotes WHERE author_id=$1", member.id
                )
                await ctx.send(
                    f"There are {user_quotes} quotes from {member} in the database "
                    f"({user_quotes / total_quotes:.2%})"
                )

    @command()
    async def quoteboard(self, ctx: Context, page: int = 1):
        """Show a leaderboard of users with the most quotes."""
        users = ""
        current = 1
        start_from = (page - 1) * 10

        async with self.bot.pool.acquire() as connection:
            page_count = ceil(
                await connection.fetchval(
                    "SELECT count(DISTINCT author_id) FROM quotes"
                ) / 10
            )

            if 1 > page > page_count:
                return await ctx.send(":no_entry_sign: Invalid page number")

            for result in await connection.fetch(
                "SELECT author_id, COUNT(author_id) as quote_count FROM quotes "
                "GROUP BY author_id ORDER BY quote_count DESC LIMIT 10 OFFSET $1",
                start_from,
            ):
                author, quotes = result.values()
                users += f"{start_from + current}. <@{author}> - {quotes}\n"
                current += 1

        embed = Embed(colour=Colour(0xAE444A))
        embed.add_field(name=f"Page {page}/{page_count}", value=users)
        embed.set_author(name="Quotes Leaderboard", icon_url=CYBERDISC_ICON_URL)

        await ctx.send(embed=embed)

    async def add_quote_to_db(self, quote: Message):
        """
        Adds a quote message ID to the database, and attempts to identify the author of the quote.
        """
        author_id = None
        if quote.author.id == QUOTES_BOT_ID:
            if not quote.embeds:
                return
            embed = quote.embeds[0]
            icon_url = embed.author.icon_url
            if type(icon_url) == embeds._EmptyEmbed or "twimg" in icon_url:
                author_id = QUOTES_BOT_ID
            elif "avatars" in icon_url:
                try:
                    author_id = int(icon_url.split("/")[-2])
                except ValueError:
                    author_id = 0
            else:
                author_info = embed.author.name.split("#")
                if len(author_info) == 1:
                    author_info.append("0000")
                author = get(
                    quote.guild.members,
                    name=author_info[0],
                    discriminator=author_info[1],
                )
                author_id = author.id if author is not None else None
        else:
            author_id = quote.mentions[0].id if quote.mentions else None

        if not LOCAL_DEBUGGING:
            async with self.bot.pool.acquire() as connection:
                if author_id is not None:
                    await connection.execute(
                        "INSERT INTO quotes(quote_id, author_id) VALUES($1, $2) ON CONFLICT DO NOTHING",
                        quote.id,
                        author_id,
                    )
                else:
                    await connection.execute(
                        "INSERT INTO quotes(quote_id) VALUES($1) ON CONFLICT DO NOTHING",
                        quote.id,
                    )

            print(f"Quote ID: {quote.id} has been added to the database.")

    async def create_text_image(self, ctx: Context, person: str, text: str):
        """
        Creates an image of a given person with the specified text.
        """
        if len(text) > 100:
            return await ctx.send(
                ":no_entry_sign: Your text must be shorter than 100 characters."
            )
        drawing_text = textwrap.fill(text, 20)
        font = ImageFont.truetype("cdbot/resources/Dosis-SemiBold.ttf", 150)

        text_layer = Image.new("RGBA", (1920, 1080), (0, 0, 0, 0))
        text_layer_drawing = ImageDraw.Draw(text_layer)
        text_layer_drawing.text(
            (0, 0), drawing_text, fill=(0, 0, 0), align="center", font=font
        )

        cropped_text_layer = text_layer.crop(text_layer.getbbox())
        cropped_text_layer.thumbnail((170, 110))

        image = Image.open(f"cdbot/resources/{person}SaysBlank.png")

        x = int((image.width / 5 + 20) - (cropped_text_layer.width / 2))
        y = int((image.height / 5 + 50 / 2) - (cropped_text_layer.height / 2))

        image.paste(cropped_text_layer, (x, y), cropped_text_layer)
        image_bytes = BytesIO()
        image.save(image_bytes, format="PNG")
        image_bytes.seek(0)
        await ctx.send(file=File(image_bytes, filename=f"{person}.png"))

    @command()
    async def agentj(self, ctx: Context, *, text: str):
        """
        Creates an image of Agent J with the specified text.
        """
        await self.create_text_image(ctx, "AgentJ", text)

    @command()
    async def jibhat(self, ctx: Context, *, text: str):
        """
        Creates an image of Jibhat with the specified text.
        """
        await self.create_text_image(ctx, "Jibhat", text)

    @command()
    async def agentq(self, ctx: Context, *, text: str):
        """
        Creates an image of Agent Q with the specified text.
        """
        await self.create_text_image(ctx, "AgentQ", text)

    @command()
    async def angryj(self, ctx: Context, *, text: str):
        """
        Creates an image of Angry Agent J with the specified text.
        """
        await self.create_text_image(ctx, "AngryJ", text)

    @command()
    async def angrylyne(self, ctx: Context, *, text: str):
        """
        Creates an image of Angry James Lyne with the specified text.
        """
        await self.create_text_image(ctx, "AngryLyne", text)

    @command()
    async def baldj(self, ctx: Context, *, text: str):
        """
        Creates an image of Bald Agent J with the specified text.
        """
        await self.create_text_image(ctx, "AgentJBadHairDay", text)

    @command()
    async def neveragoodtime(self, ctx: Context):
        """
        Returns the "as always it's never a good time to pop in" quote.
        """
        await ctx.send("https://cdn.discordapp.com/attachments/450107193820446722/546655387886157824/unknown.png")

    @command()
    async def tryharder(self, ctx: Context):
        """
        Returns the "Try Harder" music video.
        """
        await ctx.send("https://www.youtube.com/watch?v=t-bgRQfeW64")

    @command()
    async def hac(self, ctx: Context):
        """
        Hacks the specified user.
        """
        await ctx.send("Charging the Low Orbit Ion Canon, please stand by!")

    @command()
    async def dox(self, ctx: Context):
        """
        Doxes the specified user.
        """
        await ctx.send("OK, scraping their parent's public Facebook feed!")

    @command()
    async def theworstpunishmentwehave(self, ctx: Context):
        """
        Punishes a user.
        """
        await ctx.send("Ok, banning them from the Q&A server!")

    @command(hidden=True)
    async def beano(self, ctx: Context):
        await ctx.send("*grumbles*")

    @command()
    async def flowchart(self, ctx: Context):
        """
        Sends the image of the challenge solving flowchart.
        """
        await ctx.send("https://cdn.discordapp.com/attachments/411573884597436416/767122366521278474/trythis.png")

    @command()
    async def unacceptable(self, ctx: Context):
        """
        Deem something as unacceptable.
        """
        await ctx.send("https://media1.tenor.com/images/7a2aa50ab07e6e5d61ec7ef1a45bc64f/tenor.gif?itemid=16269937")

    @command()
    async def quotebait(self, ctx: Context):
        """
        Trick someone into quoting your message.
        """
        await ctx.send("haha boobs")

    @command(hidden=True)
    async def thot(self, ctx: Context):
        await ctx.send("https://cdn.discordapp.com/attachments/543766802174443531/777203751227621466/thot.jpg")

    @command(hidden=True)
    async def subtler(self, ctx: Context):
        await ctx.send("https://media.discordapp.net/attachments/463657120441696256/703333784073797632/unknown.png")

    @command(hidden=True)
    async def subtle(self, ctx: Context):
        await ctx.send("https://cdn.discordapp.com/attachments/463657120441696256/560247422912167949/unknown.png")

    @command()
    async def whoarethecyberists(self, ctx: Context):
        """
        Returns a video explaining who the cyberists are.
        """
        await ctx.send("https://cdn.discordapp.com/attachments/458769653481865227/687638009427787791"
                       "/WhoAreTheCyberists_1.mp4")

    @command(aliases=['jibhatisnotinvolvedwiththat'], hidden=True)
    async def jibhatisnotinvolvedinthat(self, ctx: Context):
        """
        It's time to stop.
        """
        await ctx.send("https://imgur.com/CoWZ05t")

    @command()
    async def whynotboth(self, ctx: Context):
        """
        Why not?
        """
        await ctx.send("https://giphy.com/gifs/yosub-girl-taco-why-not-both-3o85xIO33l7RlmLR4I")

    @command()
    async def simples(self, ctx: Context):
        """
        It's not that hard!
        """
        await ctx.send("https://thumbs.gfycat.com/DigitalGrandBrocketdeer-small.gif")

    @command()
    async def window(self, ctx: Context):
        """
        Returns the window gif.
        """
        await ctx.send("https://media.giphy.com/media/c6DIpCp1922KQ/giphy.gif")

    @command(hidden=True, aliases=['boogie'])
    async def dance(self, ctx: Context):
        """
        Dance Tom dance!
        """
        await ctx.send("https://cdn.discordapp.com/attachments/450107193820446722/484757289476030465/ezgif.com-video"
                       "-to-gif3.gif")

    @command()
    async def thisisfine(self, ctx: Context):
        """
        There is nothing wrong here.
        """
        await ctx.send("https://media.giphy.com/media/z9AUvhAEiXOqA/giphy.gif")

    @command()
    async def inout(self, ctx: Context):
        """
        Returns the inout gif.
        """
        await ctx.send("https://media.giphy.com/media/11gC4odpiRKuha/giphy.gif")

    @command(hidden=True)
    async def zucc(self, ctx: Context):
        await ctx.send("https://tenor.com/view/zuck-zuckerberg-drink-drinks-water-gif-11631267")

    @command(hidden=True)
    async def succ(self, ctx: Context):
        await ctx.send("https://tenor.com/view/alex-jones-crying-silly-info-wars-gif-7295428")

    @command(hidden=True)
    async def shhh(self, ctx: Context):
        await ctx.send("Just ordered this to help: http://webiconspng.com/wp-content/uploads/2017/09/Shovel-PNG-Image"
                       "-95986.png")

    @command()
    async def suppressdissent(self, ctx: Context):
        """
        Suppress someone.
        """
        comments = ["It will be done my lord", "I guess we'll try the Trump approach this time",
                    "I'll get the CIA on the phone then", "Give me a minute to reread 1984",
                    "Let me call Theresa for ideas"]
        await ctx.send(choice(comments))

    @command(hidden=True)
    async def murder(self, ctx: Context):
        await ctx.send("rm -rf / --no-preserve-root")


async def setup(bot):
    """
    Required boilerplate for adding functionality of cog to bot.
    """
    await bot.add_cog(Fun(bot))


================================================
FILE: cdbot/cogs/general.py
================================================
import os

from discord.ext import commands
from discord.ext.commands import Bot, Cog, Context, command
from git import Repo

from cdbot.constants import WELCOME_CHANNEL_ID, WELCOME_MESSAGE

path = os.path.dirname(os.path.abspath(__file__))
path = "/".join(path.split("/")[:-2])

repo = Repo(path)
latest = repo.commit()


class General(Cog):
    """
    General Purpose Commands
    """

    def __init__(self, bot: Bot):
        self.bot = bot

    @Cog.listener()
    async def on_ready(self):
        print("Logged in as")
        print(self.bot.user.name)
        print(self.bot.user.id)
        print("------")
        message, *_ = latest.message.partition("\n")
        link = f"{next(repo.remote().urls)}/commit/{latest}"
        date = latest.authored_datetime.strftime("**%x** at **%X**")
        self.bot.log.info(
            "CyberDiscovery bot is now logged in.\n"
            f"Latest commit: **[{message}]({link})**"
            f"\nAuthor: **{latest.author}** on {date}"
        )

    @Cog.listener()
    async def on_member_join(self, member):
        join_msg_channel = self.bot.get_channel(WELCOME_CHANNEL_ID)
        join_msg = await join_msg_channel.send(f"{member.mention}, {WELCOME_MESSAGE}")
        await join_msg.add_reaction('👋')

    @Cog.listener()
    async def on_member_remove(self, member):
        if member not in [ban.user for ban in await member.guild.bans()]:
            leave_msg_channel = self.bot.get_channel(WELCOME_CHANNEL_ID)
            await leave_msg_channel.send(f"**{member}** just left **Cyber Discovery**. Bye bye **{member}**...")

    @Cog.listener()
    async def on_member_ban(self, guild, user):
        ban_msg_channel = self.bot.get_channel(WELCOME_CHANNEL_ID)
        await ban_msg_channel.send(f"**{user}** was banned...")

    @Cog.listener()
    async def on_command_error(self, ctx, error):
        # Try provide some user feedback instead of logging all errors.

        if isinstance(error, commands.CommandNotFound):
            return  # No need to log unfound commands anywhere or return feedback

        if isinstance(error, commands.MissingRequiredArgument):
            # Missing arguments are likely human error so do not need logging
            parameter_name = error.param.name
            return await ctx.send(
                f"\N{NO ENTRY SIGN} Required argument {parameter_name} was missing"
            )
        elif isinstance(error, commands.CheckFailure):
            return await ctx.send(
                "\N{NO ENTRY SIGN} You do not have permission to use that command"
            )
        elif isinstance(error, commands.CommandOnCooldown):
            retry_after = round(error.retry_after)
            return await ctx.send(
                f"\N{HOURGLASS} Command is on cooldown, try again after {retry_after} seconds"
            )

        # All errors below this need reporting and so do not return

        if isinstance(error, commands.ArgumentParsingError):
            # Provide feedback & report error
            await ctx.send(
                "\N{NO ENTRY SIGN} An issue occurred while attempting to parse an argument"
            )
        elif isinstance(error, commands.BadArgument):
            await ctx.send("\N{NO ENTRY SIGN} Conversion of an argument failed")
        else:
            await ctx.send(
                "\N{NO ENTRY SIGN} An error occured during execution, the error has been reported."
            )

        extra_context = {
            "discord_info": {
                "Channel": ctx.channel.mention,
                "User": ctx.author.mention,
                "Command": ctx.message.content,
            }
        }

        if ctx.guild is not None:
            # We are NOT in a DM
            extra_context["discord_info"]["Message"] = (
                f"[{ctx.message.id}](https://discordapp.com/channels/"
                f"{ctx.guild.id}/{ctx.channel.id}/{ctx.message.id})"
            )
        else:
            extra_context["discord_info"]["Message"] = f"{ctx.message.id} (DM)"

        self.bot.log.exception(error, extra=extra_context)

    @command()
    async def bbcnews(self, ctx: Context):
        """
        Returns a link to BBC News.
        """
        await ctx.send("https://www.bbc.co.uk/iplayer/live/bbcnews")

    @command()
    async def skynews(self, ctx: Context):
        """
        Returns a link to Sky News.
        """
        await ctx.send("https://www.youtube.com/watch?v=9Auq9mYxFEE")

    @command()
    async def tos(self, ctx: Context):
        """
        Returns a link to discord's terms of service.
        """
        await ctx.send("https://www.discord.com/terms")


async def setup(bot):
    await bot.add_cog(General(bot))


================================================
FILE: cdbot/cogs/help.py
================================================
from discord import Colour, Embed
from discord.ext import commands


class EmbeddedHelpCommand(commands.HelpCommand):
    def __init__(self):
        super().__init__(command_attrs={'help': 'Gives detailed information about a command.'})

    async def command_callback(self, ctx, *, command=None):
        if command is not None:
            for cog in ctx.bot.cogs:
                if str(command).casefold() == cog.casefold():
                    command = cog
                    break
        return await super().command_callback(ctx, command=command)

    async def send_bot_help(self, mapping):
        ctx = self.context
        embed = Embed(
            color=Colour.blue(),
            description="Type ``:help [command]`` for more info on a command.\n"
                        "You can also type "
                        "``:help [category]`` for more info on a category."
        )
        for cog in mapping.keys():
            if cog is not None:
                if cog.get_commands():
                    embed.add_field(name=cog.qualified_name,
                                    value=f"``:help {cog.qualified_name.lower()}``")

        dm = await ctx.author.send(embed=embed)
        if ctx.guild is not None:
            embed = Embed(
                description=f"[**Jump to DM**]({dm.jump_url})"
            )
            await ctx.send(f"{ctx.author.mention} help info sent to DMs", delete_after=10, embed=embed)
            await ctx.message.delete()

    async def send_cog_help(self, cog):
        ctx = self.context
        embed = Embed(
            color=Colour.blue(),
            title=cog.description,
            description="Type ``:help [command]`` for more info on a command.\n You can also type "
                        "``:help [category]`` for more info on a different category."
        )
        for command in cog.get_commands():
            if command.hidden is not True:
                embed.add_field(name=f"``:{command.name}``", value=command.help)

        dm = await ctx.author.send(embed=embed)
        if ctx.guild is not None:
            embed = Embed(
                description=f"[**Jump to DM**]({dm.jump_url})"
            )
            await ctx.send(f"{ctx.author.mention} help info sent to DMs", delete_after=10, embed=embed)
            await ctx.message.delete()

    async def send_command_help(self, command):
        ctx = self.context
        if command.hidden is not True:
            embed = Embed(
                color=Colour.blue(),
                title=f"``:{command.name}``",
                description=command.help
            )
            embed.add_field(name="Usage",
                            value=f"``:{command.name} {command.signature}``",
                            inline=False)
            if command.aliases:
                embed.add_field(name="Aliases",
                                value=", ".join(f'``:{alias}``' for alias in command.aliases),
                                inline=False)

            await ctx.send(embed=embed)


class Help(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.bot._original_help_command = bot.help_command
        bot.help_command = EmbeddedHelpCommand()
        bot.help_command.cog = self

    def cog_unload(self):
        self.bot.help_command = self.bot._original_help_command


async def setup(bot):
    await bot.add_cog(Help(bot))


================================================
FILE: cdbot/cogs/maths.py
================================================
"""
Set of bot commands designed for Maths Challenges.
"""
import asyncio
from io import BytesIO

import aiohttp
from PIL import Image
from discord import Colour, Embed, File, Member, Message, Reaction
from discord.ext.commands import Bot, Cog, Context, command

from cdbot.constants import Maths as constants


class Maths(Cog):
    """Maths-related commands."""

    def __init__(self, bot: Bot):
        self.bot = bot

    @Cog.listener()
    async def on_message(self, message):
        """Check if the message contains inline LaTeX."""
        if constants.LATEX_RE.findall(message.content):
            await self.latex_render(
                message.channel, message.channel.id, message, message.content
            )

    @command()
    async def latex(self, ctx: Context, *, expression: str):
        """
        Render a LaTeX expression with https://quicklatex.com/
        """
        await self.latex_render(ctx, ctx.channel.id, ctx.message, expression)

    async def latex_render(
        self, ctx: Context, channel: int, message: Message, expression: str
    ):

        if channel in constants.BLOCKED_CHANNELS:
            return await ctx.send(
                "\N{NO ENTRY SIGN} You cannot use this command in this channel!",
                delete_after=10,
            )

        # Code and regexes taken from https://quicklatex.com/js/quicklatex.js
        # aiohttp seems to URL-encode things in a way quicklatex doesn't like

        if expression.startswith("...latex") or expression.startswith(":latex"):
            return

        formula = expression.replace("%", "%25").replace("&", "%26")

        preamble = constants.LATEX_PREAMBLE.replace("%", "%25").replace("&", "%26")

        body = "formula=" + formula
        body = body + "$$$$&fsize=50px"
        body = body + "&fcolor=ffffff"
        body = body + "&mode=0"
        body = body + "&out=1"
        body = body + "&errors=1"
        body = body + "&preamble=" + preamble

        border_width = 20

        async with aiohttp.ClientSession() as session:
            async with session.post(
                "https://www.quicklatex.com/latex3.f", data=body
            ) as response:
                result = await response.text()
            m = constants.LATEX_RESPONSE_RE.match(result)
            if not m:
                return
            status, url, valign, imgw, imgh, errmsg = m.groups()
            if status == "0":
                async with session.get(url) as response:
                    content = await response.content.read()
                img = Image.open(BytesIO(content))
                alpha = img.convert("RGBA").split()[-1]
                image = Image.new(
                    "RGB",
                    (img.size[0] + 2 * border_width, img.size[1] + 2 * border_width),
                    "#36393F",
                )
                image.paste(img, (border_width, border_width), mask=alpha)
                image_bytes = BytesIO()
                image.save(image_bytes, format="PNG")
                image_bytes.seek(0)

                # send the resulting image and add a bin reaction
                rendered_message = await ctx.send(
                    file=File(image_bytes, filename="result.png")
                )
                await rendered_message.add_reaction("\N{WASTEBASKET}")

                # checks if the person who reacted was the original latex author and that they reacted with a bin
                def should_delete(reaction: Reaction, user: Member):
                    return all(
                        (
                            message.author == user,
                            reaction.emoji == "\N{WASTEBASKET}",
                            reaction.message.id == rendered_message.id,
                        )
                    )

                # if the latex author reacts with a bin within 30 secs of sending, delete the rendered image
                # otherwise delete the bin reaction
                try:
                    await self.bot.wait_for(
                        "reaction_add", check=should_delete, timeout=30
                    )
                except asyncio.TimeoutError:
                    await rendered_message.remove_reaction(
                        "\N{WASTEBASKET}", self.bot.user
                    )
                else:
                    await rendered_message.delete()

            else:
                embed = Embed(
                    title="\N{WARNING SIGN} **LaTeX Compile Error** \N{WARNING SIGN}",
                    colour=Colour(0xB33A3A),
                    description=errmsg.replace("@", ""),
                )
                return await ctx.send(embed=embed, delete_after=30)


async def setup(bot):
    """
    Required boilerplate for adding functionality of cog to bot.
    """
    await bot.add_cog(Maths(bot))


================================================
FILE: cdbot/constants.py
================================================
import base64
import re
from os import environ

DEPLOY = bool(environ.get("DEPLOY"))


def getenv(name: str, fallback: str = "") -> str:
    """Return an (optionally base64-encoded) env var."""
    variable = environ.get(name)
    if DEPLOY and variable is not None:
        variable = base64.b64decode(variable).decode()
    return variable or fallback


class PostgreSQL:
    PGHOST = getenv("PGHOST")
    PGPORT = getenv("PGPORT")
    PGUSER = getenv("PGUSER")
    PGDATABASE = getenv("PGDATABASE")
    PGPASSWORD = getenv("PGPASSWORD")


class Maths:
    LATEX_RE = re.compile(r"\${1,2}(.*?)\${1,2}", re.DOTALL)
    LATEX_RESPONSE_RE = re.compile(r"^([-]?\d+)\r?\n?(\S+)\s([-]?\d+)\s(\d+)\s(\d+)\r?\n?([\s\S]*)")

    LATEX_PREAMBLE = (
        "\\usepackage{amsmath}\n"
        "\\usepackage{amssymb}\n"
        "\\usepackage{amsthm}\n"
        "\\usepackage{amsfonts}\n"
        "\\usepackage{mathtools}\n"
        "\\usepackage{stmaryrd}\n"
        "\\usepackage[utf8]{inputenc}\n"
        "\\usepackage{longtable}\n"
        "\n"
        "\\usepackage{graphicx}\n"
        "\\usepackage{subcaption}\n"
        "\\usepackage{caption}\n"
        "\n"
        "\\usepackage{booktabs}\n"
        "\\usepackage[separate-uncertainty]{siunitx}\n"
        "\\usepackage[version=4]{mhchem}\n"
        "\\usepackage{mathabx}\n"
        "\n"
        "\\newtheorem{theorem}{Theorem}[section]\n"
        "\\newtheorem{corollary}{Corollary}[theorem]\n"
        "\\newtheorem{procedure}{Procedure}[section]\n"
        "\\newtheorem{lemma}[theorem]{Lemma}\n"
        "\n"
        "\\theoremstyle{remark}\n"
        "\\newtheorem*{remark}{Remark}\n"
        "\n"
        "\\theoremstyle{definition}\n"
        "\\newtheorem{definition}{Definition}[section]")

    BLOCKED_CHANNELS = [411573884597436416]

    class Challenges:
        URL = "https://cms-kcl.cloud.contensis.com/api/delivery/projects/mathsSchool/entries/search?linkDepth=1"
        CHALLENGE_RE = re.compile(r"Challenge (\d+): .*")
        TOPIC = "Nerds, the lot of you | {0}"
        TOPIC_RE = re.compile(r"Challenge (\d+)")
        TOKEN = getenv("MATHS_TOKEN")
        CHANNEL = int(environ.get("MATHS_CHANNEL", "457923834893434881"))


class Roles:
    class Elite:
        VET2018 = int(environ.get("2018_MEMBERS_ID", "453581429528461313"))
        VET2019 = int(environ.get("2019_MEMBERS_ID", "580387468336037888"))
        VET2020 = int(environ.get("2020_MEMBERS_ID", "715852962664153168"))
        VET2021 = int(environ.get("2021_MEMBERS_ID", "844211211553603654"))


# Cyber Constants
BOT_TOKEN = getenv("BOT_TOKEN")
SENTRY_URL = getenv("SENTRY_URL")

# Fun constants
QUOTES_DELETION_QUOTA = int(environ.get("QUOTES_DELETION_QUOTA", "5"))
QUOTES_CHANNEL_ID = int(environ.get("QUOTES_CHANNEL_ID", "463657120441696256"))
QUOTES_BOT_ID = 292953664492929025
LOGGING_CHANNEL_ID = int(environ.get("LOGGING_CHANNEL_ID", "538494690601992212"))
WELCOME_BOT_ID = 155149108183695360
CMA_LINKS = {"1": "https://cdn.discordapp.com/attachments/450107193820446722/492649412560945164/unknown.png",
             "2": "https://cdn.discordapp.com/attachments/450107193820446722/492649644623659014/unknown.png",
             "3": "https://cdn.discordapp.com/attachments/450107193820446722/492649912035573770/unknown.png",
             "3a": "https://cdn.discordapp.com/attachments/450107193820446722/492650366454857737/unknown.png",
             "3za": "https://cdn.discordapp.com/attachments/450107193820446722/492650170656489472/unknown.png",
             }
REACT_EMOTES = ["\N{ONCOMING POLICE CAR}", "\N{DUCK}", "\U0001f645 \N{NO ENTRY} \N{CROSSED SWORDS}"]
REACT_TRIGGERS = {"kali": REACT_EMOTES[0], "duck": REACT_EMOTES[1], "cybergame": "*CyberStart Game",
                  "cyberstart access": "*CyberStart Assess", "cyberessentials": "*CyberStart Essentials",
                  "cyberdiscovery game": "*CyberStart Game", "cyberdiscovery access": "*CyberStart Assess",
                  "13.1": REACT_EMOTES[2], "bill gates": "Alan Turing?", "alan turing": "Bill Gates?",
                  "sibelius": "https://i.imgur.com/PwgGWV8.png?1", "we are number one": "HEY!",
                  "I can break these cuffs": "{mention}, you can't break those cuffs!",
                  "ancestry.com": "https://i.imgur.com/DDqugBj.png"}
WORD_MATCH_RE = r"^.*\b{}\b.*$"

# General constants
WELCOME_MESSAGE = ("Welcome to the Cyber Discovery discussion discord! Before you begin, please check the "
                   "rules, roles and information in <#409853512185282561> to answer any questions.")
WELCOME_CHANNEL_ID = int(environ.get("WELCOME_CHANNEL_ID", "411573884597436416"))

# Misc roles
HUNDRED_PERCENT_ROLE_ID = 640481360766697482
TRUE_HUNDRED_PERCENT_ROLE_ID = 640481628292120576

# Lists for administration
STAFF_ROLE_ID = 450063890362138624
FAKE_ROLE_ID = 533826912712130580
STATIC_NICKNAME_ROLE_ID = 567259415393075210
CD_BOT_ROLE_ID = 543768819844251658
ADMIN_MENTOR_ROLE_ID = 502238208747110411
ROOT_ROLE_ID = int(environ.get("ROOT_MEMBERS_ID", "450113490590629888"))
SUDO_ROLE_ID = int(environ.get("SUDO_MEMBERS_ID", "450113682542952451"))
ADMIN_ROLES = ("Root", "Sudo")
BANNED_DOMAINS = ["discord.gg", "discord.com"]

HINTS_LIMIT = 8
CYBERDISC_ICON_URL = (
    "https://pbs.twimg.com/profile_images/921313066515615745/fLEl2Gfa_400x400.jpg"
)
ELITECOUNT_ENABLED = True

LOCAL_DEBUGGING = bool(environ.get("LOCAL_DEBUGGING", False))

# Readme command constants
README_SEND_ALIASES = ["create", "push", "generate", "send", "make", "build", "upload"]
README_RECV_ALIASES = ["fetch", "get", "pull", "download", "retrieve", "dm", "dl"]

END_README_MESSAGE = (
    "**Can't see any of the above?**\nIf you can't see any of the rich embeds above, try the"
    " following: `Settings -> Text & Images -> Link Preview (Show website preview info from"
    " links pasted into that chat)  -> ON`"
)

BASE_ALIASES = {
    "Intern": ["intern", "i"],
    "Headquarters": ["headquarters", "main", "hq", "h"],
    "Moon": ["moonbase", "python", "moon", "m"],
    "Forensics": ["forensics", "f"],
    "Volcano": ["volcano", "v", "volc"],
}

# Admin Constants
PLACEHOLDER_NICKNAME = "Valued server member"
NICKNAME_PATTERNS = [
    r"(discord\.gg/|discord\.com/invite/)",  # invite links
    r"(nigg|ligma|fag|nazi|hitler|\bpaki\b)",  # banned words
    r"(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)",  # hyperlinks
]

# Emoji Alphabet
EMOJI_LETTERS = [
    "\U0001f1e6\U0001f170\U0001F359",  # A
    "\U0001f1e7\U0001f171",  # B
    "\U0001f1e8\u262a\u00A9",  # C
    "\U0001f1e9\u21a9",  # D
    "\U0001f1ea\U0001f4e7",  # E
    "\U0001f1eb",  # F
    "\U0001f1ec\u26fd",  # G
    "\U0001f1ed\u2653",  # H
    "\U0001f1ee\u2139",  # I
    "\U0001f1ef\u2614",  # J
    "\U0001f1f0",  # K
    "\U0001f1f1\U0001f552\U0001F462",  # L
    "\U0001f1f2\u24c2\u24c2\u264f\u264d\u303d",  # M
    "\U0001f1f3\U0001f4c8\U0001F3B5",  # N
    "\U0001f1f4\U0001f17e\u2b55",  # O
    "\U0001f1f5\U0001f17f",  # P
    "\U0001f1f6",  # Q
    "\U0001f1f7",  # R
    "\U0001f1f8\U0001f4b0\u26a1\U0001F4B2",  # S
    "\U0001f1f9\u271d\U0001F334",  # T
    "\U0001f1fa\u26ce",  # U
    "\U0001f1fb\u2648",  # V
    "\U0001f1fc\u3030",  # W
    "\U0001f1fd\u274e\u274c\u2716",  # X
    "\U0001f1fe\U0001f331\u270C",  # Y
    "\U0001f1ff\U0001f4a4",  # Z
    "\u26ab\U0001f535\U0001f534\u26aa",  # Whitespace alternatives
    "\u2755\u2757\u2763",  # !
    "\u2754\u2753",  # ?
    "\U0001f4b2",  # $
    "\U000021aa",  # (
    "\U000021a9",  # )
]

CHEATING_VIDEO = "https://cdn.discordapp.com/attachments/409860647170342919/922204571575660614/cheating_message.mp4"


================================================
FILE: cdbot/data/assess.json
================================================
[{
    "title": "You spin me right round",
    "description": "One of our agents who has a love for vinyl has come across a rather unique record with a very odd looking album name. The band are known for their use of cryptic lyrics. Can you help figure out what decrypted album title is?",
    "difficulty": "Easy"
}, {
    "title": "Movie time",
    "description": "Have you ever hidden something so well to only forget where you put it? One of our agents has done exactly this and can't remember where they have hidden the code needed to log in to their online streaming account. Help the agent find the code so they can start watching their playlist of movies.",
    "difficulty": "Easy"
}, {
    "title": "On close inspection",
    "description": "The agency has been looking to purchase fidget spinners online for all of its employees. Whilst browsing around, we came across a website that sells the perfect spinner but we're curious to find out what all of the reviews say and not all of them are viewable. Can you help us uncover what the final reviewer is saying?",
    "difficulty": "Easy"
}, {
    "title": "Bon voyage?",
    "description": "We've received intel that a lone hacker has somehow managed to sign into a staff portal at a major tour operator. The hacker seems to be helping themselves to staff discounts on flights all around the world. Can you help us find out how they are doing it before they book their next flight?",
    "difficulty": "Easy"
}, {
    "title": "One smart cookie",
    "description": "One of our agents fancies themselves as a master baker and recently purchased a 3D printer to print a custom set of cookie cutters. The trouble is, the software used for the 3D printer seems to be malfunctioning and doesn't print out the shapes in their entirety. Can you find out what is going wrong so we can request a batch of cookies for the office?",
    "difficulty": "Medium"
}, {
    "title": "Level up",
    "description": "One of the internets largest online retailers of retro games has asked for our help. It has come to light that a handful of users have been able to download games that are still on pre-release and should not yet be available for public download. We've gone ahead and purchased a few games, one of which is on pre-release. Can you figure out how we might download it so we can help fix this flaw? The flag is the filename of the download.",
    "difficulty": "Medium"
}, {
    "title": "Night at the museum",
    "description": "The Museum of History has gotten in touch with the agency in regards to some odd-looking text discovered under a carving of Julius Caesar. Historians have told us that part of the text is written in ancient Latin but not all of it. Agent, can you help the museum decipher the text and uncover the mystery?",
    "difficulty": "Medium"
}, {
    "title": "610enC0de'd Password",
    "description": "Our agents have found a server ran by black hat hackers they've been chasing for a while now. The hackers hide their illegal activities by encoding everything they do to cover their tracks ...we know we've got the username right but don't have the password and so can't login, can you help, agent?",
    "difficulty": "Medium"
}, {
    "title": "Missing in action",
    "description": "We've got an emergency here agent! A hacker has gained access to a very old system of ours with some pretty poor code and made it even worse by breaking the output. Agents are securing the server but can you get it showing location data again? The hackers left some malware in place which only allows a single character change, else it resets. We need to see all agent locations ASAP, please help agent!",
    "difficulty": "Medium"
}, {
    "title": "The thrill of the race",
    "description": "Three-time winner of the Go-Kart Grand Prix, Max Pitstop has been maliciously locked out of his changing room locker during a championship race by a rival driver! He's desperately in need of some lunch before the next session. We're lucky you are at the race, agent! Help Max get access to his locker before the next session begins!",
    "difficulty": "Hard"
}, {
    "title": "LOL sandwich",
    "description": "A package containing ripped up pieces of paper with nothing but the letters 'L' and 'O' written on them has been received by the agency. A handful of recent graduates have tried to figure out what the code might mean but are having no luck in doing so. We need someone with a knowledge of older encryption methods to help, could this be you, agent?",
    "difficulty": "Hard"
}, {
    "title": "Beep boop beep",
    "description": "Agent, we've located a factory in an abandoned industrial complex that has been taken over by robots! Intelligence suggests that they are using the factory to manufacture more robots to create an army and take over the world! We've got an agent at the scene trying to gain access to the factory floor via an entry panel but they're having no luck! We need you to assist them and crack the code before the robots take over!",
    "difficulty": "Hard"
}, {
    "title": "Watt's up?",
    "description": "We've got an emergency on our hands! The National Power Network have called us in to help fix a problem affecting a group of power plants in the south east of the country. Their main dashboard of operations is not receiving the correct information for each of the power plants which could result in a huge black-out across the region! Agent, we need you to figure out what's going on and fix it quick!",
    "difficulty": "Extreme"
}, {
    "title": "A test of intelligence",
    "description": "We've got some thrilling news to share with you, agent! Archaeologists in Egypt have uncovered what appears to be a gate capable of interstellar transportation! You can imagine how excited the archaeologists were to try it out only to find that there is a test needed to pass before they can step through it and visit new worlds. There are teams all over the world working on how to pass this 'test of intelligence' and are yet to figure it out. This is a tough one agent, and we're sending you, our best to crack this puzzle! We believe in you!",
    "difficulty": "Extreme"
}]


================================================
FILE: cdbot/data/game.json
================================================
{
  "Intern": [
    [
      {
        "title": "Hello World",
        "description": "We've found a profile page of a known hacker that we need to get in contact with but most of the information visible on the page is useless to us. However, there is one secret real way to contact him that he's managed to inject on the page - a hidden email address. Intern, we need you to find his email address!",
        "tips": [
          "Tip: The email address is the flag."
        ]
      },
      {
        "title": "Mixed Up Messages",
        "description": "We've been informed that a group of hackers are communicating cryptically on a social media site.\n\nTheir messages have been getting increasingly cryptic; so much so that all of the posts now look jumbled up and don't seem to make any sense!\n\nWe've found one post that we believe contains an important secret, and we need you to find out what that secret is so we can be ready to stop a possible cyber attack!",
        "tips": [
          "Tip: The secret is the flag."
        ]
      },
      {
        "title": "Social Engineering",
        "description": "Permission has been granted to try and log into the Chirp social media account of a hacker who goes by the name of D4YDR3AM. Luckily for us. they've been clumsy with their personal information. We know their dog's name is Barkley and they were born in 1993. Can you use what we know about them to guess their password and get us into their account?",
        "tips": [
          "Tip: Get the flag by guessing the correct password to sign into the account."
        ]
      },
      {
        "title": "Lazy Locked Login",
        "description": "Our Dutch office recently bought a new Internet of Things (IoT) connected fridge. However, the temperature settings have been widely fluctuating as of late. All agents are currently out in the field and too busy to fix the problem.\n\nWe know there is a remotely accessible technician's page where fridge settings can be modified, and that the fridge's login page isn't very secure. It was easy enough to find the username and password, but the form still has some very lazy extra protection. Intern, can you see if the rumours are true, fix our fridge, and help us verify this reported security vulnerability?",
        "tips": [
          "Tip: Successfully login to get the flag."
        ]
      }
    ],
    [
      {
        "title": "610enC0de'd Password",
        "description": "Agents believe they have found a server belonging to a gang called the Yakoottees. If we can get access to it who knows what information we can gather on them! So far the Yakoottees have been very successful hiding their activities by encoding everything they do. We've found their server but don't have the password and so can't login. Can you help, intern?",
        "tips": [
          "Tip: Login to the server to get the flag."
        ]
      },
      {
        "title": "Spinlock",
        "description": "A large bank has refitted all of their vaults with the new SpinLock Extreme. As fancy as it sounds we believe it has a rather critical vulnerability, one we think the Yakoottees have been exploiting in a series of recent bank robberies.\n\nThe physical vault itself requires a special keycard to be inserted which, after checking the authenticity of the card, re-aligns the circular locking mechanism to unlock it and updates the interface to show it's unlocked. However, we believe that the organisation has been remote accessing the interface on the vault, and unlocking the vault by doing it in reverse: getting the interface to unlock, which unlocks the physical vault itself. If we can confirm the method, we'll be one step closer to understanding how this cyber gang operates!",
        "tips": [
          "Tip: Unlock the vault to get the flag."
        ]
      },
      {
        "title": "Traffic Jam",
        "description": "Can you believe it, we think the Yakoottees are now planning to disrupt the flow of traffic in a major city! We need to find the URL of a forum they're using to communicate with each other. Can you figure out what the URL is?",
        "tips": [
          "Tip: Find the url of the forum to get the flag."
        ]
      },
      {
        "title": "Start-Up Troubles",
        "description": "A successful new start-up that sells electric scooters has discovered a handful of their customers' accounts have been hacked! And guess who we believe might be behind it? Yep, you've guessed it, the Yakoottees. Having only just entered the market and keen to maintain their otherwise excellent reputation, this business needs our help to run a security audit of their login system. Can you spot any security holes?",
        "tips": [
          "Tip: Successfully login to get the flag."
        ]
      }
    ],
    [
      {
        "title": "Hextraordinary",
        "description": "Intern, the agency have been in contact with a rival hacker to a new, secretive gang of hackers who goes by the name \"ROXy\". She specializes in short, cryptic, hard to decipher secret codes.",
        "tips": [
          "Tip: The flag is the secret code."
        ]
      },
      {
        "title": "Maths at Light Speed",
        "description": "Intern, I hope you know how to use a calculator? Of course you do. So, in theory you should be able to bypass a security gateway to a warehouse we believe holds clues to the whereabouts of a gang we are in hot pursuit of. The thing is, the gateway was created by someone who loves doing everything super fast! That means you only get 0.1 seconds to answer the question asked by the gateway. Can you find a way around it?",
        "tips": [
          "Tip: Bypass the calculator lock to get the flag."
        ]
      },
      {
        "title": "Off Balance",
        "description": "We're hot on the heels of catching this cyber gang but the closer we get the more damage they try to inflict onto the Barcelona tourism industry! This time, they've hacked into a large international bank's mobile application. Customers of the bank are complaining they can't see their current balance. Intern, help customers retrieve their balances so they can continue to spend their money during their well-earned holidays!",
        "tips": []
      },
      {
        "title": "The Final Countdown",
        "description": "The main tourism website for Barcelona has been hacked. They've devised a program that changes the content of the website based on a timer. You can imagine the confusion this has been causing the sites visitors! Can you figure out how we can get the secret code to stop this program from running?",
        "tips": [
          "Tip: The characters at the 5 URLs change quickly, but computers can be far quicker than humans, especially when getting data!"
        ]
      }
    ]
  ],
  "Headquarters": [
    [
      {
        "title": "Social Secret",
        "description": "Agent, we think we might have a way to log into a social media account of one of the gang using the site's password reset form. We have two key pieces of information: his username and his favourite color. We just need his age and we'll be able to log in - can you guess it from the information we already have?",
        "tips": [
          "Tip: Get the age correct and submit the form to get the flag."
        ]
      },
      {
        "title": "Broken Banks",
        "description": "Agent, it looks like the gang are trying to raise some money to fund their plans - and they're doing so by sending fake banking emails to the customers of their bike shop to try and defraud them. Take a look at this email we intercepted, it's seemingly from a well known bank, but can you spot anything suspicious with it?",
        "tips": [
          "Tip: Click on the part of the suspicious email that makes you think it's fake and you'll get the flag."
        ]
      },
      {
        "title": "Happy Customers",
        "description": "During our initial analysis of the gang we noticed something strange - they created lists of their customers and shared it between their bike shops, and each customer had a unique pattern of numbers after their name. One of our agents thinks the pattern of numbers might be encoded words. We also noticed the numbers never went above 26, that might be relevant. Can you work out what it means?",
        "tips": [
          "Tip: Decode the message to get the flag."
        ]
      },
      {
        "title": "Race To Where",
        "description": "Here's an interesting one for you agent - on the weekend the gang released what looks like a one page website advertising an upcoming bike race they're organising. Being cyber detectives we're naturally curious that things might not be as they seem. Have a look at the site - it looks like there are just two images on the page, a logo and the poster, can you find anything suspicious, maybe some text?",
        "tips": [
          "Tip: Find the hidden clue and it'll lead you to the flag."
        ]
      },
      {
        "title": "Mixed Messages",
        "description": "Our team have been monitoring some text message chatter between two of the senior gang members over the last week. There was nothing interesting of note until suddenly all their messages started coming through mixed up.\n\nAt first we thought it might be a technical problem, but then it occurred to us that they might be deliberately encoding the messages somehow.\n\nI've had one of the agents forward you one of the messages, see if you can decode it.",
        "tips": [
          "Tip: Decode the message, we think it mentions two colors, combine them (e.g. redgreen) and that's the flag."
        ]
      },
      {
        "title": "Executable",
        "description": "Agent, quick question for you. One of the team sent me a screenshot of his filesystem browser with a bunch of files she recovered from a memory stick one of the gang discarded. She's asking if any of them are executable files. Take a look and let me know.",
        "tips": [
          "Tip: The name of the executable file (including the extension) is the flag."
        ]
      },
      {
        "title": "Secret Caesar",
        "description": "Our agents have been reviewing some documents they recovered from one of The Slootmaekers bike shops and they came across an order form for a set of six bikes which looks a little odd.\n\nWe think the six bike reference numbers might be encrypted with a Caesar Cipher (which is where the letters are shifted along by a certain offset, e.g. if the letter is A and the offset is 2 it will become C), and we've got some intel that one of them contains a code that starts with the word \"BAR\". We've worked out that the offset for each code is determined by the order quantity. See if you can use that to break the encryption and find the hidden code.",
        "tips": [
          "Tip: The decrypted reference number containing the word BAR is the flag."
        ]
      },
      {
        "title": "Hidden Report",
        "description": "One of the ways we've been investigating the gang and their bike shop is by actually buying products from them online. We've spotted a piece of functionality that we think might be exploitable, allowing us to get more information on other customers (some of which we think might actually be helping them with their plans to steal the eBikes).\n\nHave a look at our account page on the site, it allows you to generate a report of your transactions. See if you can find a way to view a transaction report for the very first user of the site.",
        "tips": [
          "Tip: Find the report for the first user of the site and you'll get the flag."
        ]
      },
      {
        "title": "Bike Fan",
        "description": "Quick task for you agent, one of the gang, Lars De Vries, has a site where he posts pictures of bikes he likes. Recently we've noticed some of the images are broken and he hasn't fixed it. Seems odd. Take a look at this page and see if you can get the image to show, perhaps take a look at the source code of the page and see if anything doesn't look right.",
        "tips": [
          "Tip: Find a way to get the image to show and you'll find the flag."
        ]
      },
      {
        "title": "Horrible Hats",
        "description": "One of our agents has noticed something a little strange that she thought you might be able to look into. The Slootmaekers have a cycle hat creator tool on their website which allows you to select a combination of up to eight colors to go into a custom hat design. The tool itself didn't seem strange until we noticed the gang were sending each other links to hat color combinations they'd created on a regular basis that were a bit dull and had no other comment. It seems a little odd and we think they might be using it to hide secret messages.\n\nWe've managed to get hold of an example output page which we think contains a secret code word the gang are using in other communications. If we could figure out that word it would really help us discover what their bigger plans are.",
        "tips": [
          "Tip: See if you can convert the color RGB numbers into letters using the included ASCII table to get the flag."
        ]
      },
      {
        "title": "Tower of Wheels",
        "description": "So, this is strange - our undercover agent noticed that one of the gang has left a set of bike wheels set up in his workshop like a Tower of Hanoi puzzle. We think it's wired up to his computer and might reveal something if our agent can complete the puzzle.\n\nWe've mocked up the puzzle for you to try using JavaScript. You can move the wheels by entering the following command: move(<src>, <dst>), where the <src> or <dst> values can be \"left\", \"mid\", or \"right\". For example, to move a wheel from the middle lane to the left lane you need to type: move(\"mid\", \"left\"). If you get stuck type \"reset\" to start again.",
        "tips": [
          "Tip: Complete the puzzle to get the flag."
        ]
      },
      {
        "title": "Binary Bike Lock",
        "description": "Well, this gang loves their technology and bikes so much that they've combined them to make a custom bike lock!\n\nWe're concerned they're going to use this lock technology as part of the bigger bike heist, so it's worth us taking the time to try and figure out how to crack them now.\n\nTake a look at this lock and see if you can unlock it.",
        "tips": [
          "Tip: Unlock the bike lock to get the flag."
        ]
      }
    ],
    [
      {
        "title": "Decryption Ring",
        "description": "Things are really starting to get busy - the Slootmaekers seem to be in the final stages of preparing the heist, we need to move quickly! We've just received a message back from one of our undercover agents with details of where they're planning to start their heist. It's encrypted with a Caesar Cipher, but we can't remember what he said he was going to use as the offset and can't seem to get hold of him again. We've put the message into our Caesar Cipher decryption tool, see if you can use it to decrypt the text and read the message.",
        "tips": [
          "Tip: Decrypt the message by dragging the green slider, the flag is the name of the bike manufacturer."
        ]
      },
      {
        "title": "Loopy Login",
        "description": "Quick piece of analysis for you to do agent. We think there is something suspicious about the login form on the Slootmaekers website. Can you see what it is?",
        "tips": [
          "Tip: Click on the suspicious thing to get the flag."
        ]
      },
      {
        "title": "Secret Pages",
        "description": "You're now well aware of the Slootmaekers supposedly legit business, the Bike Factory. Evidence is mounting up that the business, and especially its website, have quite a few things to hide. We've just had a tip-off that there's a secret page on the website with some interesting information about when the gang plan to attempt the eBike heist. Problem is, it seems to be restricted. Can you get access?",
        "tips": [
          "Tip: Get access to the page to get the flag."
        ]
      },
      {
        "title": "Too Much Text",
        "description": "Ok, this is really weird, one of the gang just emailed every other gang member a HUGE text file with loads of random Latin text in it. There must be a secret code hidden in it, right?",
        "tips": [
          "Tip: Find the secret code, that's the flag."
        ]
      },
      {
        "title": "More Broken Bicycles",
        "description": "Remember Lars cycling fan site from the last floor? Well, we thought we'd figured out what the issue was with the broken images on the site, but on this page that we've just come across all the images already have file extensions, but one is still broken. Can you see what's wrong?",
        "tips": [
          "Tip: Get the image to show to get the flag."
        ]
      },
      {
        "title": "Broken Click",
        "description": "Here's a weird one agent, there's now a link on the Slootmaekers login page for an admin login. We wanted to take a look but the link doesn't seem to work. Can you figure out how to get to it?",
        "tips": [
          "Tip: Get to the admin login page to get the flag."
        ]
      },
      {
        "title": "Hard Hash",
        "description": "Agent D from our cryptography department has just sent me this hash she intercepted from an exchange between two Slootmaeker gang members. She thought it might be a good task for a smart agent like yourself to figure out.",
        "tips": [
          "Tip: Maybe someone has already figured out what this is a hash of. Sometimes the work of the CPA starts with an internet search."
        ]
      },
      {
        "title": "Encrypted Message",
        "description": "We have an undercover agent in one of the Slootmaekers bike shops, posing as a bike mechanic. We need to send him an urgent message but the only phone he has is one the gang gave him and may be monitoring. We have a specific Substitution Cipher we can use - quickly encode the message and send it.",
        "tips": [
          "Tip: Correctly encrypt the message and send it to get the flag."
        ]
      },
      {
        "title": "Fingerprints",
        "description": "We've found a secret lock-up used by the gang, a big breakthrough! One small problem, it uses an electronic keypad and requires a four digit pin.\n\nWe've noticed four of the numbers have fingerprints on and we've discovered that the lock only allows you to use each number once (i.e. you can't do 7777). Can you use that information to guess the right code? There are still quite a few possible combinations so you'll need to be systematic about it.",
        "tips": [
          "Tip: Get the right code to get the flag."
        ]
      },
      {
        "title": "Useful Hack",
        "description": "Agent, we really need to log in to the Slootmaekers site as one of the gang - it looks like it has functionality for them to exchange messages about their heist plans. Time is running out and we need to know what they're saying.\n\nWe don't have a username and password combination for any of the gang members, but it just occurred to one of our agents that a big popular dutch social media site called Gellukkig was recently hacked and a bunch of username and password combinations posted online. There's a small chance one of those was one of the gang members and they've re-used the details for the login on the site.",
        "tips": [
          "Tip: Login to the site as a gang member to get the flag."
        ]
      },
      {
        "title": "Under Attack",
        "description": "It looks like part of the heist is underway, they're trying to hack the Amsterdam City Bike Initiative servers as we speak! We need to help them out, see if you can change the server configuration settings to make the server login rules more secure.",
        "tips": [
          "Tip: Change the settings correctly to get the flag."
        ]
      },
      {
        "title": "User Who",
        "description": "Urgent request - one of our agents is currently at a Slootmaeker hideout and we know the rest of the gang are on their way back there. He's found a computer they've been using and managed to gain access to their Linux server through the use of an exploit - but we don't know which user he's been able to log in as, and we need to know before he runs out of time. We've patched you in to his screen - see if you can find out.",
        "tips": [
          "Tip: Get the username, that's the flag."
        ]
      }
    ],
    [
      {
        "title": "A Secret Rendezvous",
        "description": "Yesterday we intercepted some code passed between two Chopper gang members - Ibert and Dante. We believe it mentions the location of a secret meeting to discuss plans to hack into their competitors' computers.\n\nFortunately, we know it uses the Columnar Transposition Cipher and have a CPA tool to help you crack it. We've loaded the code into the tool. Give it a try and see if you can decrypt the message.",
        "tips": [
          "Tip: The flag is the city they will be meeting in."
        ]
      },
      {
        "title": "Lucky Throw",
        "description": "Whilst taking a look around The Choppers' intranet site, one of our agents found a game that gang members like to play to pass a little time. We need you to find out how the game is won so we can better assess the capabilities of gang members problem solving skills.",
        "tips": [
          "Tip: If you figure it out and you're persistent enough, you'll see the flag!"
        ]
      },
      {
        "title": "Chopper Check-in",
        "description": "For this assignment we need your help keeping an eye on the gang. One of our agents wants to remotely access their computers but only when they are not around to see.\n\nWe've discovered that today is one of the gang member's birthday party but we don't know where it is. If we knew we could send an agent along to check they were all there.\n\nOne of the gang, Sannfred, uses a location check-in site called Circle. Perhaps you could take a look at his profile to see where the party is?",
        "tips": [
          "Tip: The challenge flag is the number of the building."
        ]
      },
      {
        "title": "Confused",
        "description": "Recruit, we've intercepted some chatter between Chopper gang members talking about one of their competitor's machines. They all have unique identifiers and we think one of those identifiers is hidden in some code we found. One of the other agents has emailed it to you. Take a look and see if you can find the identifier.",
        "tips": [
          "Tip: The identifier is the flag you need."
        ]
      },
      {
        "title": "Secret Source",
        "description": "It looks like the gang has a lot of information we might find useful on their intranet; probably details of how they intend to break into their competitors' computer systems to look for the plans to their better felling machine. It would be especially useful if we could log in as their main developer, Aksel.\n\nSometimes developers can be a little bit lazy and leave information in the source code of a page that might be revealing. Perhaps you could take a look and see if there's anything Aksel left that might help you log in.",
        "tips": [
          "Tip: To get the challenge flag you need to successfully login."
        ]
      },
      {
        "title": "Corrupted",
        "description": "An agent in the field managed to get access to an XML file that he believes belonged to one of The Chopper gang members. It looks corrupted so he's sent it to us for analysis.\n\nHave a look in the file and see if you can get it working.",
        "tips": [
          "Tip: The flag to complete the challenge is on the last line of the file."
        ]
      },
      {
        "title": "Route into the Router",
        "description": "Good news, we've managed to get access to the login page for one of the gang's home routers. Useful... if only we knew the username and password. I wonder if they've been lazy and left the router's default username and password active? The router is just a standard Nethub 5000.",
        "tips": [
          "Tip: Find the correct username and password then login, to get the flag."
        ]
      },
      {
        "title": "Password Postulation",
        "description": "One of our agents was recently able to get hold of a username and password that one of The Chopper gang uses to access their company intranet. However, before he was able to use it to log in they implemented a change in their password policy.\n\nThe old password is \"evergreen\", and we've heard the new password policy requires a capital letter and number. Perhaps you could guess what he changed it to and help us get access?",
        "tips": [
          "Tip: To get the challenge flag you need to successfully login."
        ]
      },
      {
        "title": "Snake Charmer",
        "description": "We've just intercepted an email from one of the gang members with a file attached called \"passgen.py\". It looks like a Python script.\n\nWe've heard that the gang is looking for a way to make their passwords more secure, perhaps using code like this to generate them. See if you can run the code so we know exactly what it does.",
        "tips": [
          "Tip: The output from the code is the flag you need."
        ]
      },
      {
        "title": "The Competitor Revealed",
        "description": "We've intercepted some encrypted text from one of the gang members, which we think might include the name of the competitor company that they intend to target.\n\nLuckily, we also know it uses a letter Substitution Cipher and CPA has our very own tool for breaking this kind of encryption. Use it to try and work out what the text says.",
        "tips": [
          "Tip: The flag is the name of the competitor company."
        ]
      },
      {
        "title": "Big Transfer",
        "description": "Agent, we've just intercepted a message between two of The Choppers talking about how they intend to steal money from the bank account of a different competitor to fund their plans.\n\nIt said they've found a weakness in the money transfer tool on the Global Bank website. We've just successfully put through a test transfer, can you prove it's vulnerable by transferring 1000 to a bank account called cpatestreceiver from cpatestsender.",
        "tips": [
          "Tip: Sometimes URLs can be manipulated to bypass security."
        ]
      },
      {
        "title": "Dante In Command?",
        "description": "We found a file in an intercepted email from one of The Chopper gang members, Dante, but it needs to be run from the command line in order to make it work.\n\nWe think it might be a program the gang are writing to gain access to their competitors' computers, so it's important we check it out.",
        "tips": [
          "Tip: Run the program from the command line; the flag will be in the output."
        ]
      }
    ],
    [
      {
        "title": "A Helping Hand",
        "description": "This morning I received a message from my counterpart in another agency. It seems they've been monitoring The Choppers too.\n\nHelpfully they've sent us over an extract from a report one of their agents wrote, which they thought might be of use to us. Unhelpfully it's a PDF and much of the sensitive information has been redacted. Maybe you could find a way to see what it says.",
        "tips": [
          "Tip: We think it contains one of the gang's passwords - that's the flag."
        ]
      },
      {
        "title": "Arnold Chopper?",
        "description": "A few days ago one of our field agents, who is now working undercover for The Choppers, found two pages of a book ripped out and left on a table.\n\nWe weren't sure if it meant anything, but then today we intercepted an email containing some cryptic code. It looks like the gang is getting smarter with its encrypted messages and is now using Arnold Ciphers.\n\nSee if you can use the intercepted code and the two pages of the book to discover what it means.",
        "tips": [
          "Tip: We believe the message contains a code, that's the flag."
        ]
      },
      {
        "title": "Photobomb",
        "description": "Our team have been monitoring The Choppers' web traffic and it seems they've been spending a lot of time on a photo-sharing site, especially one particular page. Did you know you can hide a message inside a file? Maybe this image contains a hidden message. Why don't you take a look and see if you can find out?",
        "tips": [
          "Tip: The flag is in the image."
        ]
      },
      {
        "title": "Upgrades? Paaa!",
        "description": "It looks like The Choppers know they need to improve their security set-up, as recently we've been unable to log in to their intranet and they've been making a few changes to their security.\nThey have rewritten their login form to use slightly improved JavaScript, but it doesn't seem to be much more secure. Maybe you could change some of the JavaScript on the login page to see if you can log us back in.\nTip: Log in successfully to get the flag.\n\nThey have rewritten their login form to use slightly improved JavaScript, but it doesn't seem to be much more secure. Maybe you could change some of the JavaScript on the login page to see if you can log us back in.\nTip: Log in successfully to get the flag.",
        "tips": [
          "Tip: Log in successfully to get the flag.\n"
        ]
      },
      {
        "title": "A Dangerous Contact",
        "description": "During our research into The Choppers, we noticed that their website has a contact page with a form for getting in touch. Our agents want to know whether it might be vulnerable to SQL injection.\n\nSee if you can put the SQL code in the right order. If you do, the data should appear in the console at the top of the page. This will contain the information we're looking for - a list of all previously submitted messages.",
        "tips": [
          "Tip: The flag is the last name of the most recent person to send a message through the form."
        ]
      },
      {
        "title": "Maggie's File",
        "description": "Our field agent has managed to get some login details from one of the gang, Magdalina. We believe she uses the details to SSH into a remote server to view a particular file that might be important.\n\nThese are the details we have:\n\nIP address: 192.162.132.199\nUsername: maggie\nPassword: ubersecurepw\n\nWhy don't you try it yourself using the secure terminal we've provided and see what you find.",
        "tips": [
          "Tip: The terminal uses non-default implementation of OpenSSH and only accepts common formats. The flag is the name of the file."
        ]
      },
      {
        "title": "Bendikke Loves Axes",
        "description": "Whilst doing some background research on one of the gang members, Bendikke, we discovered that she writes a blog all about axes. We suspect she might be using the backend of the blogging tool to store information about how they plan to attack their competitors and, more importantly, the specific machinery blueprints they are after. If we knew that, it would really help us foil the gang's plans.\n\nSee if you can find the login page on the blog and use some of the techniques you've learned already to break in.",
        "tips": [
          "Tip: If you successfully log in you'll get the flag!"
        ]
      },
      {
        "title": "Phishing For Flemming",
        "description": "In our background research on one of the gang members, Flemming, we discovered their profile page on Ansikt, a Norwegian social media site.\n\nLuckily he's written all the details in English. We thought we might use them to create a phishing email and send it to him to try and get some further information, like his home address.\n\nTake a look at the profile, then create the phishing email with the right details, hit \"Send email\", and see if he sends anything back.",
        "tips": [
          "Tip: Get all the details right, otherwise you can't send the email. When you do, you'll get the flag."
        ]
      },
      {
        "title": "Lockdown",
        "description": "It looks like The Choppers might know we're trying to get into their intranet.\n\nThey've currently got it locked down by disabling the login form. We still have a correct username and password - can you find a way to get in?\n\nTip: Log in successfully and you'll get the flag.",
        "tips": [
          "Tip: Log in successfully and you'll get the flag."
        ]
      },
      {
        "title": "Lost But Not Forgotten",
        "description": "We've recovered a bunch of .gif files from an old hard drive one of the gang members threw out. We thought they were innocent enough until we started wondering whether they were all actually GIF files.\n\nCheck your email, download the files and see if you can find out what's going on.",
        "tips": [
          "Tip: The flag is in one of the files."
        ]
      },
      {
        "title": "Cookie Jar",
        "description": "Now that we've gained access to The Choppers' intranet site it has dawned on us that they might have more than one level of user. If we could log in as an admin user it might allow us to see much more.\n\nSometimes you can change the session cookie to achieve that, and admins often have an ID of 0. Why don't you give it a try?",
        "tips": [
          "Tip: Use the browser extension to help modify the cookie. If you're able to change your access to admin level the code will appear."
        ]
      },
      {
        "title": "Bad Kitty",
        "description": "For weeks now The Chopper gang members have been passing animated gifs of kittens backwards and forwards in a chat room app called KittyLitter. We thought they were just messing around, until one of our agents noticed something interesting in one of the GIFs.\n\nTake a look at the gifs in this message thread and see if you can figure out what it is. Oh, by the way, one of the agents mentioned there are websites and online tools to help you investigate fast-moving GIF files.",
        "tips": [
          "Tip: The flag is what you discover!"
        ]
      }
    ],
    [
      {
        "title": "Perplexed By Pixels",
        "description": "The Yakoottees love their cars. And so you'd expect that they might send each other car photos, images and even art. But recently we intercepted something strange: a pixel drawing of a car. What's strange about it? One of the gang, Kanako, sent two copies of it to another gang member. After some initial analysis we noticed some slight variations in colour in a selection of pixels.\n\nOne of the team has already built a comparison tool but he's just been reassigned to another urgent project. Perhaps you could take a look at what he's built and finish the analysis.",
        "tips": [
          "Tip: Calculate the differences, convert it with the ASCII table, and that's your flag."
        ]
      },
      {
        "title": "Word On The Street",
        "description": "We've been monitoring the Yakoottees for a while now, ever since we first came across one of their team trying to hack into large car manufacturers. Two of the gang members have recently started sending each other emails containing what we thought were zip files. But here's the thing, the emails don't contain any words and we can't seem to open the files. Why don't you take a look?",
        "tips": [
          "Tip: Open the file to get the flag."
        ]
      },
      {
        "title": "Shinji's Drone",
        "description": "Yesterday we spotted a Yakoottees gang member checking out the Superspin HQ, seemingly on a reconnaissance mission. He was seen flying a drone around the building with a video camera attached to it and we've since discovered that he put that video up on his personal site to share with other gang members. We know his username is \"shinji\" - if we could get in and take a look at the video, we might know better where they intend to target.\n\nCheck out the login page on his site and see if you can break in.",
        "tips": [
          "Tip: Log in successfully to get the flag."
        ]
      },
      {
        "title": "Out Of Sight",
        "description": "Well, that's a bit of luck. We've managed to get our hands on some details that allowed us to SSH into one of the Yakoottees private servers. They're a sophisticated gang, so they've probably hidden a few files in there. We've got you into their server, have a look and see if you can find anything.",
        "tips": [
          "Tip: The flag is in a file somewhere on the server."
        ]
      },
      {
        "title": "Zapped",
        "description": "The gang have been using a lesser-known chat app called ChatZap to communicate. We know one of the usernames (kanako) but not the password. If we could get access we might be able to see what they're saying.\n\nIt's not a great site and the security is average at best. Look at the login page - they do SHA1 hash their passwords but leave them in plain sight in the source code. See if you can use that to get in.",
        "tips": [
          "Tip: Gain access to get the flag."
        ]
      },
      {
        "title": "File Found",
        "description": "Whilst monitoring one of the gang, Ryosuke, we came across a file that he posted online. The problem is, the file doesn't seem to be opening. Take a look and see if you can work it out.",
        "tips": [
          "Tip: The flag is in the file."
        ]
      },
      {
        "title": "Shopping For Secrets",
        "description": "An agent on Level 05 has told us about another big hack he's working on. Apparently someone broke into a popular shopping site, stole all the usernames and passwords and was going to post them online. Luckily, we got to them first and recovered the details. Why is this important? Well, it seems one of the Yakoottees was a member of that site.\n\nHe typically uses one of these three usernames: kazuya, kaz_whizz, kazuya99. We've put the recovered data on one of our servers. We've given you access, so see if you can find him on there. If we knew the password he uses maybe we can use that later.",
        "tips": [
          "Tip: The flag is his password."
        ]
      },
      {
        "title": "Rattlesnake",
        "description": "Well, one thing is for sure, the Yakoottees' scripting language of choice is Python. We've intercepted a lot of Python files from all over the place. So many, in fact, that we need someone to look at them and see what they do. Can have a look at this one to find out what it does?",
        "tips": [
          "Tip: The flag is in the file."
        ]
      },
      {
        "title": "Yakoo Cars",
        "description": "The Yakoottees aren't just a criminal gang, they're also a group of talented car engineers. Their organisation has a front - a high-end car repair shop called Yakoo Cars.\n\nNaturally, we took a thorough look through their website to see if we could find anything interesting. One thing we found was a little strange: an empty page. Empty, apart from a message saying \"Error. Cookie `user` expected.\". Ordinarily we wouldn't think anything of it, but the page name is \"Secret\", so we should investigate.\n\nWe think only the boss Yaka Matsu should have access. Take a look and see if you can get around the cookie issue.",
        "tips": [
          "Tip: Use the browser extension to help add a new cookie."
        ]
      },
      {
        "title": "T-Shirt Trivia",
        "description": "The Yakoottees have a number of side businesses that they use to launder money. One of them is an online T-shirt business. We've been monitoring the site to look for unusual activity and yesterday a T-shirt went up for sale and then ten minutes later was taken down. Luckily we've been running a tool, which takes copies of all new pages before they get removed.\n\nThere's something odd about this page. The T-shirt for sale doesn't seem to be designed like the rest (which mostly feature cars); this one has what looks like a QR code on it. Take a look and see what you think.",
        "tips": [
          "Tip: Read the code to get the flag."
        ]
      },
      {
        "title": "Baffled By Browsers",
        "description": "Whilst monitoring the gang's web traffic, we noticed they've been visiting one particular page quite a lot. When we try we get rejected immediately without it even asking for a username or password.\n\nOur field agent, who is working for the Yakoottees undercover, thinks it might be something to do with a particular browser that the gang built themselves called \"Orion\". Time for you to investigate. Try looking for a developer tool in the browser to help you out.",
        "tips": [
          "Tip: To get the flag, get access to the page!"
        ]
      },
      {
        "title": "Dangerous Comment",
        "description": "One of the Yakoottees, Haruka, recently created a site where she posts photos of her favourite cars. We think it might be a front to store files containing their plans to break into the Superspin factory. Each page has a form on it where you can post comments. Try and use the form on this particular page to use command injection and find any files hidden in the same directory. Remember, to inject code you need to include two commands by including a semi-colon.",
        "tips": [
          "Tip: One of the files has the flag in it."
        ]
      }
    ],
    [
      {
        "title": "Rezapped",
        "description": "Remember ChatZap from a previous challenge? We need to get back in but it looks like they've changed their security. Take a look.",
        "tips": [
          "Tip: Log back in to get the flag."
        ]
      },
      {
        "title": "Truck Stop",
        "description": "Recruit, we're sending you into the field for the first time! We think you're ready, so don't let us down.\n\nHere's the situation. We know a group of the Yakoottees are heading to a nearby depot to steal a truck, which they are going to use to transport the supercar prototype without anyone seeing. We want to get in to the depot first and wait for them. However, we can't get hold of the depot's owners and the front gate uses a sophisticated locking system.\n\nWe know the Yakoottees have the code already, we need to crack it fast!",
        "tips": [
          "Tip: Crack the code for the flag."
        ]
      },
      {
        "title": "Meeting Momoko",
        "description": "Our field agents decided to make a move and pick up one of the Yakoottees, Momoko. We believe she was in charge of picking up some equipment that the gang needed to break in.\n\nUnfortunately, when they got there she'd gone. But they did find a post-it note with a cryptic code on it. On reviewing the surveillance tapes, it seems someone stuck it on her door a few minutes before she left. We think it might be a meet-up location. Can you crack the code for us?",
        "tips": [
          "Tip: Crack the code, it's the flag!\n            "
        ]
      },
      {
        "title": "Lost Key",
        "description": "Our agent on the ground noticed a USB key discarded outside The Yakoottees' garage and he's just uploaded the files for analysis. It's probably nothing important; they just look like innocent jpg files. One looks corrupt, however. Can you take a look just to be sure we don't miss anything?",
        "tips": [
          "Tip: The flag is in the file."
        ]
      },
      {
        "title": "Running Man",
        "description": "The Yakoottees are preparing their break-in, so we're doing a full review of everything they have running. We found a service running on the server services.cyberprotection.agency, port 8203. Connect to the service and find what is on there to see if it's related.",
        "tips": [
          "Tip: Connect and look at the banner for the flag."
        ]
      },
      {
        "title": "Heroka's DB",
        "description": "In the previous level you may have been part of the investigation into Heroka. She recently created a site where she posts photos of her favourite cars. That investigation revealed that the site was vulnerable to injection and that she's been hiding details about their plans there.\n\nWe think that some results from a search of the site are also hidden unless you're logged in. We don't have any login details, so perhaps you could use SQL injection to get all the results. Think about which SQL query is run when you submit the search form.",
        "tips": [
          "Tip: Get the full results and then you'll get the flag!"
        ]
      },
      {
        "title": "Hush Fund",
        "description": "It looks like the Yakoottees are preparing for an attack and the senior members of the gang seem to be providing the funds. We've intercepted a message containing what we think are bank details, but they're encrypted. If you could decrypt them, we'd have a better idea of who is behind the plan.",
        "tips": [
          "Tip: The decrypted bank account number is the flag."
        ]
      },
      {
        "title": "Junya Who?",
        "description": "Recruit, I need a favour. We've just had some intel about one of the gang members. His first name is Junya, but we can't find his surname. We know we have it on record, but it could be in any one of a huge set of files. Perhaps you could SSH into our server and see if you can find his surname for me?",
        "tips": [
          "Tip: Find \"Junya\" mentioned in the files and his surname will follow. That's the flag."
        ]
      },
      {
        "title": "QR.gif",
        "description": "Remember the QR code that the Yakoottees printed on a T-shirt? You might have been assigned to investigate it on the previous level. Well, it looks like they're using QR codes again and they've gone one step further by embedding them in a GIF file. We think the combined output is important, so get straight on it recruit.",
        "tips": [
          "Tip: Work out the QR codes and put them together for the flag."
        ]
      },
      {
        "title": "Custom Plates",
        "description": "We've just had some new intel. The Yakoottees are planning to change the supercar's numberplates to British plates when they steal it. For some reason, rather than create their own, they've ordered some from a site online called CoolPlates. We want to see which plates they've ordered and where they're being sent.\n\nSee if you can get into the site so we can find out. We don't have a username and password but we think it uses some simple JavaScript security checks.",
        "tips": [
          "Tip: Log in to get the flag."
        ]
      },
      {
        "title": "Still Hiding",
        "description": "Were you involved in the previous level when we needed someone to SSH into one of the Yakoottees' servers and take a look around? Well, we need to do it again. It seems they're becoming a little smarter about hiding their files. Take a look and see what you can find.",
        "tips": [
          "Tip: The flag is in a file somewhere on the server."
        ]
      },
      {
        "title": "Brute Strength",
        "description": "Great news, recruit. Thanks to your work we've been able to recover a zip file from the Yakoottees' private server. We're looking for blueprints of the Superspin HQ and details of how they plan to get in. Hopefully, the zip contents will lead us to that.\n\nThe zip file is password protected, so we set one of our engineers on writing a script to brute force it. Unfortunately, he was called away onto a special project for a team on another level. Perhaps you could make the final few alterations to his script to get it working.",
        "tips": [
          "Tip: Change the script and open up the zip file to get the flag. Oh, and don't forget you can use your local terminal to help."
        ]
      }
    ],
    [
      {
        "title": "Dot Dot Dash",
        "description": "It's time to go old-school, recruit.\n\nWe think the Chiquitoos have an insider that works on the ships carrying the shipping containers full of Cola. Obviously they don't want to be found out while passing details to the gang members on the nearby reconnaissance boats, so they've resorted to using Morse code to communicate. We've tapped into the frequency they're using and funnelled the code into our Morse code tool. Decipher the message and see what they're saying.",
        "tips": [
          "Tip: In the message they mention a ship name; that's the flag."
        ]
      },
      {
        "title": "Hidden Boats",
        "description": "Those Chiquitoos are a busy gang and they run a number of legitimate and criminal operations. One of their legitimate businesses is a small boating company that transports workers to the offshore wind farm. Their website has a page, which lists all the scheduled trips out, but we think there might be more that they're not making public. Our inside informant tells us they're stored in a file called \"extra.txt\". Have a look at the site and see if you can get access to it.",
        "tips": [
          "Tip: The flag is the boat number of the last one in the secret list."
        ]
      },
      {
        "title": "Docking Port",
        "description": "We have come across a site, which we believe is run by one of the Chiquitoo gang members, that sends information out on the port 1337. We've got a terminal tool you can use that is restricted to do just what you need. See if you can use it to get the information so we can find out what the site is being used for.",
        "tips": [
          "Tip: Get the information to get the flag."
        ]
      },
      {
        "title": "Pedro's Password",
        "description": "Recruit, we've had access to a server used by one of the Chiquitoos' side businesses for a while and have an idea that one of the gang members, Pedro, might have moved his user account from an old version of Linux. SSH in and see if you can find his hashed password.",
        "tips": [
          "Tip: The flag is in the same file as the password."
        ]
      },
      {
        "title": "Quick Drop",
        "description": "Recruit, we need your help urgently.\n\nThere's a package drop between two gang members about to happen; we have agents following them both. The first gang member has dropped off a briefcase at a discreet location and has left. We have an agent following the other one and he's just 4 minutes away from the pick up point.\nOur first agent has intercepted the package, it's a briefcase with two four digit number combinations required to get in. We have no idea what the unlock number is but the case has a post-it stuck to it with an encrypted code written on it.\nCan you decipher it in time to get the number combination? We've patched you in to the instant chat with the field agent - tell him the code before the time elapses.\nYou only get one chance per day to complete this timed challenge. Reload the page to try again.\nTip: If you get the right code, the agent will give you the flag.\n\nThere's a package drop between two gang members about to happen; we have agents following them both. The first gang member has dropped off a briefcase at a discreet location and has left. We have an agent following the other one and he's just 4 minutes away from the pick up point.\n\nOur first agent has intercepted the package, it's a briefcase with two four digit number combinations required to get in. We have no idea what the unlock number is but the case has a post-it stuck to it with an encrypted code written on it.\n\nCan you decipher it in time to get the number combination? We've patched you in to the instant chat with the field agent - tell him the code before the time elapses.\n\nYou only get one chance per day to complete this timed challenge. Reload the page to try again.",
        "tips": [
          "Tip: If you get the right code, the agent will give you the flag."
        ]
      },
      {
        "title": "Miguel the Moneyman",
        "description": "Recruit, we think we know where the money for this Chiquitoo operation is coming from: one of their senior gang members, Miguel. And it's given us an idea.\n\nTo make the payments to gang members look normal, Miguel has asked them all to send him links to official looking PDF invoices, which we know he is opening on his computer and then paying. So our plan is to send him a malicious PDF disguised as an invoice from one of the gang members. But we need your help serving up the file to Miguel.\n\nCreate a web server with the terminal using Python, then finish off the email we've created by inserting the web server's location.",
        "tips": [
          "Tip: Serve the file up correctly to get the flag. Make your web server listen on port 8000 to be sure to bypass the firewall."
        ]
      },
      {
        "title": "Developer Disaster",
        "description": "Recruit, quick job for you. We've been using the shipping company website to keep an eye on which ships have been coming in and out of port. However, it looks like their developer pushed an update to the site, which has broken it and then he went home for the day!\n\nThere's an error message on the site. See if you can find a way around it to get us back in.",
        "tips": [
          "Tip: Get back in to get the flag."
        ]
      },
      {
        "title": "Breaking Bottles",
        "description": "Recruit, we think we now know how the Chiquitoos' plan to distribute the Cola they steal. One of the gang's supposedly legitimate businesses is actually a bottling plant... and one of the things they bottle is Cola. We think they might be planning to re-label the bottles they steal as another brand they already bottle, making a huge mark-up and meaning the stolen Cola will effectively just disappear! Clever.\n\nWe think we might have a way in. The bottling plant has a website and there is a form where you can request a price list for their bottling services. We think it might be vulnerable to command injection, and there's a text file we believe you could access with \"recipe\" in the file name, which actually includes details about their plan. But here's the issue - they've added a filter to the form, which prevents the use of semi-colons. See if you can find a way around it.",
        "tips": [
          "Tip: Open the file to get the flag."
        ]
      },
      {
        "title": "Bash the Botnet",
        "description": "We have managed to gain access to a server used by one of The Chiquittoos' side businesses. We think the gang use it to connect to their botnet, which we want to take down. The botnet is of course password protected but, if you can find the password we can log in and disable it!\n\nLog into the server using these credentials and see if you can find the password.",
        "tips": [
          "Tip: The password is the flag."
        ]
      },
      {
        "title": "All Zipped Up",
        "description": "Recruit, we need your scripting skills again. One of our field agents has managed to turn one of the Chiquitoos' to act as an informant. He has obtained access to a zip file from one of the senior gang members but it's password protected and he doesn't know the password (although we're pretty sure it starts with \"Cola\"). We've got a Python script that might help us brute force it but we think it needs some modifications.",
        "tips": [
          "Tip: Open the zip file to get the flag. And don't forget you can use your local terminal to help."
        ]
      },
      {
        "title": "Secret Spreadsheet",
        "description": "Recruit, we've stumbled upon something. We think it might really help in getting us closer to the Chiquitoos' plan to steal the shipment of Cola. They've created a spreadsheet listing all the containers they intend to target. Luckily for us, they've posted it online for other gang members to see and update. But, of course, being the clever cyber criminals they are, they've put it behind a particularly clever password system.\n\nIf you visit the page where it exists the ciphered password is there but it changes every two seconds; too fast for us to decipher and use. One of our engineers has been working on a Python script to try and get around it. Have a look at the script and see if you can make some changes to get it working.",
        "tips": [
          "Tip: Run the correct script to get the flag."
        ]
      },
      {
        "title": "Password Pickle",
        "description": "Hmm, these Chiquitoos are clever. We've intercepted a program that takes a password in and generates some output. We found the correct password (it's \"password\") but it still won't let us in. The other agents are stumped. Can you take a look and see if you can figure out how to get the output?",
        "tips": [
          "Tip: Get it right and you get the flag."
        ]
      }
    ],
    [
      {
        "title": "Locked Doors",
        "description": "Recruit, another piece of field work for you.\n\nWe want to test the internal door locks on the ship - and how better than to let one of our most promising agents (you!) try and hack into it!\n\nTake a look at this lock. It has some binary numbers, can you figure out how to get in?",
        "tips": [
          "Tip: Break through the lock to get the flag."
        ]
      },
      {
        "title": "Port Service",
        "description": "Recruit, another interesting one for you.\n\nWe found a service running on one of The Chiquitoos servers at services.cyberprotection.agency on port 5737. We're not entirely sure what it does but we think we should check it out. We know that it somehow grants a code but it's hidden in so much text that we can't find it. Can you see if you can find it?",
        "tips": [
          "Tip: Find the code, it's the flag!"
        ]
      },
      {
        "title": "Boat Missing",
        "description": "Recruit, we've lost sight of one The Chiquitoo boats! We think it's heading out to intercept the ship they are targeting but we can't find it.\n\nWe know one of the gang members onboard, Arturo: he frequently posts photos from his phone to Picster, a social media site. Perhaps you can take a look at his latest photos and see if you can figure out where they are.",
        "tips": [
          "Tip: The flag is the absolute latitude and longitude degrees added together!"
        ]
      },
      {
        "title": "Barcode Bonanza",
        "description": "Intel just in!\n\nOn doing a routine review of all the boxes of Cola on one of the ships, we found four boxes with additional barcode stickers on them. Nobody can account for how they got there or what they mean. It must be The Chiquitoos on the inside sending each other messages about which boxes to target.\n\nSee if you can read the barcodes, decode the output and put them in the right order to get the message.",
        "tips": [
          "Tip: The flag is in the decrypted message."
        ]
      },
      {
        "title": "Encrypted",
        "description": "Quick extra project for you, recruit.\n\nWe have managed to gain access to a server, which is running The Chiquitoo gang's secure encryption service. We need the password they are using as the encryption key. Can you find out what it is for me?",
        "tips": [
          "Tip: The flag is the password."
        ]
      },
      {
        "title": "Wake Up Alarm",
        "description": "Recruit, it's time for you to get back in the field. We want to do a test run of one of the ship's onboard security systems. Take a look at the online control panel to the ship's security. Can you find a way to turn the alarms off? If you can, then maybe The Chiquitoos' can too!\n\nBy the way, our intel tells us that there used to actually be a button called \"Turn Off Alarms\", which the ship's security team removed to try and make it more secure. Maybe start with that.",
        "tips": [
          "Tip: Turn off the alarms to get the flag."
        ]
      },
      {
        "title": "Filtered Cola",
        "description": "Hey recruit, back on the previous level you might have been involved with the team that was looking into one of The Chiquitoos' side businesses: the bottling plant. We found their price list request form was vulnerable to command injection and we think the site must also be open to SQL injection.\n\nOn the site there's a page where you can search through a list of all the orders they are planning to fulfil over the next month and we've got you access to it. We think that some of the results are hidden and only available to the senior gang members. We need that extra list so we can see when they are planning to re-label the stolen Cola as a different brand.\n\nOne of our other agents has already tried but, unfortunately, it looks like there is a filter being applied to prevent certain phrases being used in the form. Can you bypass it?",
        "tips": [
          "Tip: Get the full list and you'll also get the flag. The flag is the name of the handler on the last result marked \"secret\"."
        ]
      },
      {
        "title": "Float to the Top",
        "description": "Recruit, we're close to foiling The Chiquitoos' plans, but to do so our guy on the inside needs access to the senior members of the gang. The only way he can do that is to pass one of their tests to see if he's got what it takes.\n\nThe good news is that we know what the test is. He needs to bypass the security check on a file called \"OverflowedBuffer\". We've got hold of the file and the test is tonight. Can you do it for him?",
        "tips": [
          "Tip: Pass the security check to get the flag."
        ]
      },
      {
        "title": "Secret Messages",
        "description": "Crikey, every time we think we're getting one step ahead of The Chiquitoos, they do something that proves how good they are.\n\nIt seems they now know we've been intercepting some of their messages, so they've started encoding them all. Our agents are looking at them now to try and figure it out. Why don't you take a look too and see if you can figure it out first. It probably won't harm your promotion prospects if you do!",
        "tips": [
          "Tip: Decode the message to get the flag."
        ]
      },
      {
        "title": "Shipping Lanes",
        "description": "Recruit, we've been monitoring the web traffic of one of the gang members and he's visited a page, which has made us very suspicious. Why? Two reasons: first, it's called \"shipping-lanes-target\" (so we think he's using it to monitor the ship) and second, we can't get access to it! Whatever we try, it just won't work.\n\nTake a look at the page, maybe have a dig around the source code and see if you can get us access to it.",
        "tips": [
          "Tip: Get access to get the flag."
        ]
      },
      {
        "title": "Corrupted Corruption",
        "description": "Recruit, there's another quick task I need your help with. One of the agents intercepted a zip file from The Chiquitoos, but it looks like it's corrupted. I know you've helped us with this kind of thing before. Can you take a look and see if you can get it working?",
        "tips": [
          "Tip: Open the file to get the flag."
        ]
      },
      {
        "title": "Hidra",
        "description": "Well, this is interesting. We've finally been able to get access to a messaging app (called Hidra) that The Chiquitoo gang is using to communicate. Within an hour of looking at the messages, they've already given us some great intel.\n\nFor example, FTP login details to the gang's main server! One small issue: they mention the address and the username but not the password. See if you can find a way in.",
        "tips": [
          "Tip: Find the password for the user account. The flag is in a file which you get from connecting to the FTP server via a PASV connection. If you are inside the VM, you can use ftp -p."
        ]
      }
    ],
    [
      {
        "title": "Centrifuge Exposed",
        "description": "Agent, here's a test for you to show the rest of the team you deserve to be on this level.\n\nOne of the Spetzners associates, Alexei, runs a science equipment ecommerce site called Centrifuge. Our team knows that he's secretly been using it to sell the Spetzners mad scientists the equipment they need for their schemes, so we've been trying to get access for weeks, to no avail.\n\nWe've just had a bit of good news. When making an update to the site Alexei made a mistake which, for a few hours last night, left some of the sites PHP code exposed. We managed to grab the code when it was there. Take a look and see if you can use it to find a way in to the site.",
        "tips": [
          "Tip: Get access to the site to get the flag."
        ]
      },
      {
        "title": "Cold File",
        "description": "Agent, quick task for you if you have a few minutes to spare. We have an old program one of our previous agents used that we can't get into. Can you get into it for me?",
        "tips": [
          "Tip: Run the program, find the password, input it into the program to get the flag."
        ]
      },
      {
        "title": "Hidden Centrifuge",
        "description": "Agent, we've been looking through the Centrifuge ecommerce site, run by one of the Spetzners associates Alexei. It contains thousands of pages, so one of the ways we've been doing that is simply by using a search engine, which has indexed all the pages.\n\nThe team have been starting to wonder whether there are other pages that we've not seen. Can you see if you can find them.",
        "tips": [
          "Tip: Find the hidden page to get the flag."
        ]
      },
      {
        "title": "Bunsen Burners",
        "description": "Agent, we've been looking into the different ways the Spetzners have been communicating with each other about their plans, and one of them is a science forum called Bunsen Burners. We want to see if the site is vulnerable in such a way that would allow us to serve our own JavaScript to one of the gang when they view it.\n\nWe've created an account for you and made a test forum page called \"Hack Trick\". See if you can get an alert box to appear on the page saying \"Kaboom!\".",
        "tips": [
          "Tip: Get the alert to show with the right text and you'll get the flag!"
        ]
      },
      {
        "title": "Hayka",
        "description": "Remember we said the Spetzners were mad scientists? Well, one of the gang, Vladimir, is an actual scientist, and runs an official scientific paper submission site called Hayka. But here's the thing - the server he runs the site from is his own, and we think he's also using it to store the gang's secret plans.\n\nWe want you to take a look at the scientific paper upload page, we think it might be vulnerable. See if you can upload a PHP file which will output the code word \"b1n4ry\" to the screen when you visit the page.",
        "tips": [
          "Tip: Upload the correct file and visit the file page to get the flag."
        ]
      },
      {
        "title": "Mission Extension",
        "description": "Agent, those Spetzners are really trying to test our patience! We have someone on the inside that managed to quickly take a copy of one of the gang's hard drives. But just to make it really hard to figure out what they're doing they've removed all file extensions - from every file!\n\nWe know there's a Jpeg in there somewhere with an image of the particular EMP they're trying to steal, but it's taking the team forever to find it. Can you think of a quicker way?",
        "tips": [
          "Tip: Find the Jpeg, it contains a serial number (SN), that's the flag!"
        ]
      },
      {
        "title": "Grand Old Ruble",
        "description": "Ok, we need you to step into the shoes of the Spetzners best hackers for a while. We've received some intel that the gang have identified a vulnerability on the Grand Ruble Bank's website that allows them to perform actions only an admin should be able to.\n\nTake a look and see if you can do the same so we can help the bank fix their security.",
        "tips": [
          "Tip: Transfer 1000 rubles to a user called cpa to get the flag."
        ]
      },
      {
        "title": "Bogdan's Data",
        "description": "Agent, a possible breakthrough that we need your help with. We've found a server run by the gang, it's at services.cyberprotection.agency:3166 and it seems to be protecting some data. The gang member who we think administers it, Bogdan, is one of the key members behind the plans to steal the EMP. We think it might have even been his suggestion in the first place. In short, we need access to the data on that server.\n\nFortunately we've been able to recover some source code for it. Take a look at the source code and see if you can get access to the server.",
        "tips": [
          "Tip: Get access to the server to get the flag."
        ]
      },
      {
        "title": "File Hunt",
        "description": "Agent, we have SSH access to one of the gang's servers that contains lots of files and we need to find one in particular.\n\nOne of the gang, Stanislav, created the file on 22 Nov 2015 at 8.00pm (we know because we were monitoring him!). Can you find that particular file, we think it contains important information concerning the whereabouts of several gang members.",
        "tips": [
          "Tip: Find the file, it contains the flag."
        ]
      },
      {
        "title": "Code Attack",
        "description": "We need your programming skills agent, and from what you've demonstrated so far we think you're definitely up for the challenge.\n\nYou need to write a simple script from scratch to crack into another program that takes an argument (a 4 digit numerical pin code) and outputs a result.",
        "tips": [
          "Tip: If you enter the right argument into the program you get the flag."
        ]
      },
      {
        "title": "Tayga Bears",
        "description": "One of the Spetzners lives in Tayga, a heavily forested area with a lot of bears! So it's no surprise that he spends some of his spare time taking photographs of them and posting them to a nature site called \u043c\u0435\u0434\u0432\u0435\u0434\u044c \u0441\u043b\u0435\u0434\u0438\u0442\u044c (which is Russian for \"Bear Watch\"). We thought it was fairly innocent until one of the agents spotted something weird about one of the images.\n\nTake a look at them and see if you can spot what it is.",
        "tips": [
          "Tip: There's a hidden file, open it to get the flag."
        ]
      },
      {
        "title": "Recruit List",
        "description": "Agent, the Spetzners are trying to recruit someone on the inside of the hidden weapons facility to help them get the access they need to steal the EMP. We believe one of the gang members has made a list of names of the people they want to target, and placed it on his own private web server.\n\nWe've found the login page for it, see if you can get access.",
        "tips": [
          "Tip: Bypass the login form to get the flag."
        ]
      }
    ],
    [
      {
        "title": "Overflow Egor",
        "description": "Right, recruit, it's time we tested your skills against the Spetzners finest.\n\nOne of their gang, Egor, is a master scientist and skilled programmer. He's written a program which we think is vulnerable to buffer overflow. See if you can crack it.",
        "tips": [
          "Tip: Overflow the buffer to get the flag."
        ]
      },
      {
        "title": "Calling Yaroslav",
        "description": "Agent, we know the gang are attacking soon, so it's time to start bringing them in. This morning our agents picked up one of the gang's henchmen, Yaroslav, because we knew he had the time and date of the attack on his phone. The trouble is, we've looked at his phone and it contains a sophisticated eight-digit pin locking system. It's based on a math problem, and we want you to crack it.\n\nHere's what we know: when the phone was first started the pin code code to log in was 4096. Every time we enter the pin code, the number of times the pin code has been entered correctly in the past is added to the value of the pin code, to get the new pin. We know the system has been used 5520 times before. What will the pin code be this time?",
        "tips": [
          "Tip: Get the right pin code to get the flag. All we know at the moment is that it's definitely eight digits long."
        ]
      },
      {
        "title": "Text and Text",
        "description": "Recruit, yesterday one of our field agents was able to snoop on a meeting between several gang members planning out their attack on the weapons facility.\n\nOne of them mentioned to the other that he'd send over the lock code to one of the doors on the facility. If we knew the code, we'd know how they got it (because the codes are only given to certain people) and where exactly on the facility they planned to get access.\n\nWell, this morning we intercepted an email between the two expecting to see the code, but instead they just sent over two large text files. Weird. Can you find the code?",
        "tips": [
          "Tip: Find the code, that's the flag."
        ]
      },
      {
        "title": "Hidden Bear",
        "description": "It seems the Spetzners will find any way possible to keep their secrets from us. And we think they know we're on to them, so they're doubling down with strange new ways to hide their messages.\n\nHere's a good example: you may remember the Bear Watch site from the previous level? Well, we've been looking at one of the images from that site for days and can't seem to crack what it's hiding. Take a look and see if you can find it.",
        "tips": [
          "Tip: Manipulate the image in some way to get the flag."
        ]
      },
      {
        "title": "Matryoshka",
        "description": "Agent, know anything about man pages?\n\nLog into this SSH server using the details below. We've received word that the gang has been using this public server to pass messages through the use of man pages. We need to see if they've used a particular code word (matryoshka) which they're using to say when to start implementing their plan. See if you can find it.",
        "tips": [
          "Tip: Find the code word to get the flag."
        ]
      },
      {
        "title": "Chatz with Semyon",
        "description": "Agent, we need your urgent help.\n\nOne of the gang, Semyon, has been using a Skype equivalent called Chirp to make calls. We know he made a call this morning to an associate of the gang that's planning to help as a getaway driver when they steal the EMP. We want to pick him up before he can help, so we need to know his phone number (which will help give us his location).\n\nSee if you can crack into the Chirp site. If you do you'll see the calls Semyon made today, we're looking for the one to Yury. Oh, and luckily one of the other agents has managed to get the PHP source code to the page, that might help.",
        "tips": [
          "Tip: The flag is Yury's phone number."
        ]
      },
      {
        "title": "Kapa's Hidden Secrets",
        "description": "In what we thought was an unrelated piece of intel, one of our field agents monitoring the gang reported this morning that he noticed one of them looking at a site which sells pencils. Well, it seems they're using it for something else too - to hide files.\n\nLuckily for us, we think the site is vulnerable to command injection. See if you can use the comment form to get access and find a file, we think it has a list of the gang members involved in the planned robbery and their particular role in the attack, so it's vital we get our hands on it right away.",
        "tips": [
          "Tip: Find the file, it contains the flag."
        ]
      },
      {
        "title": "Odd File",
        "description": "Hmm, this is an interesting one for you, recruit. We have a file that the Spetzners have been sending back and forth between them for a while. We have no idea what it is or how to get access. We've tried running it and answering the question it asks but to no avail.\n\nWhy don't you give it a try and see if you can get access.",
        "tips": [
          "Tip: Get access to the file to get the flag."
        ]
      },
      {
        "title": "Junk Decryption",
        "description": "Agent, we've just received a bunch of files from one of our field agents taken from one of the senior gang members hard drives, and our team is having trouble putting it all together. Maybe you can help?\n\nHere's what we have: a decryption program which takes a string as an argument, but when we run it we just get junk back; some encrypted text, and; a weird image file of a calendar. A bizarre combination, take a look and see if you can figure it out.",
        "tips": [
          "Tip: Run the correct string through the decryption program to get the flag."
        ]
      },
      {
        "title": "Snakes in Motion",
        "description": "Agents have intercepted some vital information we'd like you to look at. We've sent you an email with some encrypted text, a dictionary file and a Python script to help you extract what we believe might be the password to a cloud storage solution the Spetzners use to store their criminal documents. One problem, unfortunately, the Python script is incomplete.\n\nCan you finish it off so that it runs each line in the dictionary against the encrypted text to decrypt it? Hurry agent, the Spetzners might know we are on to them and could transfer their documents elsewhere!",
        "tips": [
          "Tip: Decrypt the text, that's the flag."
        ]
      },
      {
        "title": "Perilous Pencils",
        "description": "Agent, we've just had an interesting piece of intel. It seems the gang know we're monitoring their own sites and servers, and have therefore started removing anything incriminating. But they still intend to implement their plans to steal the EMP and so need a new way of sharing information.\n\nYou may have already come across the pencil selling e-commerce site Kapa that the gang hacked into and have been using to exchange messages. The Spetzners, being geeky science types, do love their pencils, but we think it's a little more sinister than that - it seems they might have hacked the site and started adjusting the images on it to contain hidden messages! If we hadn't noticed them using the site, we would never have known. Clever.\n\nHave a look at this page in particular which the gang member was looking at. Anything strange about it?",
        "tips": [
          "Tip: The flag is in one of the images! Oh, and for password protected files don't attempt to drag and drop them."
        ]
      },
      {
        "title": "Dear John",
        "description": "Ok, a really important task for you, recruit. We need access to the main Spetzner database! We think it's the only way to not only bring the gang down, but also gather the amount of incriminating evidence we'll need to prosecute.\n\nWe have a word list and a binary. The binary takes in a word list and attempts to break the password on their database server. Unfortunately, the word list doesn't have the password on there, we think it may be a similar word though. Another agent mentioned a program called \"john\" which can alter word lists to make minor changes, can you look into that and see if you can use it to get access to the database?",
        "tips": [
          "Tip: Get access to the database to get the flag!"
        ]
      }
    ],
    [
      {
        "title": "Port of Call",
        "description": "Quick job for you Agent. We think the gang have a port running between 14000 and 15000 that might expose some interesting information about them, we've temporarily pointed our domain services.cyberprotection.agency to their server. Use that address to find the service and connect to it.",
        "tips": [
          "Tip: Connect to the port to get the flag."
        ]
      },
      {
        "title": "Nightclub Rendezvous",
        "description": "Agent, this one is urgent. Our team have just intercepted some chatter between two gang members and it's confirmed they are holding a meeting tonight at a nightclub somewhere in London. Unfortunately there are a lot of nightclubs in London and they didn't mention which one. Then, 10 minutes after the conversation ended, one of them posted up a cryptic piece of text on his social media account. We think it contains the time and exact location of the meeting. We have every agent on the ninth floor working to decode the text, can you get there first?\n\nHere's what we know about it so far: each character in the text was converted into a decimal number (according to the ASCII chart), then each number had the same number subtracted from it (a number between 1 and 10). Then each number was written to a new line of the text.",
        "tips": [
          "Tip: The flag is the name of the nightclub."
        ]
      },
      {
        "title": "The Insider",
        "description": "One of the other agents needs your help. He's been trying to open a file we intercepted which we believe might have special insider information about the bank the Bulldogs are targeting. The agent thought opening the file was going to be simple, but it's not working. Can you help?",
        "tips": [
          "Tip: Get the password and open the file to get the flag."
        ]
      },
      {
        "title": "Binary Battle",
        "description": "Agent, it looks like we might need your programming skills again. We've intercepted a binary that we want you to take a look at. It takes a password as an argument and produces a result if you get it correct. We think writing a script which uses strings might be the best way to crack it, but none of the other agents have been able to so far.\n\nThe file was taken from the hard drive of the gang member we think has been doing reconnaissance on the bank, so it could contain some extremely important information.",
        "tips": [
          "Tip: Input the right password to get the flag."
        ]
      },
      {
        "title": "Art Thief",
        "description": "Agent, we've been doing some research on Barry Brigley, one of the Bulldogs and a notorious art thief. We've managed to get access to a site he uses to post images of the art he's stolen which are for sale on the black market, but he's hiding the images somehow so we don't know what he's stolen. If we knew it would help us track his movements over the past few months (we think the art comes from a variety of galleries all over Europe).\n\nTake a look at the site and the images. You can download the images, they're in a common image format, see if you can recover them.",
        "tips": [
          "Tip: The flag is in one of the images."
        ]
      },
      {
        "title": "Benjamin the Brute",
        "description": "Agent, one of the gang, Benjamin Goldleaf, was yesterday overheard by our inside undercover agent talking about posting notices on a secret message board that the gang use to communicate meeting times.\n\nWith a bit of detective work we've managed to find the message board and get Bens user details. We want to post a fake message using the API to try and draw one of the more senior gang members out, but we can't get it to work. See if you can.",
        "tips": [
          "Tip: Successfully post a notice on the board to get the flag."
        ]
      },
      {
        "title": "Stepping Up",
        "description": "Finally, some good news! We've had an agent undercover with the Bulldogs for a while now, but the gang is quite large and it's very hard to get access to the elite group that are planning to rob the bank. However, we've just received word from our counterparts in Spain that they've arrested one of that elite group whilst he was on a trip there. He was their best hacker and now they need to replace him quickly otherwise their attack on the bank won't work.\n\nThe elite group have decided to select someone from the rest of the gang by issuing them with a hacking challenge. Whoever cracks it first gets to go with them on the robbery. If we could get our undercover agent into that group we'd almost certainly be able to disrupt their plans. The challenge is tough - there's a file and you need to overwrite the buffer to make the secret variable read: de4dc0de. Do you think you can be the first to break it?",
        "tips": [
          "Tip: Overflow the buffer to get the flag."
        ]
      },
      {
        "title": "Unsafe Deposit Box",
        "description": "Agent, we've been working with the bank to try and make their website more secure, which will hopefully prevent the Bulldogs getting access.\n\nAs part of our penetration testing we've found a page which might be vulnerable to XSS. It's the page you use to request further information about safety deposit boxes. You need to be logged in as a standard bank customer to get to that page, but we think you can use XSS to change the access level to admin. Give it a try.",
        "tips": [
          "Tip: Get admin access to the site to get the flag."
        ]
      },
      {
        "title": "Vicious Voicemail",
        "description": "Agent, once again we need you to step up and show us your skills. This time you need to stand in for Agent V, our resident audio expert, as he's been called up to help the team on the next level with a special mission.\n\nBefore he went he was able to get access to the private voicemail system the Bulldogs were using. He got access by logging in as one of the gang members, Terry Turner, and we can see there are a set of voicemails on there for him. Agent V didn't have chance to analyse them before he went, can you do it for him? We think there might be hidden messages in the audio files.",
        "tips": [
          "Tip: Find the hidden message to get the flag."
        ]
      },
      {
        "title": "Exposure",
        "description": "One of the Bulldogs, Lara Mindsay, is a surveillance expert and we think she's been using advanced drones to take high resolution photos of the bank from different angles.\n\nShe also runs a small legit side project, a photo site which allows anyone to create an account and upload photos. We think she's using it herself to store the bank surveillance photos and so we want to see if it's vulnerable. We've been penetration testing the site but have not had any success so far, then this morning one of the other agents sent me a tip - CVE-2012-2399. Take a look and see if you can use it to see if the site is vulnerable.",
        "tips": [
          "Tip: Find the vulnerability to get the flag."
        ]
      },
      {
        "title": "Cryptonite",
        "description": "Yesterday our uncover agent let us know about an encryption tool one of the gang built called Cryptonite. It runs on one of the Bulldogs private servers and we think it might be vulnerable.\n\nSee if you can use it to get access to the server and look for any files that might be worth investigating. We think the gang member who built it, Percy Pinkly, is involved in the gangs efforts to circumvent the banks main security alarms, so he might have been storing files on the server related to blueprints or the security system.",
        "tips": [
          "Tip: There's a file on the server containing the flag."
        ]
      },
      {
        "title": "Open Box",
        "description": "It looks like Billy Johnson, one of the gang's enforcers, is using a cloud storage service to keep his files secure. We know he has an account and we know his password, but we don't know his account name.\n\nOur team have created an account with the service and logged in. See if you can find a way to trick the site into giving you a list of accounts.",
        "tips": [
          "Tip: Billy Johnson's account name is the flag."
        ]
      }
    ],
    [
      {
        "title": "Spam Robot",
        "description": "Agent, because of your great work on the previous level the gang have struggled to find a way to get physical access to the bank. So it looks like they are testing ways to get a package containing a small robot into the building which they can use remotely.\n\nThey plan to send it to one of the senior management as a gift from a friend (who the manager is unaware belongs to the Bulldog gang). When the manager plugs it into his computer to charge up via USB, it will hijack the computer and access his email. With this access, the gang will send a specific spam email, which the robot will use as a trigger to start an attack on the banks servers from the inside!\n\nTo help narrow our investiga
Download .txt
gitextract_cksaviai/

├── .dockerignore
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   ├── emoji_request.md
│   │   └── feature_request.md
│   ├── dependabot.yml
│   ├── labels.json
│   ├── labels.yaml
│   └── workflows/
│       ├── build.yml
│       ├── codeql-analysis.yml
│       ├── create-labels.yml
│       ├── label-pull-requests.yml
│       ├── lint.yml
│       ├── stale.yml
│       └── test-results.yml
├── .gitignore
├── .security.txt
├── CODE_OF_CONDUCT.md
├── Dockerfile
├── LICENSE
├── README.md
├── SECURITY.md
├── cdbot/
│   ├── __init__.py
│   ├── __main__.py
│   ├── bot.py
│   ├── cogs/
│   │   ├── admin.py
│   │   ├── cyber.py
│   │   ├── fun.py
│   │   ├── general.py
│   │   ├── help.py
│   │   └── maths.py
│   ├── constants.py
│   ├── data/
│   │   ├── assess.json
│   │   ├── game.json
│   │   ├── legacy/
│   │   │   ├── game.json
│   │   │   └── headquarters2018.json
│   │   └── readme.json
│   └── log.py
├── docker-compose.yml
├── pyproject.toml
└── tox.ini
Download .txt
SYMBOL INDEX (119 symbols across 10 files)

FILE: cdbot/__init__.py
  function main (line 11) | async def main():

FILE: cdbot/bot.py
  function register_metadata (line 30) | async def register_metadata(ctx):
  function block_banned_ids (line 40) | async def block_banned_ids(ctx):
  function block_muted (line 46) | async def block_muted(ctx):
  function load_extensions (line 51) | async def load_extensions():

FILE: cdbot/cogs/admin.py
  function check_bad_name (line 19) | def check_bad_name(nick):
  class Admin (line 26) | class Admin(Cog):
    method __init__ (line 31) | def __init__(self, bot: Bot):
    method on_member_update (line 35) | async def on_member_update(self, member_before: Member, member_after: ...
    method on_user_update (line 79) | async def on_user_update(self, member_before: Member, member_after: Me...
    method on_member_join (line 86) | async def on_member_join(self, member: Member):
    method raid (line 94) | async def raid(
  function setup (line 155) | async def setup(bot):

FILE: cdbot/cogs/cyber.py
  function generatebase64 (line 33) | async def generatebase64(seed: int) -> str:
  class Cyber (line 39) | class Cyber(Cog):
    method __init__ (line 93) | def __init__(self, bot: Bot):
    method fieldmanual (line 101) | async def fieldmanual(self, ctx: Context):
    method level (line 109) | async def level(
    method flag (line 162) | async def flag(
    method readme (line 197) | async def readme(
    method assess (line 351) | async def assess(self, ctx: Context, challenge_num: int):
    method game (line 387) | async def game(self, ctx: Context):
    method essentials (line 395) | async def essentials(self, ctx: Context):
    method hundred (line 403) | async def hundred(self, ctx: Context):
    method elitecount (line 417) | async def elitecount(self, ctx: Context):
    method countdown (line 453) | async def countdown(self, countdown_target_str: str, stage_name: str, ...
    method support (line 486) | async def support(self, ctx: Context):
    method meta (line 493) | async def meta(self, ctx: Context):
    method cdtos (line 500) | async def cdtos(self, ctx: Context):
    method cma (line 507) | async def cma(self, ctx: Context, *, section: str = None):
    method on_message (line 520) | async def on_message(self, message: Message):
  function setup (line 534) | async def setup(bot):

FILE: cdbot/cogs/fun.py
  function convert_emoji (line 60) | def convert_emoji(message: str) -> List[str]:
  function emojify (line 80) | async def emojify(message: Message, string: str):
  class FormerUser (line 87) | class FormerUser(UserConverter):
    method convert (line 88) | async def convert(self, ctx, argument):
  class Fun (line 95) | class Fun(Cog):
    method __init__ (line 120) | def __init__(self, bot: Bot):
    method migrate_quotes (line 126) | async def migrate_quotes(self):
    method on_ready (line 138) | async def on_ready(self):
    method on_message (line 161) | async def on_message(self, message: Message):
    method on_raw_reaction_add (line 252) | async def on_raw_reaction_add(self, raw_reaction: RawReactionActionEve...
    method lmgtfy (line 291) | async def lmgtfy(self, ctx: Context, *args: str):
    method react (line 311) | async def react(self, ctx, *, message: str):
    method xkcd (line 330) | async def xkcd(self, ctx: Context, number: str = None):
    method quotes (line 379) | async def quotes(self, ctx: Context, member: FormerUser = None):
    method quotecount (line 419) | async def quotecount(self, ctx: Context, member: FormerUser = None):
    method quoteboard (line 439) | async def quoteboard(self, ctx: Context, page: int = 1):
    method add_quote_to_db (line 470) | async def add_quote_to_db(self, quote: Message):
    method create_text_image (line 516) | async def create_text_image(self, ctx: Context, person: str, text: str):
    method agentj (line 548) | async def agentj(self, ctx: Context, *, text: str):
    method jibhat (line 555) | async def jibhat(self, ctx: Context, *, text: str):
    method agentq (line 562) | async def agentq(self, ctx: Context, *, text: str):
    method angryj (line 569) | async def angryj(self, ctx: Context, *, text: str):
    method angrylyne (line 576) | async def angrylyne(self, ctx: Context, *, text: str):
    method baldj (line 583) | async def baldj(self, ctx: Context, *, text: str):
    method neveragoodtime (line 590) | async def neveragoodtime(self, ctx: Context):
    method tryharder (line 597) | async def tryharder(self, ctx: Context):
    method hac (line 604) | async def hac(self, ctx: Context):
    method dox (line 611) | async def dox(self, ctx: Context):
    method theworstpunishmentwehave (line 618) | async def theworstpunishmentwehave(self, ctx: Context):
    method beano (line 625) | async def beano(self, ctx: Context):
    method flowchart (line 629) | async def flowchart(self, ctx: Context):
    method unacceptable (line 636) | async def unacceptable(self, ctx: Context):
    method quotebait (line 643) | async def quotebait(self, ctx: Context):
    method thot (line 650) | async def thot(self, ctx: Context):
    method subtler (line 654) | async def subtler(self, ctx: Context):
    method subtle (line 658) | async def subtle(self, ctx: Context):
    method whoarethecyberists (line 662) | async def whoarethecyberists(self, ctx: Context):
    method jibhatisnotinvolvedinthat (line 670) | async def jibhatisnotinvolvedinthat(self, ctx: Context):
    method whynotboth (line 677) | async def whynotboth(self, ctx: Context):
    method simples (line 684) | async def simples(self, ctx: Context):
    method window (line 691) | async def window(self, ctx: Context):
    method dance (line 698) | async def dance(self, ctx: Context):
    method thisisfine (line 706) | async def thisisfine(self, ctx: Context):
    method inout (line 713) | async def inout(self, ctx: Context):
    method zucc (line 720) | async def zucc(self, ctx: Context):
    method succ (line 724) | async def succ(self, ctx: Context):
    method shhh (line 728) | async def shhh(self, ctx: Context):
    method suppressdissent (line 733) | async def suppressdissent(self, ctx: Context):
    method murder (line 743) | async def murder(self, ctx: Context):
  function setup (line 747) | async def setup(bot):

FILE: cdbot/cogs/general.py
  class General (line 16) | class General(Cog):
    method __init__ (line 21) | def __init__(self, bot: Bot):
    method on_ready (line 25) | async def on_ready(self):
    method on_member_join (line 40) | async def on_member_join(self, member):
    method on_member_remove (line 46) | async def on_member_remove(self, member):
    method on_member_ban (line 52) | async def on_member_ban(self, guild, user):
    method on_command_error (line 57) | async def on_command_error(self, ctx, error):
    method bbcnews (line 113) | async def bbcnews(self, ctx: Context):
    method skynews (line 120) | async def skynews(self, ctx: Context):
    method tos (line 127) | async def tos(self, ctx: Context):
  function setup (line 134) | async def setup(bot):

FILE: cdbot/cogs/help.py
  class EmbeddedHelpCommand (line 5) | class EmbeddedHelpCommand(commands.HelpCommand):
    method __init__ (line 6) | def __init__(self):
    method command_callback (line 9) | async def command_callback(self, ctx, *, command=None):
    method send_bot_help (line 17) | async def send_bot_help(self, mapping):
    method send_cog_help (line 39) | async def send_cog_help(self, cog):
    method send_command_help (line 59) | async def send_command_help(self, command):
  class Help (line 78) | class Help(commands.Cog):
    method __init__ (line 79) | def __init__(self, bot):
    method cog_unload (line 85) | def cog_unload(self):
  function setup (line 89) | async def setup(bot):

FILE: cdbot/cogs/maths.py
  class Maths (line 15) | class Maths(Cog):
    method __init__ (line 18) | def __init__(self, bot: Bot):
    method on_message (line 22) | async def on_message(self, message):
    method latex (line 30) | async def latex(self, ctx: Context, *, expression: str):
    method latex_render (line 36) | async def latex_render(
  function setup (line 128) | async def setup(bot):

FILE: cdbot/constants.py
  function getenv (line 8) | def getenv(name: str, fallback: str = "") -> str:
  class PostgreSQL (line 16) | class PostgreSQL:
  class Maths (line 24) | class Maths:
    class Challenges (line 60) | class Challenges:
  class Roles (line 69) | class Roles:
    class Elite (line 70) | class Elite:

FILE: cdbot/log.py
  class DiscordHandler (line 21) | class DiscordHandler(logging.Handler):
    method __init__ (line 26) | def __init__(self, bot: commands.Bot, *args, **kwargs):
    method _level_to_color (line 32) | def _level_to_color(self, level_number: int):
    method emit (line 35) | def emit(self, record):
Condensed preview — 41 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (528K chars).
[
  {
    "path": ".dockerignore",
    "chars": 168,
    "preview": ".github\n.vscode\nazure-pipelines\n.gitignore\n.security.txt\nCODE_OF_CONDUCT.md\ndeployment.yaml\ndocker-compose.yml\nLICENSE\nR"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 146,
    "preview": "# To require the approval of the server staff as well as that of the technical community developer team\n*       @CyberDi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 498,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n**Describe the "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/emoji_request.md",
    "chars": 423,
    "preview": "---\nname: Emoji Reaction Request\nabout: Request the addition of an automatic emoji reaction\ntitle: ''\nlabels: enhancemen"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "chars": 604,
    "preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 156,
    "preview": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: daily\n  open-pull-requests-limit"
  },
  {
    "path": ".github/labels.json",
    "chars": 2015,
    "preview": "[\n    {\n        \"name\": \"duplicate\",\n        \"color\": \"26282a\",\n        \"description\": \"This issue or pull request alrea"
  },
  {
    "path": ".github/labels.yaml",
    "chars": 744,
    "preview": "meta:\n  - .github/*\n  - .github/**/*\n  - .gitignore\n  - .security.txt\n  - CODE_OF_CONDUCT.md\n  - LICENSE\n  - README.md\n "
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 1359,
    "preview": "name: Build Docker Container\n\non: push\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout reposi"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "chars": 583,
    "preview": "name: \"Code scanning\"\n\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n  schedule:\n    - "
  },
  {
    "path": ".github/workflows/create-labels.yml",
    "chars": 282,
    "preview": "on: issues\nname: Create Default Labels\njobs:\n  labels:\n    name: DefaultLabelsActions\n    runs-on: ubuntu-latest\n    ste"
  },
  {
    "path": ".github/workflows/label-pull-requests.yml",
    "chars": 271,
    "preview": "name: \"Pull Request Labeler\"\n\non:\n  schedule:\n  - cron: \"*/15 * * * *\"\n\njobs:\n  execute:\n    runs-on: ubuntu-latest\n    "
  },
  {
    "path": ".github/workflows/lint.yml",
    "chars": 1143,
    "preview": "name: Lint\n\non: push\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout repository\n      uses: ac"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 594,
    "preview": "name: \"Close stale issues\"\non:\n  schedule:\n  - cron: \"0 0 * * *\"\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n "
  },
  {
    "path": ".github/workflows/test-results.yml",
    "chars": 984,
    "preview": "name: Unit Test Results\n\non:\n  workflow_run:\n    workflows: [\"Lint\"]\n    types:\n      - completed\n\njobs:\n  unit-test-res"
  },
  {
    "path": ".gitignore",
    "chars": 2833,
    "preview": "# Changes to the lockfile should be deliberate\npoetry.lock\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[co"
  },
  {
    "path": ".security.txt",
    "chars": 290,
    "preview": "Contact: thebeanogamer@gmail.com\nEncryption: https://keybase.io/thebeanogamer/pgp_keys.asc\nPreferred-Languages: en\nCanon"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3220,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "Dockerfile",
    "chars": 227,
    "preview": "FROM python:3.9-buster\n\nWORKDIR /app\nRUN pip install poetry\nADD pyproject.toml poetry.lock /app/\nRUN poetry config virtu"
  },
  {
    "path": "LICENSE",
    "chars": 1087,
    "preview": "MIT License\n\nCopyright (c) 2018-2020 Cyber Discovery Community\n\nPermission is hereby granted, free of charge, to any per"
  },
  {
    "path": "README.md",
    "chars": 5428,
    "preview": "# Cyber Discovery Discord Bot\n\n[![Build Docker Container](https://github.com/CyberDiscovery/cyberdisc-bot/actions/workfl"
  },
  {
    "path": "SECURITY.md",
    "chars": 1213,
    "preview": "# Security Policies and Procedures\n\nThis document outlines security procedures and general policies for the **cyberdisc-"
  },
  {
    "path": "cdbot/__init__.py",
    "chars": 490,
    "preview": "\"\"\"Initialise cdbot as a package for poetry.\"\"\"\n\nimport sentry_sdk\nfrom git import Repo\nfrom sentry_sdk.integrations.aio"
  },
  {
    "path": "cdbot/__main__.py",
    "chars": 142,
    "preview": "\"\"\"Entry point for the 'python -m cdbot' command.\"\"\"\n\nfrom asyncio import run\n\nimport cdbot\n\nif __name__ == \"__main__\":\n"
  },
  {
    "path": "cdbot/bot.py",
    "chars": 1539,
    "preview": "\"\"\"Main script to define bot methods, and start the bot.\"\"\"\n\nimport logging\nfrom os import listdir\nfrom platform import "
  },
  {
    "path": "cdbot/cogs/admin.py",
    "chars": 5920,
    "preview": "import re\n\nfrom discord import AuditLogAction, Colour, Embed, Member\nfrom discord.ext.commands import Bot, Cog, Context,"
  },
  {
    "path": "cdbot/cogs/cyber.py",
    "chars": 20172,
    "preview": "import datetime\nimport random\nimport re\nimport string\nimport textwrap\nfrom asyncio import sleep\nfrom io import StringIO\n"
  },
  {
    "path": "cdbot/cogs/fun.py",
    "chars": 26776,
    "preview": "\"\"\"\nSet of bot commands designed for general leisure.\n\"\"\"\nimport asyncio\nimport re\nimport textwrap\nfrom io import BytesI"
  },
  {
    "path": "cdbot/cogs/general.py",
    "chars": 4715,
    "preview": "import os\n\nfrom discord.ext import commands\nfrom discord.ext.commands import Bot, Cog, Context, command\nfrom git import "
  },
  {
    "path": "cdbot/cogs/help.py",
    "chars": 3408,
    "preview": "from discord import Colour, Embed\nfrom discord.ext import commands\n\n\nclass EmbeddedHelpCommand(commands.HelpCommand):\n  "
  },
  {
    "path": "cdbot/cogs/maths.py",
    "chars": 4847,
    "preview": "\"\"\"\nSet of bot commands designed for Maths Challenges.\n\"\"\"\nimport asyncio\nfrom io import BytesIO\n\nimport aiohttp\nfrom PI"
  },
  {
    "path": "cdbot/constants.py",
    "chars": 7641,
    "preview": "import base64\nimport re\nfrom os import environ\n\nDEPLOY = bool(environ.get(\"DEPLOY\"))\n\n\ndef getenv(name: str, fallback: s"
  },
  {
    "path": "cdbot/data/assess.json",
    "chars": 6192,
    "preview": "[{\n    \"title\": \"You spin me right round\",\n    \"description\": \"One of our agents who has a love for vinyl has come acros"
  },
  {
    "path": "cdbot/data/game.json",
    "chars": 144432,
    "preview": "{\n  \"Intern\": [\n    [\n      {\n        \"title\": \"Hello World\",\n        \"description\": \"We've found a profile page of a kn"
  },
  {
    "path": "cdbot/data/legacy/game.json",
    "chars": 145581,
    "preview": "{\n  \"Headquarters\": [\n    [\n      {\n        \"title\": \"Secret Caesar\",\n        \"description\": \"Our agents have been revie"
  },
  {
    "path": "cdbot/data/legacy/headquarters2018.json",
    "chars": 94872,
    "preview": "{\n  \"L1\": {\n    \"C1\": {\n      \"title\": \"Secret Caesar\",\n      \"description\": \"Our agents have been reviewing some docume"
  },
  {
    "path": "cdbot/data/readme.json",
    "chars": 10927,
    "preview": "{\n    \"part1\": {\n        \"content\": \"**Welcome to the Cyber Discovery: Legacy Discord Server!** This was once the place "
  },
  {
    "path": "cdbot/log.py",
    "chars": 2052,
    "preview": "\"\"\"\nAn implementation of a logging.Handler for sending messages to Discord\n\"\"\"\n\nimport datetime\nimport logging\n\nfrom dis"
  },
  {
    "path": "docker-compose.yml",
    "chars": 393,
    "preview": "version: '3'\n\nvolumes:\n\n  database:\n    driver: local\n\n\nservices:\n\n  database:\n    image: postgres:11\n    volumes:\n     "
  },
  {
    "path": "pyproject.toml",
    "chars": 788,
    "preview": "[tool.poetry]\nname = \"cdbot\"\nversion = \"0.1.0\"\ndescription = \"A Discord bot for the Cyber Discovery community.\"\nauthors "
  },
  {
    "path": "tox.ini",
    "chars": 164,
    "preview": "[flake8]\nmax-line-length=120\napplication_import_names=cdbot\nexclude=__pycache__\nimport-order-style=pycharm\nignore=S311,P"
  }
]

About this extraction

This page contains the full source code of the CyberDiscovery/cyberdisc-bot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 41 files (493.5 KB), approximately 114.7k tokens, and a symbol index with 119 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!