├── config.json ├── requirements.txt ├── LICENSE ├── README.md ├── megbot.py └── megbotex.py /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "addedStops": ["lol", "haha"] 3 | } 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nltk==3.0.4 2 | requests==2.7.0 3 | selenium==2.47.1 4 | splinter==0.7.3 5 | stemming==1.0.1 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Megan Ruthven 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MegBot 2 | Facebook messenger groups have the ability to reach 100+ unread messages pretty quickly. At that point, you have to make a decision: miss out on the messages and risk missing an awesome discussion, or read every single line and risk oportunity cost. Now you don't have to make that decision! MegBot allows you to automate your Facebook message group summaries. 3 | 4 | ## Details 5 | MegBot itself is an object that allows you to interface with Facebook messenger through your own account. 6 | 7 | The example, megbotex.py, summarizes the past 50 messages the group said. It does this either every 50 messages, or when someone in the chat group calls it with "@megbot". It also draws attention to whoever you want by responding to your message of "@"xyz with a capitalized version of the word attached to the @. 8 | 9 | ![](https://scontent.fsnc1-1.fna.fbcdn.net/hphotos-xfp1/v/t1.0-9/11898559_10206907510503212_5645712742133939266_n.jpg?oh=4c25ee4333985aed381cc4b5ac132a2a&oe=567CF17E) 10 | 11 | ## Installation 12 | 13 | 1. Install [PhantomJS](http://phantomjs.org/download.html). Note: 14 | It is available via 15 | ``` 16 | brew install phantomjs 17 | ``` 18 | and 19 | ``` 20 | port install phantomjs 21 | ``` 22 | 2. Create a python env if you want, or use your own and then 23 | ``` 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | ## Usage 28 | To use the example: 29 | 30 | python megbotex.py [facebook email] [facebook password] [message ID] 31 | 32 | The message ID is located here: 33 | ![](http://i.imgur.com/vmuNWDC.png) 34 | 35 | The information in your chat is stored in an array of strings, and therefore does not persist in this example. Please be aware of privacy concerns if you decide to change this feature. 36 | 37 | ## Contributions 38 | Feel free to send a pull request for added functionality to MegBot or any use cases you want to share. 39 | 40 | ## Disclaimer 41 | PM's are private. Therefore, you should have full consent of everyone in your group to opporate MegBot. Please be considerate of others. 42 | 43 | ## Maintainer 44 | [Megan Ruthven](http://maruthven.com/) ([@maruthven](https://twitter.com/maruthven)) 45 | -------------------------------------------------------------------------------- /megbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' 3 | Author: Megan Ruthven 4 | Date: August 18, 2015 5 | ''' 6 | 7 | import sys 8 | import json 9 | import re 10 | import string 11 | from collections import Counter 12 | from nltk.corpus import stopwords 13 | from stemming.porter2 import stem 14 | import time 15 | from selenium import webdriver 16 | from selenium.webdriver.common.by import By 17 | from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 18 | from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0 19 | from selenium.common.exceptions import StaleElementReferenceException 20 | from selenium.common.exceptions import TimeoutException 21 | import requests 22 | from splinter import Browser 23 | from urllib2 import URLError 24 | 25 | _base_url = "https://mbasic.facebook.com" 26 | _base_msg_url = _base_url + "/messages/read/?tid=id." 27 | _msg_url_mid = "&start="; 28 | _intro_message = "Hey y'all! I'm megbot. Here to help with some chat automation. My use cases are endless. If you're interested in what I'm here for, ask who invited me into this chat what I can do!"; 29 | 30 | class MegBot: 31 | """MegBot is an interface to Facebook Messages. You can use it by logging into your account, and giving it a group message you are a member of. You can navigate to different pages of the message, read the page, and write to the group chat. Please make sure the other members are aware MegBot is joining y'all in your conversation.""" 32 | def __init__(self, un, pw): 33 | self.username = un; 34 | self.password = pw; 35 | self.currentPage = 0; 36 | self.messageID = 0; 37 | self.browser = Browser("phantomjs"); 38 | pass; 39 | 40 | def login(self): 41 | self.browser.visit(_base_url) 42 | self.browser.fill('email', self.username); 43 | self.browser.fill('pass', self.password); 44 | self.browser.find_by_css('input[type="submit"]').first.click(); 45 | 46 | print "Logged in!" 47 | 48 | if self.messageID: 49 | self.moveToMessage(); 50 | 51 | def move_to_message(self, mID): 52 | self.currentPage = 0; 53 | self.messageID = mID.strip(); 54 | self.browser.visit(_base_msg_url + self.messageID); 55 | time.sleep(3); 56 | self.send_message(_intro_message); 57 | 58 | def refresh_messages(self): 59 | if self.messageID == 0: 60 | return; 61 | 62 | self.currentPage = 0; 63 | self.browser.visit(_base_msg_url + self.messageID); 64 | time.sleep(3); 65 | 66 | def next_page(self): 67 | if self.messageID == 0: 68 | return False; 69 | 70 | self.currentPage += 1; 71 | print self.currentPage; 72 | self.browser.visit(_base_msg_url + self.messageID + _msg_url_mid + str(5 * self.currentPage)); 73 | time.sleep(3); 74 | 75 | def send_message(self, inWords): 76 | self.browser.find_by_id('composerInput').fill(inWords); 77 | self.browser.find_by_css('input[name="send"]').first.click(); 78 | time.sleep(3); 79 | 80 | def read_messages(self): 81 | messages = []; 82 | mHTML = self.browser.find_by_id("messageGroup").find_by_css("div"); 83 | #unnecessary right now 84 | link = mHTML.pop(0).find_by_css("a")["href"]; 85 | lines = mHTML.find_by_css("span"); 86 | for line in lines: 87 | l = filter(lambda x: x in string.printable, line.text.strip()); 88 | if l and l != "." and not ("Sent from" in l) and not ("Seen by" in l): 89 | messages.insert(0, l); 90 | else: 91 | print "skipped " + l; 92 | 93 | return messages; 94 | 95 | 96 | -------------------------------------------------------------------------------- /megbotex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | ''' 3 | Author: Megan Ruthven 4 | Date: August 18, 2015 5 | How to use the MegBot summary example: 6 | python megbotex.py [facebook email] [facebook password] [messageID] 7 | ''' 8 | 9 | import sys 10 | import json 11 | import re 12 | import string 13 | from collections import Counter 14 | from nltk.corpus import stopwords 15 | import time 16 | from selenium import webdriver 17 | from selenium.webdriver.common.by import By 18 | from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 19 | from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0 20 | from selenium.common.exceptions import StaleElementReferenceException 21 | from selenium.common.exceptions import TimeoutException 22 | 23 | import requests 24 | from splinter import Browser 25 | from urllib2 import URLError 26 | from megbot import MegBot 27 | 28 | maxMessages = 50; 29 | # attempt to load stopwords 30 | stop = stopwords.words('english') 31 | 32 | regex = re.compile('[%s]' % re.escape(string.punctuation)); 33 | _intro = "Top words: "; 34 | _megbot_call = "@megbot"; 35 | _at_key = "@"; 36 | pattern = re.compile("\\b("+_intro+"|"+_megbot_call+")\\W", re.I); 37 | 38 | try: 39 | username = sys.argv[1]; 40 | password = sys.argv[2]; 41 | message = sys.argv[3]; 42 | with open('config.json') as in_json: 43 | data = json.load(in_json); 44 | print data; 45 | stop = stop + [word.lower().strip() for word in data['addedStops']]; 46 | stop = list(set(stop)); 47 | regex = re.compile('[%s]' % re.escape(string.punctuation)); 48 | mb = MegBot(username,password); 49 | 50 | except IndexError: 51 | print "Usage: python megbotex.py "; 52 | sys.exit(); 53 | 54 | print stop; 55 | 56 | def next_set(pastMessages): 57 | #checks to see if there are incoming messages. If there are, returns the new ones. 58 | newMessage = []; 59 | end = False; 60 | mb.refresh_messages(); 61 | if len(pastMessages) != 0: 62 | print "last line of past messages: " + pastMessages[0]; 63 | while len(newMessage) < maxMessages: 64 | outM = mb.read_messages(); 65 | print outM; 66 | t = []; 67 | for out in outM: 68 | if _intro in out: 69 | print "found my message"; 70 | elif len(pastMessages) == 0 or not any(out == s for s in pastMessages[:min(len(pastMessages), 4)]): 71 | t.append(out); 72 | else: 73 | print "was not an original message"; 74 | print "but this was original" + ' '.join(t); 75 | end = True; 76 | break; 77 | 78 | newMessage = newMessage+t; 79 | if end: 80 | print "returning now"; 81 | return newMessage; 82 | else: 83 | mb.next_page(); 84 | 85 | return newMessage; 86 | 87 | def highest_words(mess): 88 | #cleans, filters out punctuation, and lowers all words in the string in order to count the frequency of each word. Returns top 5 frequent words 89 | oneStr = ' '.join(mess).lower(); 90 | inWords = re.sub('[^0-9a-zA-Z]+', ' ', regex.sub('', oneStr)).strip(); 91 | 92 | totWords = ' '.join([word for word in inWords.split() if word not in stop]); 93 | print totWords; 94 | wordCount = Counter(totWords.split()); 95 | print wordCount; 96 | toGoOut = ', '.join([letter for letter, count in wordCount.most_common(5)]); 97 | return toGoOut; 98 | 99 | 100 | #set up environment 101 | mb.login(); 102 | mb.move_to_message(message); 103 | 104 | #reading all of the past messages from the group chat 105 | currCheck =[]; 106 | currCheck = next_set(currCheck); 107 | currCheck = currCheck[:maxMessages-1]; 108 | print currCheck; 109 | ou = highest_words(currCheck); 110 | print ou; 111 | mb.send_message(_intro + ou); 112 | newMess = 0; 113 | filteredAts = []; 114 | while True: 115 | try: 116 | print "checking"; 117 | #checking to see if the group message has gotten any more messages from before. 118 | n = next_set(currCheck); 119 | newMess = newMess + len(n); 120 | currCheck = n + currCheck; 121 | print newMess; 122 | print currCheck; 123 | 124 | #if the max amount of messages or a @megbot call have come in, summarize the past maxMessages amount of chat and return the top 5 words. 125 | if (newMess) > maxMessages or any( _megbot_call in s for s in currCheck[:len(n)]): 126 | currCheck = currCheck[:maxMessages]; 127 | ou = highest_words(currCheck); 128 | mb.send_message(_intro+ou); 129 | newMess = 0; 130 | print "found another message"; 131 | 132 | #shout out feature. If anyone says "@"xyz that isn't a megbot call. This shouts out the name following the @ in all capitals 133 | if any(_at_key in s for s in currCheck[:len(n)]): 134 | foundShoutOut = ' '.join(currCheck[:len(n)]).split(); 135 | for f in foundShoutOut: 136 | if f[0] == _at_key and _megbot_call not in f: 137 | mb.send_message(f[1:].upper()); 138 | 139 | time.sleep(20); 140 | except (URLError, selenium.common.exceptions.StaleElementReferenceException): 141 | print "reconnecting......"; 142 | # Try to reconnect 143 | time.sleep(5) 144 | try: 145 | login(); 146 | except: 147 | continue; 148 | 149 | --------------------------------------------------------------------------------