GithubHelp home page GithubHelp logo

will's Introduction

Will's smilling face

Meet Will.

Will is the friendliest, easiest-to-teach bot you've ever used. He works on hipchat, in rooms and 1-1 chats. Batteries included.

Quick-links:

Will can:

Hear

class CookiesPlugin(WillPlugin):

    @hear("cookies")
    def will_likes_cookies(self, message):
        self.say("I LOOOOVE COOOKIEEESS!!!")

Reply

# All examples below are impled to be on a subclass of WillPlugin

@respond_to("^hi")   # Basic
def hi(self, message):
    self.reply(message, "hello, %s!" % message.sender.nick)

@respond_to("award (?P<num_stars>\d)+ gold stars? to (?P<user_name>.*)")   # With named matches
def gold_stars(self, message, num_stars=1, user_name=None):
    stars = self.load("gold_stars", {})
    stars[user_name] += num_stars
    self.save("gold_stars", stars)

    self.say("Awarded %s stars to %s." % (num_stars, user_name), message=message)

Do things on a schedule.

@periodic(hour='10', minute='0', day_of_week="mon-fri")
def standup(self):
    self.say("@all Standup! %s" % settings.WILL_HANGOUT_URL)

Do things randomly

@randomly(start_hour='10', end_hour='17', day_of_week="mon-fri", num_times_per_day=1)
def walkmaster(self):
    self.say("@all time for a walk!")

Schedule things on the fly

@randomly(start_hour='10', end_hour='17', day_of_week="mon-fri", num_times_per_day=1)
def walkmaster(self):
    now = datetime.datetime.now()
    in_5_minutes = now + datetime.timedelta(minutes=5)

    self.say("@all Walk happening in 5 minutes!")
    self.schedule_say("@all It's walk time!", in_5_minutes)

Remember

Will can remember almost any python object, even across reboots.

self.save("my_key", "my_value")
self.load("my_key", "default value")

Respond to webhooks

# Simply
@route("/ping")
def ping(self):
    return "PONG"

# With templates
@route("/keep-alive")
@rendered_template("keep_alive.html")
def keep_alive(self):
    return {}

# With full control, multiple templates, still connected to chat.
@route("/complex_page/<page_id:int>", method=POST)
def complex_page(self, page_id):
    # Talk to chat
    self.say("Hey, somebody's loading the complex page.")
    # Get JSON post data:
    post_data = self.request.json

    # Render templates
    header = rendered_template("header.html", post_data)
    some_other_context = {"page_id": page_id}
    some_other_context["header"] = header
    return rendered_template("complex_page.html", some_other_context)

Talk in HTML and plain text

roster.py

@respond_to("who do you know about\?")
def list_roster(self, message):
    context = {"internal_roster": self.internal_roster.values(),}
    self.say(rendered_template("roster.html", context), message=message, html=True)

roster.html

Here's who I know: <br>
<ul>
    {% for user in internal_roster %}
    <li><b>@{{user.nick|lower}}</b> - {{user.name}}.  (# {{user.hipchat_id}})</li>
    {% endfor %}
</ul>

Understand natural time

@respond_to("remind me to (?P<reminder_text>.*?) (at|on) (?P<remind_time>.*)")
def remind_me_at(self, message, reminder_text=None, remind_time=None):
    
    # Parse the time
    now = datetime.datetime.now()
    parsed_time = self.parse_natural_time(remind_time)

    # Make a friendly reply
    natural_datetime = self.to_natural_day_and_time(parsed_time)

    # Schedule the reminder    
    formatted_reminder_text = "@%(from_handle)s, you asked me to remind you %(reminder_text)s" % {
        "from_handle": message.sender.nick,
        "reminder_text": reminder_text,
    }
    self.schedule_say(formatted_reminder_text, parsed_time, message=message)

    # Confirm that he heard you.
    self.say("%(reminder_text)s %(natural_datetime)s. Got it." % locals(), message=message)

Then:

You: @will remind me to take out the trash at 6pm tomorrow
Will: take out the trash tomorrow at 6pm. Got it.

or:

You: @will remind me to take out the trash at 6pm monday
Will: take out the trash December 16 at 6pm. Got it.

Document himself

@respond_to("image me (?P<search_query>.*)$")
def image_me(self, message, search_query):
    """image me ___ : Search google images for ___, and post a random one."""
    pass

Then:

You: @will help
Will: Sure thing, Steven.
Will: Here's what I know how to do:
   image me ___ : Search google images for ___, and post a random one.

A lot more

We've built will to be easy to extend, change, and write. Check out the plugins directory for lots more examples!

You can also take a look at our will. He's open-source, handles our deploys and lots of fun things - enjoy!

High-level API

@hear(regex, include_me=False, case_sensitive=False, multiline=False)
  • regex: a regular expression to match.
  • include_me: whether will should hear what he says
  • case_sensitive: should the regex be case sensitive?

Plugin method decorators

@hear(regex, include_me=False, case_sensitive=False, multiline=False)
  • regex: a regular expression to match.
  • include_me: whether will should hear what he says
  • case_sensitive: should the regex be case sensitive?
@respond_to(regex, include_me=False, case_sensitive=False, multiline=False)
  • regex: a regular expression to match.
  • include_me: whether will should hear what he says
  • case_sensitive: should the regex be case sensitive?
@periodic(**periodic_args)

Args are parsed by apscheduler.

  • year: 4-digit year number
  • month: month number (1-12)
  • day: day of the month (1-31)
  • week: ISO week number (1-53)
  • day_of_week: number or name of weekday (0-6 or mon,tue,wed,thu,fri,sat,sun)
  • hour: hour (0-23)
  • minute: minute (0-59)
  • second: second (0-59)

The following expressions are valid:

  • * (any): Fire on every value
  • */a (any): Fire every a values, starting from the minimum
  • a-b (any): Fire on any value within the a-b range (a must be smaller than b)
  • a-b/c (any): Fire every c values within the a-b range
  • xth y (day): Fire on the x -th occurrence of weekday y within the month
  • last x (day): Fire on the last occurrence of weekday x within the month
  • last (day): Fire on the last day within the month
  • x,y,z (any): Fire on any matching expression; can combine any number of any of the above expressions
@randomly(start_hour=0, end_hour=23, day_of_week="*", num_times_per_day=1)
  • start_hour: the earliest a random task could fall.
  • end_hour: the latest hour a random task could fall (inclusive, so end_hour:59 is a possible time.)
  • day_of_week: valid days of the week, same expressions available as @periodic
  • num_times_per_day: number of times this task should happen per day.
@route(routing_rule)

Routes a bottle request. Note that self.request will contain the bottle request object

  • routing_rule: A bottle routing rule. Args in the bottle rule are automatically passed into the function.
@rendered_template("template_name.html")
  • "template_name.html": the path to the template, relative to the templates directory. Assumes the function returns a dictionary, to be used as the template context.

High-level chat methods

A note about multiple rooms: For all methods that include message=None, room=None, both are optional, unless you have multiple chat rooms. If you have multiple rooms, you will need to specify either message or room. To reply to the room the message came from, use message. To send to a specific room, use room.

Typically, it's considered good form to pass message=message along when you have it - it'll save you from needing to refactor when you do have multiple rooms!

self.say(content, message=None, room=None, html=False, color="green", notify=False)

Speak directly into a room or 1-1 message.

  • content: the content you want to send to the room. HTML or plain text.
  • message: (optional) The incoming message object
  • room: (optional) The room object (from self.available_rooms) to send the message to.
  • html: if the message is HTML. True or False.
  • color: the hipchat color to send. "yellow", "red", "green", "purple", "gray", or "random". Default is "green".
  • notify: whether the message should trigger a 'ping' notification. True or False.
self.reply(message, content, html=False, color="green", notify=False)

Reply to a direct message, either @will'd, or in a 1-1 room. Note: html is stripped for 1-1 messages

  • message: The incoming message object. Required
  • content: the content you want to send to the room. HTML or plain text.
  • html: if the message is HTML. True or False.
  • color: the hipchat color to send. "yellow", "red", "green", "purple", "gray", or "random". Default is "green".
  • notify: whether the message should trigger a 'ping' notification. True or False.
self.set_topic(topic, message=None, room=None)

Set the room topic. Note: you can't set the topic of a 1-1 chat. Will will complain politely.

  • topic: The string you want to set the topic to
  • message: (optional) The incoming message object
  • room: (optional) The room object (from self.available_rooms) to send the message to.
self.schedule_say(content, when, message=None, room=None, html=False, color="green", notify=False)

Schedule a .say() for a future time

  • content: the content you want to send to the room. HTML or plain text.
  • when: when you want the message to be said. Python datetime object.
  • message: (optional) The incoming message object
  • room: (optional) The room object (from self.available_rooms) to send the message to.
  • html: if the message is HTML. True or False.
  • color: the hipchat color to send. "yellow", "red", "green", "purple", "gray", or "random". Default is "green".
  • notify: whether the message should trigger a 'ping' notification. True or False.

High-level helpers

self.parse_natural_time(time_string)

Parses a textual time string using parsedatetime

  • time_string: the time string you want to parse.
self.to_natural_day_and_time(my_datetime)

Converts a python datetime into a human-friendly string using natural, and a bit of extra code.

  • my_datetime: the python datetime to convert
self.rendered_template(template_name, context={})

Renders a template using Jinja

  • template_name: path to the template, relative to /templates.
  • context: a dictionary to render the template with.

Advanced Topics

Multiple Chat Rooms

Will fully supports multiple chat rooms. To take advantage of them, you'll need to:

  1. Include both rooms, semicolon-separated in WILL_ROOMS
  2. Make sure to include either message or room on any calls to .say(), set_topic(), or schedule_say() you have a specific room in mind for, or don't want going to the default room.

Auto documentation

Will has two kinds of help: regular help, and programmer help.

Regular help lists all listen/reply methods that have docstrings. If the docstring includes a colon, i.e. "my command: does cool stuff", it will be formatted nicely as "my command: does cool stuff"

Programmer help lists the regexes for all listen/reply methods. It's significantly less friendly, but still useful for more buried/admin-type functions.

Installation

Starting a new will

  1. pip install will

  2. Install and configure redis

  3. Set environment variables. The easiest way to do this is to put all of the following in your virtualenv's bin/postactivate file, so it's always there when you workon will, and things Just Work:

     # Required
     export WILL_USERNAME='[email protected]'
     export WILL_PASSWORD='asj2498q89dsf89a8df'
     export WILL_V2_TOKEN='asdfjl234jklajfa3azfasj3afa3jlkjiau'
     export WILL_ROOMS='Testing, Will Kahuna;GreenKahuna'  # Semicolon-separated, so you can have commas in names.
     export WILL_NAME='William T. Kahuna'  # Must be the *exact, case-sensitive* full name from hipchat.
     export WILL_HANDLE='will'  # Must be the exact handle from hipchat.
     export WILL_REDIS_URL="redis://localhost:6379/7"
    
     # Optional
     export WILL_TOKEN='kjadfj89a34878adf78789a4fae3' # for v1 API. Must be an 'admin' token, not just notification. Optional, unless you have > ~30 rooms.
     export WILL_DEFAULT_ROOM='[email protected]'  # Default room: (otherwise defaults to the first of WILL_ROOMS)
     export WILL_HANGOUT_URL='https://plus.google.com/hangouts/_/event/ceggfjm3q3jn8ktan7k861hal9o...'  # For google hangouts:
     export WILL_DEFAULT_FROM_EMAIL="[email protected]"
     export WILL_MAILGUN_API_KEY="key-12398912329381"
     export WILL_MAILGUN_API_URL="example.com"
     export WILL_LOGLEVEL="DEBUG"
    
     # For Production:
     export WILL_HTTPSERVER_PORT="80"  # Port to listen to (defaults to $PORT, then 80.) Set > 1024 to run without elevated permission.
     export WILL_URL="http://my-will.herokuapp.com" # If will isn't accessible at localhost (heroku, etc). No trailing slash.:
    
  4. Run generate_will_project. This will create the following structure (you can also create it by hand):

    /plugins
        __init__.py
        hello.py
    /templates
    .gitignore
    run_will.py
    requirements.txt
    Procfile
    README.md
    

    Where run_will.py is

    #!/usr/bin/env python
    from will.main import WillBot
    
    if __name__ == '__main__':
        bot = WillBot(plugins_dirs=["plugins",], template_dirs=["templates",])
        bot.bootstrap()
  5. Just run ./run_will.py!

Deploying on Heroku

  1. Create a new will, as above.

  2. Set up your heroku app, and a redis addon.

    heroku create our-will-name
    heroku addons:add rediscloud   # Or redistogo, etc. Your call.
  3. Add all the needed environment variables:

    heroku config:set \
    WILL_URL="http://our-will-name.herokuapp.com" \
    WILL_USERNAME='[email protected]' \
    WILL_PASSWORD='asj2498q89dsf89a8df' \
    WILL_TOKEN='kjadfj89a34878adf78789a4fae3' \
    WILL_V2_TOKEN='asdfjl234jklajfa3azfasj3afa3jlkjiau' \
    WILL_ROOMS='Testing, Will Kahuna;GreenKahuna' \
    WILL_NAME='William T. Kahuna' \
    WILL_HANDLE='will' \
    WILL_REDIS_URL="`heroku config:get REDISCLOUD_URL`" \
    WILL_DEFAULT_ROOM='[email protected]' \
    WILL_HANGOUT_URL='https://plus.google.com/hangouts/_/event/ceggfjm3q3jn8ktan7k861hal9o...' \
    TZ="America/Los_Angeles"
    # Or whatever your time zone is.
  4. git push heroku

  5. heroku scale web=1

Hacking on will

Most of the time, you'll want to start a new will, as above, and add your functionality to your project. However, if you'd like to make improvements to will itself (PRs are welcome!), here's how to test.

  1. Fork this repo.
  2. Clone down a copy, set up redis and the env, as above.
  3. Run ./start_dev_will.py to start up just core will.

The Shoulders of Giants

Will leverages some fantastic libraries. He wouldn't exist without them.

Will was originally written by Steven Skoczen at GreenKahuna. The rest of the GK team has also pitched in, including:

Will's also has had help from lots of coders. Alphabetically:

  • adamgilman gave you the friendly error messages when the hipchat key was invalid.
  • bfhenderson removed dependence on the v1 token.
  • crccheck gave you friendly error messages if your WILL_ROOMS was wrong.
  • dpoirier figured out how to properly ignore the initial catch-up messages, and gave you log-level control.
  • jbeluch found a bug with get_roster not populating in time.
  • michaeljoseph suggested improvements to setup and requirements.txt format.
  • quixeybrian wrote the awesome new help system and stopped the rate limit nightmare.
  • rbp fixed a bug with room not being passed along properly to messages.

Releases

0.4.9 - May 28, 2014

  • Passing a room to a .say() now works properly, thanks to rbp.
  • New optional WILL_LOGLEVEL setting, thanks to dpoirier.

0.4.8 - May 21, 2014

  • Will now ignores all previously sent messages properly, by passing in bot as the resource instead of an ugly time hack, thanks to dpoirier.

0.4.7 - May 15, 2014

  • Will now prints a helpful message if one of your WILL_ROOMS is wrong, and continues starting, instead of crashing in a fiery ball, thanks to crccheck.

0.4.6 - May 5, 2014

  • @route decorators now honor all bottle arguments, most helpfully method!

0.4.5 - May 2, 2014

  • Awesome new help system by quixeybrian.
  • "@will help" now only displays functions with docstrings, and formats them nicely.
  • Old help (regexes and all) is available at "@will programmer help"

0.4.4 - April 22, 2014

  • Removes the dependence on the v1 token (though it still helps with rate-limiting), thanks to bfhenderson.
  • Much friendlier error message on an invalid API key, thanks to adamgilman.

0.4.3 - ~ April 1, 2014

  • Support for hundreds of users and rooms without hitting the API limit.
  • get_all_users use of the bulk API added by quixeybrian. Thanks also to jbeluch and jdrukman for nudges in the right direction.
  • The start of some useful comments - the meat of will was hacked out by one person over a handful of days and it looks that way. Slowly but surely making this codebase more friendly to other contributions!
  • Added a CONTRIBUTING.md file thanks to michaeljoseph.
  • Proper releases in the docs, and an updated AUTHORS file. If you see something awry, send a PR!

0.4 - ~ March 2014

  • Ye olden past before we started keeping this list. All contributions by GreenKahuna. Will did everything that's not in the release list above. That's called lazy retconning release lists!

will's People

Contributors

adamgilman avatar bfhenderson avatar ckcollab avatar crccheck avatar poirier avatar quixeybrian avatar rbp avatar skoczen avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.