aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/bug-report-template.md29
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md19
-rw-r--r--.gitignore113
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--Pipfile21
-rw-r--r--Pipfile.lock101
-rw-r--r--README.md108
-rw-r--r--bot/__init__.py30
-rw-r--r--bot/bot.py29
-rw-r--r--bot/cogs/hacktoberstats.py186
-rw-r--r--bot/cogs/movie.py99
-rw-r--r--bot/cogs/template.py3
-rw-r--r--requirements.txt10
13 files changed, 733 insertions, 17 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug-report-template.md b/.github/ISSUE_TEMPLATE/bug-report-template.md
new file mode 100644
index 00000000..6c03fc5a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug-report-template.md
@@ -0,0 +1,29 @@
+---
+name: Bug Report Template
+about: A simple bug report template.
+
+---
+
+## Description
+
+[provide a simple description of the bug]
+
+## Steps to Reproduce
+
+- [replace with a description of a step to reproduce the issue]
+- [replace with a description of a step to reproduce the issue]
+- [replace with a description of a step to reproduce the issue]
+
+## Expected Behavior
+The expected behaviour is [provide a detailed description and, if necessary, screenshots].
+
+## Actual Behavior
+The actual behaviour is [provide a detailed description and, if necessary, screenshots].
+
+## Environment
+- Device Type: (e.g.: Desktop or Mobile)
+- Operating System: (e.g.: Microsoft Windows or Ubuntu Linux)
+ - Version: (e.g.: 10 or 18.04)
+
+## Other
+[provide any additional details that can be helpful]
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 00000000..04d31a5d
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature request
+about: A simple feature request template.
+
+---
+
+Provide a simple description of how the feature would work. This should not include technical details, but should instead be a high level description of how the user experience would be when you used this feature.
+
+## Implementation details
+
+List the changes required for the implementation here.
+
+- [ ] Write the bot command
+- [ ] Add the command to our README.md
+- [ ] etc.
+
+## Additional information
+
+Provide any additional information or clarifications here.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..8f8974f9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,113 @@
+# bot (project-specific)
+log/*
+
+
+
+
+# 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
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+
+# jetbrains
+.idea/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a35f269b..35c951d1 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,4 +9,6 @@ This project is a community project for the Python Discord community over at htt
* Pull requests that do not solve an open issue, for issues that have already been solved, or that are low-effort will be marked with the `invalid` label, which will ensure that they do not count towards your 5 Hacktoberfest PRs.
* You must be a member of our Discord community in order to contribute to this project.
+# Installation & Dependency Management
+Hacktoberbot utilizes [Pipenv](https://pipenv.readthedocs.io/en/latest/) for installation and dependency management. For users unfamiliar with the Pipenv workflow, a [Pipenv Primer](https://github.com/discord-python/hacktoberbot/wiki/Hacktoberbot-and-Pipenv) is provided in Hactoberbot's Wiki.
diff --git a/Pipfile b/Pipfile
new file mode 100644
index 00000000..90f6f45e
--- /dev/null
+++ b/Pipfile
@@ -0,0 +1,21 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+"discord.py" = {ref = "rewrite", git = "https://github.com/Rapptz/discord.py"}
+
+[dev-packages]
+"flake8" = "*"
+"flake8-bugbear" = "*"
+"flake8-import-order" = "*"
+"flake8-tidy-imports" = "*"
+"flake8-todo" = "*"
+"flake8-string-format" = "*"
+
+[requires]
+python_version = "3.7"
+
+[scripts]
+start = "python -m bot" \ No newline at end of file
diff --git a/Pipfile.lock b/Pipfile.lock
new file mode 100644
index 00000000..8d330b8d
--- /dev/null
+++ b/Pipfile.lock
@@ -0,0 +1,101 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "4ea08ef13815b0819e975097df6bb77d74d756524db2ad106ec93873fd1594de"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.7"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "discord.py": {
+ "git": "https://github.com/Rapptz/discord.py",
+ "ref": "1da696258095d5c1171a1cdbe75f56c535c6683e"
+ }
+ },
+ "develop": {
+ "attrs": {
+ "hashes": [
+ "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
+ "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
+ ],
+ "version": "==18.2.0"
+ },
+ "flake8": {
+ "hashes": [
+ "sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
+ "sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
+ ],
+ "index": "pypi",
+ "version": "==3.5.0"
+ },
+ "flake8-bugbear": {
+ "hashes": [
+ "sha256:07b6e769d7f4e168d590f7088eae40f6ddd9fa4952bed31602def65842682c83",
+ "sha256:0ccf56975f4db1d69dc1cf3598c99d768ebf95d0cad27d76087954aa399b515a"
+ ],
+ "index": "pypi",
+ "version": "==18.8.0"
+ },
+ "flake8-import-order": {
+ "hashes": [
+ "sha256:9be5ca10d791d458eaa833dd6890ab2db37be80384707b0f76286ddd13c16cbf",
+ "sha256:feca2fd0a17611b33b7fa84449939196c2c82764e262486d5c3e143ed77d387b"
+ ],
+ "index": "pypi",
+ "version": "==0.18"
+ },
+ "flake8-string-format": {
+ "hashes": [
+ "sha256:68ea72a1a5b75e7018cae44d14f32473c798cf73d75cbaed86c6a9a907b770b2",
+ "sha256:774d56103d9242ed968897455ef49b7d6de272000cfa83de5814273a868832f1"
+ ],
+ "index": "pypi",
+ "version": "==0.2.3"
+ },
+ "flake8-tidy-imports": {
+ "hashes": [
+ "sha256:5fc28c82bba16abb4f1154dc59a90487f5491fbdb27e658cbee241e8fddc1b91",
+ "sha256:c05c9f7dadb5748a04b6fa1c47cb6ae5a8170f03cfb1dca8b37aec58c1ee6d15"
+ ],
+ "index": "pypi",
+ "version": "==1.1.0"
+ },
+ "flake8-todo": {
+ "hashes": [
+ "sha256:6e4c5491ff838c06fe5a771b0e95ee15fc005ca57196011011280fc834a85915"
+ ],
+ "index": "pypi",
+ "version": "==0.7"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
+ "pycodestyle": {
+ "hashes": [
+ "sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
+ "sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
+ ],
+ "version": "==2.3.1"
+ },
+ "pyflakes": {
+ "hashes": [
+ "sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
+ "sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
+ ],
+ "version": "==1.6.0"
+ }
+ }
+}
diff --git a/README.md b/README.md
index d601d329..27671cd4 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,108 @@
# hacktoberbot
-A community project for Hacktoberfest 2018. A Discord bot primarily designed to help teach Python learners from the PythonDiscord community how to contribute to open source.
+A community project for [Hacktoberfest 2018](https://hacktoberfest.digitalocean.com). A Discord bot primarily designed to help teach Python learners from the PythonDiscord community how to contribute to open source.
+
+You can find our community by going to https://discord.gg/python
+
+## Motivations
+We know it can be difficult to get into the whole open source thing at first. To help out, we've decided to start a little community project during hacktober that you can all choose to contribute to if you're finding the event a little overwhelming, or if you're new to this whole thing and just want someone to hold your hand at first.
+
+## Commands
+
+!repository - Links to this repository
+
+### Git
+!Git - Links to getting started with Git page
+!Git.commit - An example commit command
+
+## Getting started
+
+If you are new to this you will find it far easier using PyCharm:
+
+### With PyCharm
+
+First things first, what is PyCharm?
+PyCharm is a Python IDE(integrated development environment) that is used to make python development quicker and easier overall.
+So now your going to need to download it [here](https://www.jetbrains.com/pycharm/).
+
+#### 1. Fork
+Ok, now you have got PyCharm downloading you are going to want to fork this project and find its git URL. To fork scroll to the top of the page and press this button.
+![](https://i.imgur.com/Saf9pgJ.png)
+Then when you new forked repository loads you are going to want to get the Git Url by clicking the green `clone or download` button and then the copy link button as seen below:
+![](https://i.imgur.com/o6kuQcZ.png)
+#### 2. Clone
+Now that you have done that you are going to want to load up Pycharm and you'll get something like this without the left sidebar:
+![](https://i.imgur.com/xiGERvR.png)
+You going to want to click Check Out from Version `Control->Git` and you'll get a popup like the one below:
+![](https://i.imgur.com/d4U6Iw7.png)
+Now paste your link in, test the connection and hit `clone`. You now have a copy of your repository to work with and it should setup your Pipenv automatically.
+#### 3. Bot
+Now we have setup our repository we need somewhere to test out bot.
+You'll need to make a new discord server:
+![](https://i.imgur.com/49gBlQI.png)
+We need to make the applicaiton for our bot... navigate over to [discordapp.com/developers](https://discordapp.com/developers) and hit new application
+![](https://i.imgur.com/UIeGPju.png)
+Now we have our discord application you'll want to name your bot as below:
+![](https://i.imgur.com/odTWSMV.png)
+To actually make the bot hit `Bot->Add Bot->Yes, do It!` as below:
+![](https://i.imgur.com/frAUbTZ.png)
+Copy that Token and put to somewhere for safe keeping.
+![](https://i.imgur.com/oEpIqND.png)
+Now to add that robot to out new discord server we need to generate an OAuth2 Url to do so navigate to the OAuth2 tab, Scroll to the OAUTH2 URL GENERATOR section, click the `Bot` checkbox in the scope section and finally hit the `administrator` checkbox in the newly formed Bot Permissions section.
+![](https://i.imgur.com/I2XzYPj.png)
+Copy and paste the link into your browser and follow the instructions to add the bot to your server - ensure it is the server you have just created.
+#### 4. Run Configurations
+Go back to PyCharm and you should have something a bit like below, Your going to want to hit the `Add Configuration` button in the top right.
+![](https://i.imgur.com/nLpDfQO.png)
+We are going to want to choose a python config as below:
+![](https://i.imgur.com/9FgCuP1.png)
+The first setting we need to change is script path as below (the start script may have changed from bot.py so be sure to click the right one
+![](https://i.imgur.com/napKLar.png)
+Now we need to add an enviroment variable - what this will do is allow us to set a value without it affact the main repository.
+To do this click the folder icon to the right of the text, then on the new window the plus icon. Now name the var `HACKTOBERBOT_TOKEN` and give the value the token we kept for safe keeping earilier.
+![](https://i.imgur.com/nZFWNaQ.png)
+Now hit apply on that window and your ready to get going!
+#### 5. Git in PyCharm
+As we work on our project we are going to want to make commits. Commits are effectively a list of changes you made to the pervious version. To make one first hit the green tick in the top right
+![](https://i.imgur.com/BCiisvN.png)
+1. Select the files you wish to commit
+2. Write a brief description of what your commit is
+3. See the actual changes you commit does here (you can also turn some of them off or on if you wish)
+4. Hit commit
+
+![](https://i.imgur.com/xA5ga4C.png)
+Now once you have made a few commits and are happy with your changes you are going to want to push them back to your fork.
+There are three ways of doing this.
+1. Using the VSC Menu `VSC->Git->Push`
+2. Using the VSC popup <code>alt-\`->Push</code>
+3. A shortcut: `ctrl+shift+K`
+
+You should get a menu like below:
+1. List of commits
+2. List of changed files
+3. Hit Push to send to fork!
+
+![](https://i.imgur.com/xA5ga4C.png)
+#### 6. Pull Requests (PR or PRs)
+Goto https://github.com/discord-python/hacktoberbot/pulls and the green New Pull Request button!
+![](https://i.imgur.com/fB4a2wQ.png)
+Now you should hit `Compare across forks` then on the third dropdown select your fork (it will be `your username/hacktoberbot`) then hit Create Pull request.
+1[](https://i.imgur.com/N2X9A9v.png)
+Now to tell other people what your PR does
+1. Title - be concise and informative
+2. Description - write what the PR changes as well as what issues it relates to
+3. Hit `Create pull request`
+![](https://i.imgur.com/OjKYdsL.png)
+
+#### 7. Wait & further reading
+At this point your PR will either be accepted or a maintainer might request some changes.
+
+So you can read up some more on [https://try.github.io](Git), [https://www.jetbrains.com/help/pycharm/quick-start-guide.html](PyCharm) or you might want to learn more about Python and discord: [https://discordpy.readthedocs.io/en/rewrite/](discord.py rewrite)
+
+
+### Without PyCharm
+The process above can be completed without PyCharm however it will be necessary to learn how to use Git, Pipenv and Environment variables.
+
+You can find tutorials for the above below:
+- Git: [try.github](http://try.github.io/)
+- Pipenv [Pipenv.readthedocs](https://pipenv.readthedocs.io)
+- Environment Variables: [youtube](https://youtu.be/bEroNNzqlF4?t=27)
diff --git a/bot/__init__.py b/bot/__init__.py
new file mode 100644
index 00000000..8cbcd121
--- /dev/null
+++ b/bot/__init__.py
@@ -0,0 +1,30 @@
+import os
+import logging.handlers
+
+# set up logging
+
+log_dir = 'log'
+log_file = log_dir + os.sep + 'hackbot.log'
+os.makedirs(log_dir, exist_ok=True)
+
+# file handler sets up rotating logs every 5 MB
+file_handler = logging.handlers.RotatingFileHandler(
+ log_file, maxBytes=5*(2**20), backupCount=10)
+file_handler.setLevel(logging.DEBUG)
+
+# console handler prints to terminal
+console_handler = logging.StreamHandler()
+console_handler.setLevel(logging.INFO)
+
+# remove old loggers if any
+root = logging.getLogger()
+if root.handlers:
+ for handler in root.handlers:
+ root.removeHandler(handler)
+
+# setup new logging configuration
+logging.basicConfig(format='%(asctime)s - %(name)s %(levelname)s: %(message)s', datefmt="%D %H:%M:%S",
+ level=logging.DEBUG,
+ handlers=[console_handler, file_handler])
+
+logging.info('Logging Process Started') \ No newline at end of file
diff --git a/bot/bot.py b/bot/bot.py
index a67fcdab..a40ed0d4 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -1,13 +1,24 @@
+from os import environ
from pathlib import Path
from sys import stderr
-from traceback import print_exc
-from os import environ
+from traceback import print_exc, format_exc
from discord.ext import commands
-
+import logging
HACKTOBERBOT_TOKEN = environ.get('HACKTOBERBOT_TOKEN')
-bot = commands.Bot(command_prefix=commands.when_mentioned_or('!'))
+
+if HACKTOBERBOT_TOKEN:
+ token_dl = len(HACKTOBERBOT_TOKEN) // 8
+ logging.info(f'Bot token loaded: {HACKTOBERBOT_TOKEN[:token_dl]}...{HACKTOBERBOT_TOKEN[-token_dl:]}')
+else:
+ logging.error(f'Bot token not found: {HACKTOBERBOT_TOKEN}')
+
+ghost_unicode = "\N{GHOST}"
+bot = commands.Bot(command_prefix=commands.when_mentioned_or(".", f"{ghost_unicode} ", ghost_unicode))
+
+logging.info('Start loading extensions from ./cogs/')
+
if __name__ == '__main__':
# Scan for files in the /cogs/ directory and make a list of the file names.
@@ -15,8 +26,14 @@ if __name__ == '__main__':
for extension in cogs:
try:
bot.load_extension(f'cogs.{extension}')
+ logging.info(f'Successfully loaded extension: {extension}')
except Exception as e:
- print(f'Failed to load extension {extension}.', file=stderr)
- print_exc()
+ logging.error(f'Failed to load extension {extension}: {repr(e)} {format_exc()}')
+ # print(f'Failed to load extension {extension}.', file=stderr)
+ # print_exc()
+
+logging.info(f'Spooky Launch Sequence Initiated...')
bot.run(HACKTOBERBOT_TOKEN)
+
+logging.info(f'HackBot has been slain!') \ No newline at end of file
diff --git a/bot/cogs/hacktoberstats.py b/bot/cogs/hacktoberstats.py
new file mode 100644
index 00000000..4e896ae9
--- /dev/null
+++ b/bot/cogs/hacktoberstats.py
@@ -0,0 +1,186 @@
+import re
+import typing
+from collections import Counter
+from datetime import datetime
+
+import aiohttp
+import discord
+from discord.ext import commands
+
+
+class Stats:
+ def __init__(self, bot):
+ self.bot = bot
+
+ @commands.command(
+ name="stats",
+ aliases=["getstats", "userstats"],
+ brief="Get a user's Hacktoberfest contribution stats",
+ )
+ async def get_stats(self, ctx, username: str):
+ """
+ Query GitHub's API for PRs created by a GitHub user during the month of October that
+ do not have an 'invalid' tag
+
+ For example:
+ !getstats heavysaturn
+
+ If a valid username is provided, an embed is generated and posted to the channel
+
+ Otherwise, post a helpful error message
+
+ The first input argument is treated as the username, any additional inputs are discarded
+ """
+ prs = await self.get_october_prs(username)
+
+ if prs:
+ stats_embed = self.build_embed(username, prs)
+ await ctx.send('Here are some stats!', embed=stats_embed)
+ else:
+ await ctx.send(f"No October GitHub contributions found for '{username}'")
+
+ def build_embed(self, username: str, prs: typing.List[dict]) -> discord.Embed:
+ """
+ Return a stats embed built from username's PRs
+ """
+ pr_stats = self._summarize_prs(prs)
+
+ n = pr_stats['n_prs']
+ if n >= 5:
+ shirtstr = f"**{username} has earned a tshirt!**"
+ elif n == 4:
+ shirtstr = f"**{username} is 1 PR away from a tshirt!**"
+ else:
+ shirtstr = f"**{username} is {5 - n} PRs away from a tshirt!**"
+
+ stats_embed = discord.Embed(
+ title=f"{username}'s Hacktoberfest",
+ color=discord.Color(0x9c4af7),
+ description=f"{username} has made {n} {Stats._contributionator(n)} in October\n\n{shirtstr}\n\n"
+ )
+
+ stats_embed.set_thumbnail(url=f"https://www.github.com/{username}.png")
+ stats_embed.set_author(
+ name="Hacktoberfest",
+ url="https://hacktoberfest.digitalocean.com",
+ icon_url="https://hacktoberfest.digitalocean.com/assets/logo-hacktoberfest.png"
+ )
+ stats_embed.add_field(
+ name="Top 5 Repositories:",
+ value=self._build_top5str(pr_stats)
+ )
+
+ return stats_embed
+
+ @staticmethod
+ async def get_october_prs(username: str) -> typing.List[dict]:
+ """
+ Query GitHub's API for PRs created during the month of October by username that do
+ not have an 'invalid' tag
+
+ If PRs are found, return a list of dicts with basic PR information
+
+ For each PR:
+ {
+ "repo_url": str
+ "repo_shortname": str (e.g. "discord-python/hacktoberbot")
+ "created_at": datetime.datetime
+ }
+
+ Otherwise, return None
+ """
+ base_url = "https://api.github.com/search/issues?q="
+ not_label = "invalid"
+ action_type = "pr"
+ is_query = f"public+author:{username}"
+ date_range = "2018-10-01..2018-10-31"
+ per_page = "300"
+ query_url = f"{base_url}-label:{not_label}+type:{action_type}+is:{is_query}+created:{date_range}&per_page={per_page}"
+
+ headers = {"user-agent": "Discord Python Hactoberbot"}
+ async with aiohttp.ClientSession() as session:
+ async with session.get(query_url, headers=headers) as resp:
+ jsonresp = await resp.json()
+
+ if "message" in jsonresp.keys():
+ # One of the parameters is invalid, short circuit for now
+ # In the future, log: jsonresp["errors"][0]["message"]
+ return
+ else:
+ if jsonresp["total_count"] == 0:
+ # Short circuit if there aren't any PRs
+ return
+ else:
+ outlist = []
+ for item in jsonresp["items"]:
+ shortname = Stats._get_shortname(item["repository_url"])
+ itemdict = {
+ "repo_url": f"https://www.github.com/{shortname}",
+ "repo_shortname": shortname,
+ "created_at": datetime.strptime(
+ item["created_at"], r"%Y-%m-%dT%H:%M:%SZ"
+ ),
+ }
+ outlist.append(itemdict)
+ return outlist
+
+ @staticmethod
+ def _get_shortname(in_url: str) -> str:
+ """
+ Extract shortname from https://api.github.com/repos/* URL
+
+ e.g. "https://api.github.com/repos/discord-python/hacktoberbot"
+ |
+ V
+ "discord-python/hacktoberbot"
+ """
+ exp = r"https?:\/\/api.github.com\/repos\/([/\-\w]+)"
+ return re.findall(exp, in_url)[0]
+
+ @staticmethod
+ def _summarize_prs(prs: typing.List[dict]) -> typing.Dict:
+ """
+ Generate statistics from an input list of PR dictionaries, as output by get_october_prs
+
+ Return a dictionary containing:
+ {
+ "n_prs": int
+ "top5": [(repo_shortname, ncontributions), ...]
+ }
+ """
+ contributed_repos = [pr["repo_shortname"] for pr in prs]
+ return {"n_prs": len(prs), "top5": Counter(contributed_repos).most_common(5)}
+
+ @staticmethod
+ def _build_top5str(stats: typing.List[tuple]) -> str:
+ """
+ Build a string from the Top 5 contributions that is compatible with a discord.Embed field
+
+ Top 5 contributions should be a list of tuples, as output in the stats dictionary by
+ _summarize_prs
+
+ String is of the form:
+ n contribution(s) to [shortname](url)
+ ...
+ """
+ baseURL = "https://www.github.com/"
+ contributionstrs = []
+ for repo in stats['top5']:
+ n = repo[1]
+ contributionstrs.append(f"{n} {Stats._contributionator(n)} to [{repo[0]}]({baseURL}{repo[0]})")
+
+ return "\n".join(contributionstrs)
+
+ @staticmethod
+ def _contributionator(n: int) -> str:
+ """
+ Return "contribution" or "contributions" based on the value of n
+ """
+ if n == 1:
+ return "contribution"
+ else:
+ return "contributions"
+
+
+def setup(bot):
+ bot.add_cog(Stats(bot))
diff --git a/bot/cogs/movie.py b/bot/cogs/movie.py
new file mode 100644
index 00000000..bb6f8df8
--- /dev/null
+++ b/bot/cogs/movie.py
@@ -0,0 +1,99 @@
+import requests
+import random
+from os import environ
+from discord.ext import commands
+from discord import Embed
+
+TMDB_API_KEY = environ.get('TMDB_API_KEY')
+TMDB_TOKEN = environ.get('TMDB_TOKEN')
+
+
+class Movie:
+ """
+ Selects a random scary movie and embeds info into discord chat
+ """
+
+ def __init__(self, bot):
+ self.bot = bot
+
+ @commands.command(name='movie', alias=['tmdb'], brief='Pick a scary movie')
+ async def random_movie(self, ctx):
+ selection = await self.select_movie()
+ movie_details = await self.format_metadata(selection)
+
+ await ctx.send(embed=movie_details)
+
+ @staticmethod
+ async def select_movie():
+ """
+ Selects a random movie and returns a json of movie details from TMDb
+ """
+
+ url = 'https://api.themoviedb.org/4/discover/movie'
+ params = {
+ 'with_genres': '27',
+ 'vote_count.gte': '5'
+ }
+ headers = {
+ 'Authorization': 'Bearer ' + TMDB_TOKEN,
+ 'Content-Type': 'application/json;charset=utf-8'
+ }
+
+ # Get total page count of horror movies
+ response = requests.get(url=url, params=params, headers=headers)
+ total_pages = response.json().get('total_pages')
+
+ # Get movie details from one random result on a random page
+ params['page'] = random.randint(1, total_pages)
+ response = requests.get(url=url, params=params, headers=headers)
+ selection_id = random.choice(response.json().get('results')).get('id')
+
+ # Get full details and credits
+ selection = requests.get(url='https://api.themoviedb.org/3/movie/' + str(selection_id),
+ params={'api_key': TMDB_API_KEY, 'append_to_response': 'credits'})
+
+ return selection.json()
+
+ @staticmethod
+ async def format_metadata(movie):
+ """
+ Formats raw TMDb data to be embedded in discord chat
+ """
+
+ tmdb_url = 'https://www.themoviedb.org/movie/' + str(movie.get('id'))
+ poster = 'https://image.tmdb.org/t/p/original' + movie.get('poster_path')
+
+ cast = []
+ for actor in movie.get('credits').get('cast')[:3]:
+ cast.append(actor.get('name'))
+
+ director = movie.get('credits').get('crew')[0].get('name')
+
+ rating_count = movie.get('vote_average') / 2
+ rating = ''
+
+ for i in range(int(rating_count)):
+ rating += ':skull:'
+
+ if (rating_count % 1) >= .5:
+ rating += ':bat:'
+
+ embed = Embed(
+ colour=0x01d277,
+ title='**' + movie.get('title') + '**',
+ url=tmdb_url,
+ description=movie.get('overview')
+ )
+ embed.set_image(url=poster)
+ embed.add_field(name='Starring', value=', '.join(cast))
+ embed.add_field(name='Directed by', value=director)
+ embed.add_field(name='Year', value=movie.get('release_date')[:4])
+ embed.add_field(name='Runtime', value=str(movie.get('runtime')) + ' min')
+ embed.add_field(name='Spooky Rating', value=rating)
+ embed.set_footer(text='powered by themoviedb.org')
+
+ return embed
+
+
+def setup(bot):
+ bot.add_cog(Movie(bot))
diff --git a/bot/cogs/template.py b/bot/cogs/template.py
index 89f12fe1..b3f4da21 100644
--- a/bot/cogs/template.py
+++ b/bot/cogs/template.py
@@ -12,6 +12,9 @@ class Template:
@commands.command(name='repo', aliases=['repository', 'project'], brief='A link to the repository of this bot.')
async def repository(self, ctx):
+ """
+ A command to send the hacktoberbot github project
+ """
await ctx.send('https://github.com/discord-python/hacktoberbot')
@commands.group(name='git', invoke_without_command=True)
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 850f73bb..00000000
--- a/requirements.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-aiohttp==3.4.4
-async-timeout==3.0.0
-attrs==18.2.0
-chardet==3.0.4
-discord.py==1.0.0a0
-idna==2.7
-idna-ssl==1.1.0
-multidict==4.4.2
-websockets==6.0
-yarl==1.2.6 \ No newline at end of file