Skip to content

Commit

Permalink
Revert "Revert "Command refactor""
Browse files Browse the repository at this point in the history
  • Loading branch information
wgranados committed Aug 8, 2017
1 parent 2b4cb0f commit 179512b
Show file tree
Hide file tree
Showing 51 changed files with 2,281 additions and 1,286 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ install:
- pip install -r requirements.txt
# command to run tests
script:
- python3 -m pytest --cov-report term-missing --doctest-modules --cov=. test
- python3 -m pytest --pep8 --cov-report term-missing --doctest-modules --cov=. test
- COVERALLS_REPO_TOKEN=$COVERALLS_TOKEN coveralls
67 changes: 24 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,36 @@ Pokemon Showdown chat bot made in Python 3.

Functionality
-------------
This chat bot supports the following
This chat bot supports some of the following. More functionality can be found in our [wiki](https://github.com/wgma00/quadbot/wiki/Commands)
- Moderation
- Games and leader-boards
- LaTeX compilation
- Battling
- Sentence generator using Markov chains
- More functionality detailed in ``commands.py`` and ``commands.md``


Structure
---------

The Showdown bot is built from four main components:
The Showdown bot is built from two main components:

- app.py which contains the class PSBot, the central workings, and is where most of the connections to other pieces of
- ``app.py`` which contains the class PSBot, the central workings, and serves it's purpose of parsing messages and delegating information to proper modules.
the app is created.
- The class PSBot is extended from the base class PokemonShowdownBot found in robot.py, which contains almost all basic
functions that are required for the bot to function. Most of the more general functions like ``join``, ``leave`` and
``say`` are defined here.
- The third file that this relies on is room.py, as every room joined creates new room objects that store information
for the bot, such as ``userlists`` and ``allowed uses``.

- ``commands.py`` delegates or handles most of the commands given by users. You can define your own commands here
or have them defined in the plugins module. These are considered ``External Commands``.
- ``commands.py`` which parses the user input and determines which command to pass user's arguments to.

Most of the fun modules are implemented in the plugins section and are treated as stand alone programs, so to not
interfere up the core functionality of the bot. These modules are meant to contain the implementation of chat games.
If you want to develop your own feature you should create a submodule in plugins, and map the handler for the command
in the ``__init__.py`` module handler ``PluginCommands`` section. Here are some examples of implemented commands.

- ``anagram`` is a simple game where the user has to determine, from a randomize string, the original string it was
created from. This is pretty famous game in rooms like gamecorner. The game currently only chooses words for Pokemon
related stuff like names, and battle moves.

- ``periodic`` is an academic game where the user is given a random word from the BSD dictionary, and must determine a
a correct sequence of chemistry symbols (i.e. H, He, Li, etc) which spell out the word.



Style
-------
This project follows the PEP8 Standard and also the Google Python Standard. There may be some discrepancies, as I
am trying to claw my way closer to these standards.
Follow the PEP8 Standard and also the Google Python Standard for documentation. W503 (line length 80 chars) is ignored
since some lines would look unintuitive when spread out.

Setting up
---------
The following are the necessary dependencies for running this software.

### Operating System:
- APT/RPM based linux distribution like Debian or Fedora
- Some functionality may still work on Windows, but have not been fully tested. Use at your own discretion.
- APT/RPM based Linux distribution like Debian or Fedora
- Some functionality may still work on Windows and MacOS, but have not been fully tested. Use at your own discretion.
- If you're not running Linux natively then consider running this project in [vagrant](https://www.vagrantup.com/); more info provided below.

### LaTeX:
- Requires the following to be installed, ``texlive`` and ``poppler-utils``
Expand All @@ -63,29 +43,30 @@ The following are the necessary dependencies for running this software.
- Requires that ``gcalccmd`` is installed

#### Python:
- Python 3.4, not tested for any other versions yet. And no plans are made to make it Python 2 compliant.
- PIP - package manager for python
- Python 3.4+. No plans are made to make this support earlier versions.
- [PIP](https://pip.pypa.io/en/stable/). Package manager for python

#### Guide:
0. (optional) setup a virtualenv with the following ``virtualenv -p /usr/bin/python3 pokemonshowdownbot && source pokemonshowdownbot/bin/activate``
0. (optional) Setting up an environment separate from your host OS or global python interpreter to avoid dependency conflicts.

- If you're on Linux you might find setting up a virtualenv with the following ``virtualenv -p /usr/bin/python3 quadbot && source quadbot/bin/activate`` will be helpful.
- If you're on MacOS or Windows setting up [vagrant](https://www.vagrantup.com/docs/installation/) will be useful. Then you can use ``vagrant init && vagrant ssh`` to load this project's production environment.

1. Clone the git repo to your desired location
2. Install pip dependencies manually with `pip install -r requirements.txt`, and install the aforementioned software manually.
2. Run ``git update-index --assume-unchanged plugins/SecretCommands.py`` to stop tracking SecretCommands.py
3. Install pip dependencies manually with `pip install -r requirements.txt`, and install the aforementioned software manually.
Or you run the install script using ``chmod +x install.sh && ./install.sh``
3. Follow the instructions in `details-example.yaml` to configure your bot for login
4. Run using `python3 app.py`
5. (optional) Test for errors you can run ``test.sh`` using ``chmod +x test.sh && ./test.sh``

4. Follow the instructions in `details-example.yaml` to configure your bot for login
5. Run using `python3 app.py`
6. (optional) Test for errors you can run ``test.sh`` using ``chmod +x test.sh && ./test.sh``

### Docker (optional)
A docker image is available with the all of the necessary dependencies installed. You will still need to follow steps
3-4 in the above guide. ``docker pull wgma/pokemonshowdownbot``

License
-------

This project is distributed under the terms of the [GPLv3 and MIT License][1].

[1]: https://github.com/wgma00/PokemonShowdownBot/blob/master/NOTICE
[1]: https://github.com/wgma00/quadbot/blob/master/NOTICE

Credits
-------
Expand All @@ -94,5 +75,5 @@ Maintainer

- wgma00

Previous maintainer:
Upstream maintainer:
- QuiteQuiet
77 changes: 77 additions & 0 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# The most common configuration options are documented and commented below.
# For a complete reference, please see the online documentation at
# https://docs.vagrantup.com.

# Every Vagrant development environment requires a box. You can search for
# boxes at https://atlas.hashicorp.com/search.
config.vm.box = "ubuntu/trusty64"

# Disable automatic box update checking. If you disable this, then
# boxes will only be checked for updates when the user runs
# `vagrant box outdated`. This is not recommended.
# config.vm.box_check_update = false

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine. In the example below,
# accessing "localhost:8080" will access port 80 on the guest machine.
# NOTE: This will enable public access to the opened port
# config.vm.network "forwarded_port", guest: 80, host: 8080

# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
# config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

# Create a private network, which allows host-only access to the machine
# using a specific IP.
# config.vm.network "private_network", ip: "192.168.33.10"

# Create a public network, which generally matched to bridged network.
# Bridged networks make the machine appear as another physical device on
# your network.
# config.vm.network "public_network"

# Share an additional folder to the guest VM. The first argument is
# the path on the host to the actual folder. The second argument is
# the path on the guest to mount the folder. And the optional third
# argument is a set of non-required options.
# config.vm.synced_folder "../data", "/vagrant_data"

# Provider-specific configuration so you can fine-tune various
# backing providers for Vagrant. These expose provider-specific options.
# Example for VirtualBox:
#
# config.vm.provider "virtualbox" do |vb|
# # Display the VirtualBox GUI when booting the machine
# vb.gui = true
#
# # Customize the amount of memory on the VM:
# vb.memory = "1024"
# end
#
# View the documentation for the provider you are using for more
# information on available options.

# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
# such as FTP and Heroku are also available. See the documentation at
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
# config.push.define "atlas" do |push|
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
# end

# Enable provisioning with a shell script. Additional provisioners such as
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
# documentation for more information about their specific syntax and use.
# config.vm.provision "shell", inline: <<-SHELL
# apt-get update
# apt-get install -y apache2
# SHELL
end
47 changes: 26 additions & 21 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
import details



class PSBot(PokemonShowdownBot):
"""Mainly a wrapper class for the Robot class, implementing required methods.
Expand Down Expand Up @@ -98,8 +97,10 @@ def splitMessage(self, ws, message):
By default, nothing is raised. But handlers which this method delegates tasks to
may produce exceptions, so best follow the path to the individual module.
"""
if not message: return
if '\n' not in message: self.parseMessage(message, '')
if not message:
return
if '\n' not in message:
self.parseMessage(message, '')

room = ''
msg = message.split('\n')
Expand Down Expand Up @@ -144,7 +145,8 @@ def handleJoin(self, room, message):
room.doneLoading()
user = User(message, message[0], self.isOwner(message))
if not room.addUser(user):
return self.takeAction(room.title, user, 'roomban', "You are blacklisted from this room, so please don't come here.")
return self.takeAction(room.title, user, 'roomban',
"You are blacklisted from this room, so please don't come here.")

# If the user have a message waiting, tell them that in a pm
if self.usernotes.shouldNotifyMessage(user.id):
Expand All @@ -167,9 +169,10 @@ def parseMessage(self, msg, roomName):
Raises:
None.
"""
if not msg.startswith('|'): return
if not msg.startswith('|'):
return
message = self.escapeMessage(msg).split('|')
room = self.getRoom(roomName)
room = Room('Empty') if not roomName else self.getRoom(roomName)

# Logging in
if message[1] == "challstr":
Expand All @@ -188,7 +191,7 @@ def parseMessage(self, msg, roomName):
if format in self.bh.supportedFormats:
team = self.bh.getRandomTeam(format)
self.send('|/utm {}'.format(team))
self.send('|/accept {name}'.format(name = opp))
self.send('|/accept {name}'.format(name=opp))
else:
self.sendPm(opp, "Sorry, I can't accept challenges in that format :(")
elif 'updatesearch' in message[1]:
Expand Down Expand Up @@ -263,7 +266,8 @@ def parseMessage(self, msg, roomName):
self.log('Command', saidMessage, user.id)

res = self.do(self, command, room, saidMessage[len(command) + 1:].lstrip(), user)
if not res.text or res.text == 'NoAnswer': return
if not res.text or res.text == 'NoAnswer':
return

if self.userHasPermission(user, room.broadcast_rank) or res.ignoreBroadcastPermission:
if not res.ignoreEscaping:
Expand All @@ -285,53 +289,55 @@ def parseMessage(self, msg, roomName):
self.takeAction(room.title, user, action, reason)

if type(room.activity) == Workshop:
room.activity.logSession(room.title, user.rank+user.name,
message[4])
room.activity.logSession(room.title, user.rank + user.name, message[4])

elif 'pm' in message[1].lower():
user = User(message[2][1:], message[2][0], self.isOwner(message[2]))
if self.userIsSelf(user.id): return
if self.userIsSelf(user.id):
return
if message[4].startswith("/invite"):
if not message[4][8:] == "lobby":
if user.hasRank("+"):
if user.hasRank("&"):
self.joinRoom(message[4][8:])
self.log("Invite", message[4], user.id)
else:
self.sendPm(user.id, ("Only global voices (+) and up "
"can add me to rooms, sorry :("))
self.sendPm(user.id, "Only global leaders (&) and up can add me to rooms, sorry :(")

message[4] = '|'.join(message[4:])
if message[4].startswith(self.commandchar) and message[4][1:] and message[4][1].isalpha():
command = self.extractCommand(message[4])
self.log('Command', message[4], user.id)
params = message[4][len(command)+len(self.commandchar):]
params = message[4][len(command) + len(self.commandchar):]
params = params.lstrip()
response = self.do(self, command, Room('pm'), params, user)
if not response.text or response.text == 'NoAnswer': return
if not response.text or response.text == 'NoAnswer':
return
self.sendPm(user.id, response.text)

# Tournaments
elif 'tournament' == message[1]:
if 'create' in message[2]:
room.createTour(self.ws, message[3], self.bh)

if room.loading: return
if room.loading:
return
# Tour was created, join it if in supported formats
if details.joinTours and room.tour.format in self.bh.supportedFormats:
room.tour.joinTour()
elif 'end' == message[2]:
if not room.loading:
winner, tier = room.getTourWinner(message[3])
if self.name in winner:
self.say(room.title, 'I won the {form} tournament :o'.format(form = tier))
self.say(room.title, 'I won the {form} tournament :o'.format(form=tier))
else:
self.say(room.title, 'Congratulations to {name} for winning :)'.format(name = ', '.join(winner)))
self.say(room.title, 'Congratulations to {name} for winning :)'.format(name=', '.join(winner)))
room.endTour()
elif "forceend" in message[2]:
room.endTour()
else:
# This is for general tournament updates
if not room.tour or room.loading: return
if not room.tour or room.loading:
return
room.tour.onUpdate(message[2:])


Expand All @@ -356,4 +362,3 @@ def parseMessage(self, msg, roomName):
except SystemExit:
print("bot closed by SystemExit")
exit()

Loading

0 comments on commit 179512b

Please sign in to comment.