diff --git a/.gitignore b/.gitignore index 894a44c..2bb111d 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ venv.bak/ # mypy .mypy_cache/ + +.vscode/ +config.json diff --git a/FPLbot/bot.py b/FPLbot/bot.py new file mode 100644 index 0000000..9fb7542 --- /dev/null +++ b/FPLbot/bot.py @@ -0,0 +1,90 @@ +import asyncio +import json +import logging +import os +from datetime import datetime + +import aiohttp +import praw +from fpl import FPL +from fpl.utils import position_converter +from pymongo import MongoClient + +from utils import create_logger, get_player_table, update_players + +dirname = os.path.dirname(os.path.realpath(__file__)) +logger = create_logger() + + +class FPLBot: + def __init__(self, config, session): + self.client = MongoClient() + self.fpl = FPL(session) + self.reddit = praw.Reddit( + client_id=config.get("CLIENT_ID"), + client_secret=config.get("CLIENT_SECRET"), + password=config.get("PASSWORD"), + user_agent=config.get("USER_AGENT"), + username=config.get("USERNAME")) + self.subreddit = self.reddit.subreddit(config.get("SUBREDDIT")) + + async def get_price_changers(self): + """Returns a list of players whose price has changed since the last + time the database was updated. + """ + logger.info("Retrieving risers and fallers.") + new_players = await self.fpl.get_players(include_summary=True) + old_players = [player for player in self.client.fpl.players.find()] + + risers = [] + fallers = [] + + for new_player in new_players: + try: + old_player = next(player for player in old_players + if player["id"] == new_player.id) + # New player has been added to the game + except StopIteration: + logger.info(f"New player added: {new_player}.") + continue + + if old_player["now_cost"] > new_player.now_cost: + fallers.append(new_player) + elif old_player["now_cost"] < new_player.now_cost: + risers.append(new_player) + + return risers, fallers + + async def post_price_changes(self): + """Posts the price changes to Reddit.""" + risers, fallers = await self.get_price_changers() + risers_table = get_player_table(risers, True) + fallers_table = get_player_table(fallers, False) + + post_template = open(f"{dirname}/../post_template.md").read() + post_body = post_template.format( + risers_number=len(risers), + risers_table=risers_table, + fallers_number=len(fallers), + fallers_table=fallers_table + ) + + today = datetime.now() + current_date = f"({today:%B} {today.day}, {today.year})" + post_title = f"Player Price Changes {current_date}" + + logger.info(f"Posting price changes to Reddit.\n\n{post_body}") + self.subreddit.submit(post_title, selftext=post_body) + await update_players() + + +async def main(config): + async with aiohttp.ClientSession() as session: + fpl_bot = FPLBot(config, session) + + await fpl_bot.post_price_changes() + + +if __name__ == "__main__": + config = json.loads(open(f"{dirname}/../config.json").read()) + asyncio.run(main(config)) diff --git a/FPLbot/utils.py b/FPLbot/utils.py new file mode 100644 index 0000000..09b7c26 --- /dev/null +++ b/FPLbot/utils.py @@ -0,0 +1,74 @@ +import asyncio +import logging +import os + +import aiohttp +from fpl import FPL +from fpl.utils import position_converter, team_converter +from pymongo import MongoClient, ReplaceOne + +client = MongoClient() +database = client.fpl +logger = logging.getLogger("FPLbot") + + +def create_logger(): + """Creates a logger object for use in logging across all files. + + See: https://docs.python.org/3/howto/logging-cookbook.html + """ + dirname = os.path.dirname(os.path.realpath(__file__)) + + logger = logging.getLogger("FPLbot") + logger.setLevel(logging.DEBUG) + + fh = logging.FileHandler(f"{dirname}/FPLbot.log") + fh.setLevel(logging.DEBUG) + + ch = logging.StreamHandler() + ch.setLevel(logging.ERROR) + + formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - " + "%(message)s") + + fh.setFormatter(formatter) + ch.setFormatter(formatter) + + logger.addHandler(fh) + logger.addHandler(ch) + return logger + + +async def update_players(): + """Updates all players in the database.""" + async with aiohttp.ClientSession() as session: + fpl = FPL(session) + players = await fpl.get_players(include_summary=True, return_json=True) + + requests = [ReplaceOne({"id": player["id"]}, player, upsert=True) + for player in players] + + logger.info("Updating players in database.") + database.players.bulk_write(requests) + + +def get_player_table(players, risers=True): + """Returns the table used in the player price change posts on Reddit.""" + table_header = ("|Name|Team|Position|Ownership|Price|∆|Form|\n" + "|:-|:-|:-|:-:|:-:|:-:|:-:|\n") + + table_body = "\n".join([ + f"|{player.web_name}|" + f"{team_converter(player.team)}|" + f"{position_converter(player.element_type)}|" + f"{player.selected_by_percent}%|" + f"£{player.now_cost / 10.0:.1f}|" + f"{'+' if risers else '-'}£{abs(player.cost_change_event / 10.0):.1f}|" + f"{sum([fixture['total_points'] for fixture in player.history[-5:]])}|" + for player in players]) + + return table_header + table_body + + +if __name__ == "__main__": + asyncio.run(update_players()) diff --git a/README.md b/README.md index 0890852..3b07c42 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,35 @@ -# FantasyPL_bot -A bot for the FantasyPL subreddit +# FPLbot + +FPLbot is a bot made for the subreddit [/r/FantasyPL](https://www.reddit.com/r/FantasyPL/). +It can also be used for other subreddits by changing the values in the +configuration file. + +Its current features are: + +* Posting the price changes of Fantasy Premier League players + +## Installation + +FPLbot uses MongoDB to store players in a database, and so it is required to +have MongoDB installed. Other than that, it uses [fpl](https://github.com/amosbastian/fpl) +to retrieve information from Fantasy Premier League's API, and thus requires +Python 3.6+. + + git clone git@github.com:amosbastian/FPLbot.git + cd FPLbot + pip install -r requirements.txt + +Once installed you can schedule a cron job to run the bot whenever you want! + +## Configuration + +|Option|Value| +|:-|:-| +|USERNAME|The bot's username| +|PASSWORD|The bot's password| +|CLIENT_ID|The bot's client ID| +|CLIENT_SECRET|The bot's client secret| +|USER_AGENT|A unique identifier that helps Reddit determine the source of network requests| +|SUBREDDIT|The subreddit the bot will post to| + +For more information about how to set up a bot see [Reddit's guide](https://github.com/reddit-archive/reddit/wiki/OAuth2-Quick-Start-Example#first-steps). diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..5645674 --- /dev/null +++ b/config.json.example @@ -0,0 +1,8 @@ +{ + "USERNAME": "bigluke575", + "PASSWORD": "baldfraud", + "CLIENT_ID": "p-jcoLKBynTLew", + "CLIENT_SECRET": "gko_LXELoV07ZBNUXrvWZfzE3aI", + "USER_AGENT": "The original FPLbot.", + "SUBREDDIT": "FantasyPL", +} diff --git a/post_template.md b/post_template.md new file mode 100644 index 0000000..4b1cfb6 --- /dev/null +++ b/post_template.md @@ -0,0 +1,17 @@ +### Risers ({risers_number}) + +{risers_table} + +  + +### Fallers ({fallers_number}) + +{fallers_table} + +  + +^∆ ^= ^price ^change ^this ^gameweek. ^Form ^= ^points ^last ^5 ^gameweeks. + +--- + +^Made ^by ^/u/esoemah. ^Source: ^https://github.com/amosbastian/FPLbot diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..80a427e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,28 @@ +aiohttp==3.5.4 +appdirs==1.4.3 +async-timeout==3.0.1 +atomicwrites==1.2.1 +attrs==18.2.0 +certifi==2018.11.29 +chardet==3.0.4 +Click==7.0 +colorama==0.4.1 +fpl==0.6.1 +idna==2.8 +more-itertools==5.0.0 +multidict==4.5.2 +pep8==1.7.1 +pluggy==0.8.1 +praw==6.1.1 +prawcore==1.0.0 +PTable==0.9.2 +py==1.7.0 +pymongo==3.7.2 +pytest==4.2.0 +pytest-aiohttp==0.3.0 +requests==2.21.0 +six==1.12.0 +update-checker==0.16 +urllib3==1.24.1 +websocket-client==0.54.0 +yarl==1.3.0