├── .gitignore ├── users.txt ├── tox.ini ├── BotProperties.py ├── priveleged_users.txt ├── privileged_users.txt ├── .gitmodules ├── youtube_search.py ├── who_to_welcome.py ├── setup.bat ├── LICENSE ├── CONTRIBUTING.md ├── setup.sh ├── weather_search.py ├── google_search.py ├── nocrash.sh ├── image_search.py ├── excepthook.py ├── README.md └── welcomebot.py /.gitignore: -------------------------------------------------------------------------------- 1 | client.log* 2 | 3 | *.pyc 4 | .idea/ 5 | errorLogs* 6 | -------------------------------------------------------------------------------- /users.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelpri10/WelcomeBot/HEAD/users.txt -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E501,F403 3 | exclude = ChatExchange/*,.idea/*,.git/* -------------------------------------------------------------------------------- /BotProperties.py: -------------------------------------------------------------------------------- 1 | class BotProperties: 2 | paused = False 3 | welcome_message = "" 4 | -------------------------------------------------------------------------------- /priveleged_users.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelpri10/WelcomeBot/HEAD/priveleged_users.txt -------------------------------------------------------------------------------- /privileged_users.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelpri10/WelcomeBot/HEAD/privileged_users.txt -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ChatExchange"] 2 | path = ChatExchange 3 | url = https://github.com/michaelpri10/ChatExchange.git 4 | -------------------------------------------------------------------------------- /youtube_search.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | from bs4 import BeautifulSoup 3 | import random 4 | 5 | 6 | def youtube_search(search_term): 7 | search_term = search_term.encode('ascii', errors='replace') 8 | youtube_page = urllib2.urlopen("https://www.youtube.com/results?search_query=" + search_term) 9 | parse_youtube = BeautifulSoup(youtube_page) 10 | video_parents = parse_youtube.select("ol.item-section > li > div.yt-lockup > div.yt-lockup-dismissable > div.yt-lockup-content > h3.yt-lockup-title > a") 11 | video_links = ["https://www.youtube.com" + i['href'] for i in video_parents] 12 | if len(video_links) == 0: 13 | return False 14 | else: 15 | return random.choice(video_links) 16 | -------------------------------------------------------------------------------- /who_to_welcome.py: -------------------------------------------------------------------------------- 1 | import shelve 2 | 3 | 4 | def check_user(user_id, id_room, enter_or_leave): 5 | f = shelve.open("users.txt") 6 | 7 | if (id_room + enter_or_leave) in f: 8 | if user_id in f[id_room + enter_or_leave]: 9 | print f[id_room + enter_or_leave] 10 | print user_id 11 | print False 12 | return_value = False 13 | else: 14 | f[id_room + enter_or_leave] += [user_id] 15 | print f[id_room + enter_or_leave] 16 | print user_id 17 | print True 18 | return_value = True 19 | else: 20 | f[id_room + enter_or_leave] = [] 21 | f[id_room + enter_or_leave] += [user_id] 22 | print f[id_room + enter_or_leave] 23 | print user_id 24 | print True 25 | return_value = True 26 | 27 | f.close() 28 | return return_value 29 | -------------------------------------------------------------------------------- /setup.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | rem 3 | rem WelcomeBot setup 4 | rem 5 | 6 | setlocal 7 | 8 | pip --version 1> nul 2>&1 9 | if errorlevel 1 ( 10 | echo "pip --version" errored, make sure you've got python-pip installed. 11 | goto :error 12 | ) 13 | 14 | for /f %%R in ('git rev-list HEAD --max-count=1 --abbrev-commit 2^> nul') do (set REV=%%R) 15 | 16 | set HELLO=Setting up WelcomeBot 17 | if not defined REV ( 18 | echo %HELLO%^. 19 | echo First please clone WelcomeBot repository or obtain its source 20 | echo code and navigate to the directory where you have saved it to. 21 | goto :error 22 | ) else ( 23 | echo %HELLO% ^(revision: %REV%^). 24 | ) 25 | 26 | endlocal 27 | 28 | git submodule update --init 2> nul 29 | pip install beautifulsoup4 30 | pip install requests --upgrade 31 | pip install websocket-client --upgrade 32 | 33 | :bailout 34 | echo Setup completed. 35 | goto :end 36 | 37 | :error 38 | echo. 39 | echo Setup could not complete due to an error. 40 | echo Please check if you have satisfied all requirements listed in README.md. 41 | 42 | :end 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 michaelpri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | WelcomeBot welcomes your pull requests! 4 | 5 | When creating a pull request, please take the code styling guidelines in account, as specified in this document. 6 | 7 | The first, and most important rule is the indentation: you have to use 4 spaces for indentation, no tabs. 8 | 9 | Your code has to be conform to the PEP styling guidelines. An easy way to check whether it is, is to install the tool [Flake8](https://pypi.python.org/pypi/flake8) (`pip install flake8`) and to run it in the directory of the bot: 10 | 11 | flake8 ./ 12 | 13 | When running that command, Flake8 will look at the `tox.ini` file. This file contains rules to exclude the following warnings: 14 | 15 | - E501: Flake8 gives this error when a line is longer than 80 chars. Having a line that's longer than this is not a big problem for WelcomeBot. 16 | - F403: Flake8 gives this warning when it comes across a star import (`from import *`), because it cannot check for undefined names. 17 | 18 | `tox.ini` also excludes a few directories which shouldn't be checked. 19 | 20 | The Contributors of WelcomeBot (michaelpri10, Jacob-Gray and ProgramFOX) will check whether your code passes on Flake8 before it gets merged. 21 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # WelcomeBot setup 4 | # 5 | 6 | rev="`git rev-list HEAD --max-count=1 --abbrev-commit 2>/dev/null`" 7 | 8 | if [ "$#" -gt 1 ] || ( [ "$#" -eq 1 ] && [ "$1" == "--help" ] ) ; 9 | then 10 | echo "Set up WelcomeBot." 11 | echo 12 | echo "Usage:" 13 | echo " $0 [--help | --no-clone]" 14 | echo 15 | echo "Options:" 16 | echo " --help Print this help message and exit" 17 | echo " --no-clone Do NOT clone a WelcomeBot git repo (default behavior when inside a repo)" 18 | exit 1 19 | fi 20 | 21 | hello="Setting up WelcomeBot" 22 | if [ "$rev" == "" ] ; 23 | then 24 | echo "$hello." 25 | else 26 | echo "$hello (revision: $rev)." 27 | fi 28 | 29 | if [ "$1" == "--no-clone" ] && [ "$rev" == "" ] ; 30 | then 31 | echo "There's nothing to setup. Please obtain a copy of WelcomeBot source code first." 32 | exit 1 33 | elif [ "$1" == "--no-clone" ] || [ "$rev" != "" ]; 34 | then 35 | echo "Cloning supressed, assuming `pwd` as a WelcomeBot git repo directory." 36 | else 37 | echo "Cloning WelcomeBot..." 38 | git clone https://github.com/michaelpri10/WelcomeBot.git michaelpri10/WelcomeBot 39 | cd "`pwd`/michaelpri10/WelcomeBot" 40 | fi 41 | 42 | git submodule update --init 43 | pip install beautifulsoup4 44 | pip install requests --upgrade 45 | pip install websocket-client --upgrade 46 | 47 | echo "Setup completed." 48 | exit 0 49 | -------------------------------------------------------------------------------- /weather_search.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | 4 | 5 | def weather_search(city, country_state): 6 | print "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + country_state + "&units=imperial" 7 | imperial_info = json.loads(urllib2.urlopen("http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + country_state + "&units=imperial").read()) 8 | city_name = imperial_info["name"] 9 | city_country = imperial_info["sys"]["country"] 10 | imperial_temperature = str(imperial_info["main"]["temp"]) 11 | imperial_high = str(imperial_info["main"]["temp_max"]) 12 | imperial_low = str(imperial_info["main"]["temp_min"]) 13 | imperial_condition = [imperial_info["weather"][i]["description"] for i in range(len(imperial_info["weather"]))] 14 | 15 | metric_info = json.loads(urllib2.urlopen("http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + country_state + "&units=metric").read()) 16 | metric_temperature = str(metric_info["main"]["temp"]) 17 | metric_high = str(metric_info["main"]["temp_max"]) 18 | metric_low = str(metric_info["main"]["temp_min"]) 19 | 20 | return "Weather for " + city_name + ", " + city_country + "\nTemperature: " + imperial_temperature + " F / " + metric_temperature + " C\nHigh: " + imperial_high + " F / " + metric_high + " C\nLow: " + imperial_low + " F / " + metric_low + " C\nCurrent Condition: " + ", ".join(imperial_condition) 21 | -------------------------------------------------------------------------------- /google_search.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | 4 | 5 | def google_search(search_term): 6 | """ 7 | Searches for a `search_term` which should be a string or a value convertable to string. 8 | 9 | Parameters: 10 | - str `search_term`: a string to search for 11 | 12 | Returns a tuple (on success): 13 | - first value is a list of search results for the `search_term` returned by Google API 14 | - second value is a Google Search UI URL, where more results can be obtained 15 | 16 | Returns False (on failure). 17 | 18 | -- 19 | Authors: 20 | - michaelpri10 21 | - Jacob-Gray 22 | - Kubo2 23 | """ 24 | 25 | # The request also includes the userip parameter which provides the end 26 | # user's IP address. Doing so will help distinguish this legitimate 27 | # server-side traffic from traffic which doesn't come from an end-user. 28 | search_term = search_term.encode('ascii', errors='replace') 29 | url = "https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s&userip=USERS-IP-ADDRESS" % search_term 30 | 31 | request = urllib2.Request(url, None) 32 | response = urllib2.urlopen(request) 33 | 34 | # Process the JSON string. 35 | results = json.load(response) 36 | 37 | # now have some fun with the results... 38 | if len(results["responseData"]["results"]) > 0: 39 | return results["responseData"]["results"], results["responseData"]["cursor"]["moreResultsUrl"] 40 | 41 | return False 42 | -------------------------------------------------------------------------------- /nocrash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Mostly copied from the nocrash.sh (https://github.com/Charcoal-SE/SmokeDetector/blob/master/nocrash.sh) 3 | #on Smoke Detector made by Charcoal (https://github.com/Charcoal-SE/SmokeDetector) 4 | read -p "Username: " u 5 | export ChatExchangeU=$u 6 | export CEU="h" 7 | stty -echo 8 | read -p "Password: " p 9 | export ChatExchangeP=$p 10 | stty echo 11 | echo 12 | echo "Welcome Bot Host Site Options (select 1, 2, or 3)" 13 | echo " 1. chat.stackexchange.com" 14 | echo " 2. chat.meta.stackexchange.com" 15 | echo " 3. chat.stackoverflow.com" 16 | read -p "What will be your Welcome Bot's host site? " h 17 | export HostSite=$h 18 | read -p "What is the room's ID? " i 19 | export RoomID=$i 20 | read -p "What is the welcome message? " m 21 | export WelcomeMessage=$m 22 | count=0 23 | crashcount=0 24 | stoprunning=0 25 | while [ "$stoprunning" -eq "0" ] 26 | do 27 | if [ "$count" -eq "0" ] 28 | then 29 | python2 welcomebot.py first_start 30 | else 31 | python2 welcomebot.py 32 | fi 33 | 34 | ecode=$? 35 | 36 | if [ "$ecode" -eq "3" ] 37 | then 38 | git checkout master 39 | git pull 40 | git submodule update 41 | count=0 42 | crashcount=0 43 | 44 | elif [ "$ecode" -eq "4" ] 45 | then 46 | count=$((count+1)) 47 | sleep 5 48 | if [ "$crashcount" -eq "2" ] 49 | then 50 | git checkout HEAD~1 51 | count=0 52 | crashcount=0 53 | else 54 | crashcount=$((crashcount+1)) 55 | fi 56 | 57 | elif [ "$ecode" -eq "5" ] 58 | then 59 | count=0 60 | elif [ "$ecode" -eq "6" ] 61 | then 62 | stoprunning=1 63 | 64 | else 65 | sleep 5 66 | count=$((count+1)) 67 | 68 | fi 69 | done 70 | -------------------------------------------------------------------------------- /image_search.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | from bs4 import BeautifulSoup 3 | import random 4 | import re 5 | 6 | 7 | def search_image(search_term): 8 | search_term = search_term.encode('ascii', errors='replace') 9 | site = "http://www.google.com/search?tbm=isch&safe=strict&q=" + search_term 10 | print site 11 | hdr = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.3', 'Accept-Encoding': 'none', 'Accept-Language': 'en-US,en;q=0.8', 'Connection': 'keep-alive'} 12 | 13 | req = urllib2.Request(site, headers=hdr) 14 | 15 | try: 16 | page = urllib2.urlopen(req) 17 | except urllib2.HTTPError, e: 18 | print e.fp.read() 19 | return False 20 | 21 | images_page = page.read() 22 | parsing_page = BeautifulSoup(images_page) 23 | image_container = parsing_page.select("div#rg_s")[0] 24 | image_tags = image_container.findAll("a", {"class": "rg_l"}, limit=20) 25 | final = "" 26 | counter = 0 27 | while counter < len(image_tags): 28 | i = random.choice(image_tags) 29 | split_at = i["href"].find("imgurl=") + 7 30 | end_split = i["href"].find("&imgrefurl") 31 | result = i["href"][split_at:end_split] 32 | if result.endswith((".jpg", ".gif", ".png")): 33 | try_open = True 34 | single_image = None 35 | try: 36 | single_image = urllib2.urlopen(result) 37 | except: 38 | try_open = False 39 | if try_open: 40 | if len(BeautifulSoup(single_image).findAll(text=re.compile('404'))) > 0: 41 | break 42 | else: 43 | final = result 44 | break 45 | else: 46 | pass 47 | counter += 1 48 | 49 | if final == "": 50 | return False 51 | else: 52 | return final 53 | -------------------------------------------------------------------------------- /excepthook.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copied from the excepthook.py (https://github.com/Charcoal-SE/SmokeDetector/blob/master/excepthook.py) 3 | on Smoke Detector made by Charcoal (https://github.com/Charcoal-SE/SmokeDetector) 4 | """ 5 | import os 6 | import traceback 7 | import threading 8 | import sys 9 | from datetime import datetime 10 | from websocket import WebSocketConnectionClosedException 11 | import requests 12 | 13 | 14 | def uncaught_exception(exctype, value, tb): 15 | now = datetime.utcnow() 16 | delta = now - datetime.utcnow() 17 | seconds = delta.total_seconds() 18 | tr = '\n'.join((traceback.format_tb(tb))) 19 | exception_only = ''.join(traceback.format_exception_only(exctype, value)).strip() 20 | logged_msg = exception_only + '\n' + str(now) + " UTC" + '\n' + tr + '\n\n' 21 | print(logged_msg) 22 | with open("errorLogs.txt", "a") as f: 23 | f.write(logged_msg) 24 | if seconds < 180 and exctype != WebSocketConnectionClosedException\ 25 | and exctype != KeyboardInterrupt and exctype != SystemExit and exctype != requests.ConnectionError: 26 | os._exit(4) 27 | else: 28 | os._exit(1) 29 | 30 | 31 | def install_thread_excepthook(): 32 | """ 33 | Workaround for sys.excepthook thread bug 34 | From 35 | http://spyced.blogspot.com/2007/06/workaround-for-sysexcepthook-bug.html 36 | (https://sourceforge.net/tracker/?func=detail&atid=105470&aid=1230540&group_id=5470). 37 | Call once from __main__ before creating any threads. 38 | If using psyco, call psyco.cannotcompile(threading.Thread.run) 39 | since this replaces a new-style class method. 40 | """ 41 | init_old = threading.Thread.__init__ 42 | 43 | def init(self, *args, **kwargs): 44 | init_old(self, *args, **kwargs) 45 | run_old = self.run 46 | 47 | def run_with_except_hook(*args, **kw): 48 | try: 49 | run_old(*args, **kw) 50 | except (KeyboardInterrupt, SystemExit): 51 | raise 52 | except: 53 | sys.excepthook(*sys.exc_info()) 54 | self.run = run_with_except_hook 55 | threading.Thread.__init__ = init 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #WelcomeBot 2 | 3 | A Stack Exchange chatbot written in Python that uses [ChatExchange](https://github.com/Manishearth/ChatExchange). Many parts of this were modeled after [SmokeDetector](https://github.com/Charcoal-SE/SmokeDetector), another Stack Exchange chatbot. 4 | 5 | --- 6 | 7 | ##Features 8 | 9 | - Welcomes new users entering chatrooms on [chat.stackexchange](http://chat.stackexchange.com), [chat.meta.stackexchange](http://chat.meta.stackexchange.com), and [chat.stackoverflow](http://chat.stackoverflow.com) with a custom welcome command made by you 10 | - Many different chatroom commands: 11 | - `//image (image search term)` - searches for and posts images of or relating to the image search term 12 | - `//search (search term)` - finds the top three results on google and posts them 13 | - `//choose (choice) or (choice) [or choice...]` - makes decisions for you so you don't have to. Can accept more than two choices as long as they are separated by `' or '` 14 | - `//weather (city)[, country/state]` - gets the weather for whatever location you would like 15 | - `//youtube (youtube search term)` - search [Youtube](https://www.youtube.com/) for and returns a video of or relating to your search term 16 | - `//source` - gives you a link to WelcomeBot's source code on Github 17 | - `//info` - tells you the host site, room id, and welcome message 18 | - `//help` - tells you all of the commands listed above in case you forget 19 | - Commands for privileged users: 20 | - `//die` - kills the bot if it is acting up 21 | - `//reset` - resets the bot just in case 22 | - `//pull` - updates the bot with the latest commit on Github 23 | - `//pause` - pauses the bot temporarily 24 | - `//start` - resumes the bot when it is paused 25 | - `//editmsg [new welcome message]` - edit the welcome message 26 | - `//priv [user id]` - gives a user the ability to use this command and the six commands listed above 27 | 28 | --- 29 | 30 | ##Setup 31 | 32 | In all cases, you are supposed to have [Git](http://git-scm.com/) installed, as well as 33 | [pip Python package manager](https://pip.pypa.io/en/latest/installing.html#install-pip). 34 | 35 | ###Linux 36 | 37 | Run `setup.sh`: 38 | 39 | ```sh 40 | dev@welcomebot$ ./setup.sh 41 | Setting up WelcomeBot. 42 | Cloning WelcomeBot... 43 | Cloning into 'michaelpri10/WelcomeBot'... 44 | Submodule 'ChatExchange' (https://github.com/michaelpri10/ChatExchange.git) registered for path 'ChatExchange' 45 | Cloning into 'ChatExchange'... 46 | Submodule path 'ChatExchange': checked out '...' 47 | Successfully installed beautifulsoup4-4.4.0 48 | Successfully installed requests-2.7.0 49 | Successfully installed websocket-client-0.32.0 50 | Setup completed. 51 | 52 | dev@welcomebot$ ./setup.sh --help 53 | Set up WelcomeBot. 54 | 55 | Usage: 56 | ./setup.sh [--help | --no-clone] 57 | 58 | Options: 59 | --help Print this help message and exit 60 | --no-clone Do NOT clone a WelcomeBot git repo (default behavior when inside a repo) 61 | 62 | ``` 63 | 64 | You can obtain our `setup.sh` from our [Releases](https://github.com/michaelpri10/WelcomeBot/releases) 65 | list on GitHub; in that case, it will try to clone and install the WelcomeBot version it was released for. 66 | 67 | Otherwise you can `git clone` the latest development version of WelcomeBot source code from GitHub 68 | yourself and then run `setup.sh` included in repository. 69 | 70 | ###Windows 71 | 72 | On Windows, installation is not so straightforward. You first have to clone WelcomeBot source code yourself: 73 | 74 | ```cmd 75 | C:\Users\WelcomeBot> git clone https://github.com/michaelpri10/WelcomeBot.git 76 | Cloning into 'WelcomeBot'... 77 | C:\Users\WelcomeBot> cd WelcomeBot 78 | ``` 79 | 80 | And then run `setup.bat` included in repository: 81 | 82 | ```cmd 83 | C:\Users\WelcomeBot\WelcomeBot> .\setup.bat 84 | ``` 85 | 86 | --- 87 | 88 | To run WelcomeBot, run `nocrash.sh` and log in with your Stack Exchange OpenID credentials. 89 | 90 | --- 91 | 92 | ##Why use WelcomeBot? 93 | 94 | - Make new users feel welcome in your chatroom 95 | - Have fun in your chatroom 96 | 97 | --- 98 | 99 | ##Source Code 100 | 101 | - WelcomeBot is an open source project and all of its code is available on [Github.](https://github.com/michaelpri10/WelcomeBot) 102 | - WelcomeBot is constantly being updated; luckily the `//pull` commands makes it easy to keep up 103 | - If you want to see WelcomeBot in action check out the [Teenage Programmers Chatroom](http://chat.stackoverflow.com/rooms/22091/teenage-programmers-chatroom), the current home for WelcomeBot 104 | - WelcomeBot was created by michaelpri ([Github](https://github.com/michaelpri10), [Stack Exhange](http://stackexchange.com/users/4642421/michaelpri)), ProgramFOX ([Github](https://github.com/programfox), [Stack Exchange](http://stackexchange.com/users/3094403/programfox)), and Jacob Gray ([Github](https://github.com/Jacob-Gray), [Stack Exchange](http://stackexchange.com/users/3984803/jacob-gray)) 105 | -------------------------------------------------------------------------------- /welcomebot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from excepthook import uncaught_exception, install_thread_excepthook 3 | import sys 4 | sys.excepthook = uncaught_exception 5 | install_thread_excepthook() 6 | 7 | import getpass 8 | import logging 9 | import logging.handlers 10 | import os 11 | import time 12 | import shelve 13 | import random 14 | import re 15 | import requests 16 | from threading import Thread 17 | 18 | import ChatExchange.chatexchange.client 19 | import ChatExchange.chatexchange.events 20 | import ChatExchange.chatexchange.browser 21 | import who_to_welcome 22 | import image_search 23 | import weather_search 24 | import youtube_search 25 | import google_search 26 | from BotProperties import BotProperties 27 | 28 | logger = logging.getLogger(__name__) 29 | 30 | host_id = '' 31 | room_id = 0 32 | bot = None 33 | room = None 34 | 35 | 36 | def main(): 37 | global host_id 38 | global room_id 39 | global bot 40 | global room 41 | 42 | setup_logging() 43 | # Run `. setp.sh` to set the below testing environment variables 44 | 45 | def welcome_bot_host_options(): 46 | print "Welcome Bot Host Site Options (select 1, 2, or 3)" 47 | print " 1. chat.stackexchange.com" 48 | print " 2. chat.meta.stackexchange.com" 49 | print " 3. chat.stackoverflow.com" 50 | print "What will be your Welcome Bot's host site?" 51 | if 'HostSite' in os.environ: 52 | host_id_choice = os.environ['HostSite'] 53 | if host_id_choice == '1': 54 | print "You have chosen chat.stackexchange.com as your Welcome Bot's host site." 55 | host_id = 'stackexchange.com' 56 | elif host_id_choice == '2': 57 | print "You have chosen meta.chat.stackexchange.com as your Welcome Bot's host site." 58 | host_id = 'meta.stackexchange.com' 59 | elif host_id_choice == '3': 60 | print "You have chosen chat.stackoverflow.com as your Welcome Bot's host site." 61 | host_id = 'stackoverflow.com' 62 | else: 63 | welcome_bot_host_options() 64 | host_id_choice = raw_input() 65 | while host_id_choice not in ['1', '2', '3']: 66 | print "Invalid Choice" 67 | welcome_bot_host_options() 68 | host_id_choice = raw_input() 69 | if host_id_choice == '1': 70 | print "You have chosen chat.stackexchange.com as your Welcome Bot's host site." 71 | host_id = 'stackexchange.com' 72 | elif host_id_choice == '2': 73 | print "You have chosen meta.chat.stackexchange.com as your Welcome Bot's host site." 74 | host_id = 'meta.stackexchange.com' 75 | elif host_id_choice == '3': 76 | print "You have chosen chat.stackoverflow.com as your Welcome Bot's host site." 77 | host_id = 'stackoverflow.com' 78 | 79 | if 'RoomID' in os.environ: 80 | room_id = os.environ['RoomID'] 81 | else: 82 | print "What is the room's ID?" 83 | room_id_choice = raw_input() 84 | while not room_id_choice.isdigit(): 85 | print "Invalid Input, must be a number" 86 | room_id_choice = raw_input() 87 | room_id = room_id_choice 88 | 89 | if 'WelcomeMessage' in os.environ: 90 | BotProperties.welcome_message = os.environ['WelcomeMessage'] 91 | else: 92 | print "What would you like the welcome message to be?" 93 | BotProperties.welcome_message = raw_input() 94 | 95 | if 'ChatExchangeU' in os.environ: 96 | email = os.environ['ChatExchangeU'] 97 | else: 98 | email = raw_input("Email: ") 99 | if 'ChatExchangeP' in os.environ: 100 | password = os.environ['ChatExchangeP'] 101 | else: 102 | password = getpass.getpass("Password: ") 103 | 104 | client = ChatExchange.chatexchange.client.Client(host_id) 105 | client.login(email, password) 106 | 107 | bot = client.get_me() 108 | 109 | room = client.get_room(room_id) 110 | room.join() 111 | room.watch(on_event) 112 | 113 | print "(You are now in room #%s on %s.)" % (room_id, host_id) 114 | if "first_start" in sys.argv: 115 | room.send_message("I'm alive :) (running on commit: " + os.popen('git log --pretty=format:"%h" -n 1').read() + ")") 116 | 117 | while True: 118 | message = raw_input("<< ") 119 | if message == "die": 120 | room.send_message("I'm dead :(") 121 | time.sleep(0.4) 122 | break 123 | else: 124 | room.send_message(message) 125 | 126 | os._exit(6) 127 | 128 | 129 | def on_event(event, client): 130 | if not BotProperties.paused: 131 | if isinstance(event, ChatExchange.chatexchange.events.UserEntered): 132 | on_enter(event) 133 | elif isinstance(event, ChatExchange.chatexchange.events.MessagePosted): 134 | on_command(event, client) 135 | else: 136 | priv_users = shelve.open("privileged_users.txt") 137 | if isinstance(event, ChatExchange.chatexchange.events.MessagePosted) and event.content.startswith("//start"): 138 | if (event.user.id == 121401 and host_id == 'stackexchange.com') or (event.user.id == 284141 and host_id == 'meta.stackexchange.com') or (event.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(event.user.id) in priv_users[host_id + room_id]): 139 | BotProperties.paused = False 140 | event.message.reply("I am now resuming :)") 141 | priv_users.close() 142 | 143 | 144 | def on_enter(event): 145 | print "User Entered" 146 | if len(BotProperties.welcome_message) < 1: 147 | pass 148 | else: 149 | if event.user.id == bot.id or event.user.reputation < 20: 150 | pass 151 | else: 152 | if who_to_welcome.check_user(event.user.id, room_id, 'enter'): 153 | room.send_message("@" + event.user.name.replace(" ", "") + " " + BotProperties.welcome_message) 154 | 155 | 156 | def on_command(message, client): 157 | priv_users = shelve.open("privileged_users.txt") 158 | print "Message Posted" 159 | message.content = chaterize_message(message.content) 160 | 161 | if message.content.startswith("//image"): 162 | print "Is image request" 163 | if len(message.content.split()) == 1: 164 | message.message.reply("No search term given") 165 | else: 166 | def perform_image_search(): 167 | search_term = "+".join(message.content.split()[1:]) 168 | image = image_search.search_image(search_term) 169 | print image 170 | if image is False: 171 | print "No Image" 172 | message.message.reply("No image was found for " + message.content[8:]) 173 | else: 174 | print message.content 175 | print search_term 176 | message.message.reply(image) 177 | t = Thread(target=perform_image_search) 178 | t.start() 179 | elif message.content.startswith("//alive"): 180 | print "Is alive request" 181 | message.message.reply("Indeed :D") 182 | 183 | elif message.content.startswith("//testenter"): 184 | print "Is testenter request" 185 | room.send_message("@SkynetTestUser " + BotProperties.welcome_message) 186 | 187 | elif message.content.startswith("//search"): 188 | print "Is search request" 189 | if len(message.content.split()) == 1: 190 | message.message.reply("Can't search, no terms given") 191 | else: 192 | def perform_google_search(): 193 | search_term = "+".join(message.content.split()[1:]) 194 | try: 195 | results, moreResultsUrl = google_search.google_search(search_term) 196 | print results 197 | except TypeError: 198 | print "No results" 199 | message.message.reply("No results were found for " + message.content[9:]) 200 | return 201 | 202 | print message.content 203 | print search_term 204 | 205 | message.message.reply("Search results for **%s**: " % message.content[9:]) 206 | 207 | # sould be configurable 208 | count = len(results) < 3 and len(results) or 3 209 | for i in range(0, count): 210 | res = results[i] 211 | room.send_message("[%s](%s)" % (res['titleNoFormatting'], res['unescapedUrl'])) 212 | room.send_message(chaterize_message(res["content"]).replace("\n", " ")) 213 | 214 | room.send_message("[See More...](%s)" % moreResultsUrl) 215 | 216 | g = Thread(target=perform_google_search) 217 | g.start() 218 | 219 | elif message.content.startswith("//info"): 220 | print "Is info request" 221 | message.message.reply("Host ID: " + host_id + "\nRoom ID: " + room_id + "\nWelcome Message:\n" + BotProperties.welcome_message) 222 | 223 | elif message.content.startswith("//editmsg"): 224 | print "Is message edit request" 225 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 226 | if len(message.content.split()) == 1: 227 | message.message.reply("No string given") 228 | else: 229 | u_welcome_message = " ".join(message.content.split()[1:]) 230 | print u_welcome_message 231 | if '' in u_welcome_message: 232 | print "is link" 233 | text_before = u_welcome_message[:u_welcome_message.find('') + 4):] 235 | link_opening = u_welcome_message.find('href="') + 6 236 | link_ending = u_welcome_message.find('" rel=') 237 | link = u_welcome_message[link_opening:link_ending] 238 | text_opening = u_welcome_message.find('ow">') + 4 239 | text_ending = u_welcome_message.find('') 240 | link_text = u_welcome_message[text_opening:text_ending] 241 | BotProperties.welcome_message = text_before + "[" + link_text + "](" + link + ")" + text_after 242 | else: 243 | BotProperties.welcome_message = u_welcome_message 244 | message.message.reply("Welcome message changed to: " + BotProperties.welcome_message) 245 | else: 246 | message.message.reply("You are not authorized to edit the welcome message. Please contact michaelpri if it needs to be changed.") 247 | 248 | elif message.content.startswith("//die"): 249 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 250 | message.message.reply("I'm dead :(") 251 | time.sleep(0.4) 252 | os._exit(6) 253 | else: 254 | message.message.reply("You are not authorized kill me!!! Muahaha!!!! Please contact michaelpri if I am acting up.") 255 | elif message.content.startswith("//reset"): 256 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 257 | message.message.reply("Resetting...") 258 | time.sleep(0.4) 259 | os._exit(5) 260 | else: 261 | message.message.reply("You are not authorized reset me. Please contatct michaelpri if I need resetting.") 262 | elif message.content.startswith("//source"): 263 | message.message.reply("My source code can be found on [GitHub](https://github.com/michaelpri10/WelcomeBot)") 264 | elif message.content.startswith("//pull"): 265 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 266 | os._exit(3) 267 | else: 268 | message.message.reply("You are not authorized to pull. Please contatct michaelpri if I need some pulling.") 269 | 270 | elif message.content.startswith("//priv"): 271 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 272 | if len(message.content.split()) == 2: 273 | user_to_priv = message.content.split()[1] 274 | if (host_id + room_id) not in priv_users: 275 | priv_users[host_id + room_id] = [] 276 | if user_to_priv in priv_users[host_id + room_id]: 277 | message.message.reply("User already privileged") 278 | else: 279 | priv_users[host_id + room_id] += [user_to_priv] 280 | message.message.reply("User " + user_to_priv + " added to privileged users for room " + room_id + " on chat." + host_id) 281 | else: 282 | message.message.reply("Invalid privilege giving") 283 | else: 284 | message.message.reply("You are not authorized to add privileged users :( Please contact michaelpri if someone needs privileges.") 285 | elif message.content.startswith("//choose"): 286 | print "Is choose request" 287 | if len(message.content.split()) == 1: 288 | message.message.reply("No choices given") 289 | else: 290 | if " or " in message.content: 291 | choices = message.content[8:].split(" or ") 292 | message.message.reply("I choose " + random.choice(choices)) 293 | else: 294 | message.message.reply("I'm not sure what your options are") 295 | elif message.content.startswith("//help"): 296 | print "Is help request" 297 | message.message.reply("""My Commands 298 | - //image (search term) 299 | - //choose (choice) or (choice) [or choice...] 300 | - //weather (city)[, country/state] 301 | - //youtube (search term) 302 | - //source 303 | - //info 304 | - //search (search term) 305 | """) 306 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 307 | message.message.reply("""You are a privileged user, so you can also use these commands: 308 | - //pull 309 | - //pause 310 | - //start 311 | - //die 312 | - //priv 313 | - //reset 314 | - //editmsg [new welcome message] 315 | """) 316 | elif message.content.startswith("//weather"): 317 | print "Is weather request" 318 | if len(message.content.split()) == 1: 319 | message.message.reply("City and country/state not given") 320 | else: 321 | def perform_weather_search(): 322 | city_and_country = [i.replace(" ", "%20") for i in message.content[10:].split(",")] 323 | city = city_and_country[0] 324 | if len(city_and_country) == 2: 325 | country_state = city_and_country[1] 326 | print city, country_state 327 | try: 328 | message.message.reply(weather_search.weather_search(city, country_state)) 329 | except: 330 | message.message.reply("Couldn't find weather info for " + message.content) 331 | elif len(city_and_country) == 1: 332 | try: 333 | message.message.reply(weather_search.weather_search(city, "")) 334 | except: 335 | message.message.reply("Couldn't find weather info for " + message.content) 336 | w = Thread(target=perform_weather_search) 337 | w.start() 338 | elif message.content.startswith("//youtube"): 339 | print "Is youtube request" 340 | if len(message.content.split()) == 1: 341 | message.message.reply("No search term given") 342 | else: 343 | def perform_youtube_search(): 344 | search_term = "+".join(message.content.split()[1:]) 345 | video = youtube_search.youtube_search(search_term) 346 | print video 347 | if video is False: 348 | print "No Image" 349 | message.message.reply("No video was found for " + search_term) 350 | else: 351 | print message.content 352 | print search_term 353 | message.message.reply(video) 354 | v = Thread(target=perform_youtube_search) 355 | v.start() 356 | elif message.content.startswith("//pause"): 357 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 358 | BotProperties.paused = True 359 | message.message.reply("I am now paused :|") 360 | else: 361 | message.message.reply("You are not authorized to pause me. Please contact michaelpri if I need some pausing.") 362 | elif re.compile("^:[0-9]+ delete$").search(message.message.content_source): 363 | if (message.user.id == 121401 and host_id == 'stackexchange.com') or (message.user.id == 284141 and host_id == 'meta.stackexchange.com') or (message.user.id == 4087357 and host_id == 'stackoverflow.com') or (str(message.user.id) in priv_users[host_id + room_id]): 364 | message_to_delete = client.get_message(int(message.message.content_source.split(" ")[0][1:])) 365 | try: 366 | message_to_delete.delete() 367 | except requests.HTTPError: 368 | pass 369 | else: 370 | message.message.reply("You are not authorized to delete one of my messages.") 371 | 372 | priv_users.close() 373 | 374 | 375 | def chaterize_message(message): 376 | 377 | message = message.replace('"', '"') 378 | message = message.replace("'", "'") 379 | message = message.replace("", "*") 380 | message = message.replace("", "*") 381 | message = message.replace("", "**") 382 | message = message.replace("", "**") 383 | message = message.replace("", "***") 384 | message = message.replace("", "***") 385 | return message 386 | 387 | 388 | def setup_logging(): 389 | logging.basicConfig(level=logging.INFO) 390 | logger.setLevel(logging.DEBUG) 391 | 392 | # In addition to the basic stderr logging configured globally 393 | # above, we'll use a log file for chatexchange.client. 394 | wrapper_logger = logging.getLogger('ChatExchange.chatexchange.client') 395 | wrapper_handler = logging.handlers.TimedRotatingFileHandler( 396 | filename='client.log', 397 | when='midnight', delay=True, utc=True, backupCount=7, 398 | ) 399 | wrapper_handler.setFormatter(logging.Formatter( 400 | "%(asctime)s: %(levelname)s: %(threadName)s: %(message)s" 401 | )) 402 | wrapper_logger.addHandler(wrapper_handler) 403 | 404 | if __name__ == '__main__': 405 | main() 406 | --------------------------------------------------------------------------------