-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added IO adapter for communicating with users through twitter.
- Loading branch information
1 parent
ab89c89
commit 760ced2
Showing
9 changed files
with
561 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
from .io import IOAdapter | ||
from .terminal import TerminalAdapter | ||
from .io_json import JsonAdapter | ||
from .twitter_io import TwitterAdapter | ||
from .no_output import NoOutputAdapter | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
# -*- encoding: utf-8 -*- | ||
from chatterbot.adapters.io import IOAdapter | ||
from chatterbot.conversation import Statement | ||
import twitter | ||
import threading | ||
import time | ||
|
||
try: | ||
from queue import Queue | ||
except ImportError: | ||
# Use the python 2 queue import | ||
from Queue import Queue | ||
|
||
|
||
class SimulatedAnnealingScheduler(object): | ||
""" | ||
This class implements a simulated annealing algorithm to determine | ||
the correct schedule for running a function. | ||
The benefit of this class is that it is more efficient than interval | ||
checking when a given function may yield a greater probability of | ||
returning similar consecutive results. | ||
""" | ||
|
||
def __init__(self, function, comparison_function, interval=5): | ||
""" | ||
Takes a function to be run periodically and a comparison function to | ||
determine if the result of the function is true or false. | ||
""" | ||
self.function = function | ||
self.check = comparison_function | ||
|
||
self.interval = interval | ||
|
||
# INTERVAL_MIN = 1 second | ||
self.INTERVAL_MIN = 1 | ||
|
||
# INTERVAL_MAX = number of seconds in 1 day | ||
self.INTERVAL_MAX = 60 * 60 * 24 | ||
|
||
self.INCREMENT_AMOUNT = 2 | ||
self.DECREMENT_AMOUNT = 2 | ||
|
||
self.thread = threading.Thread(target=self.start, args=()) | ||
self.thread.daemon = True | ||
self.thread.start() | ||
|
||
def get_temperature(self, scaling_factor=10): | ||
pass | ||
|
||
def decrease_interval(self): | ||
""" | ||
Decrement the interval as long as doing so will not cause it to | ||
decrease past the predefined minimum. | ||
""" | ||
if (self.interval - self.DECREMENT_AMOUNT) >= self.INTERVAL_MIN: | ||
self.interval -= self.DECREMENT_AMOUNT | ||
|
||
def increase_interval(self): | ||
""" | ||
Increment the interval as long as doing so will not cause it to | ||
increase past the predefined maximum. | ||
""" | ||
if (self.interval + self.INCREMENT_AMOUNT) <= self.INTERVAL_MAX: | ||
self.interval += self.INCREMENT_AMOUNT | ||
|
||
def start(self): | ||
while True: | ||
result = self.function() | ||
|
||
if self.check(result): | ||
self.increase_interval() | ||
else: | ||
self.decrease_interval() | ||
|
||
time.sleep(self.interval) | ||
|
||
def stop(self): | ||
self.thread.stop() | ||
|
||
|
||
class TwitterAdapter(IOAdapter): | ||
|
||
def __init__(self, **kwargs): | ||
super(TwitterAdapter, self).__init__(**kwargs) | ||
|
||
self.api = twitter.Api( | ||
consumer_key=kwargs["twitter_consumer_key"], | ||
consumer_secret=kwargs["twitter_consumer_secret"], | ||
access_token_key=kwargs["twitter_access_token_key"], | ||
access_token_secret=kwargs["twitter_access_token_secret"] | ||
) | ||
|
||
self.mention_queue = Queue() | ||
self.direct_message_queue = Queue() | ||
|
||
self.message_checker = SimulatedAnnealingScheduler( | ||
self.get_messages, | ||
self.is_new_message | ||
) | ||
|
||
def post_update(self, message): | ||
return self.api.PostUpdate(message) | ||
|
||
def favorite(self, tweet_id): | ||
return self.api.CreateFavorite(id=tweet_id) | ||
|
||
def follow(self, username): | ||
return self.api.CreateFriendship(screen_name=username) | ||
|
||
def get_list_users(self, username, slug): | ||
return self.api.GetListMembers(None, slug, owner_screen_name=username) | ||
|
||
def get_mentions(self): | ||
mentions = self.api.GetMentions() | ||
|
||
print "get_mentions:", mentions | ||
return mentions | ||
|
||
def is_new_message(self, data): | ||
print "message data:" | ||
for d in data: | ||
print "\t", d.text | ||
print "\t", d | ||
|
||
return True | ||
|
||
def get_messages(self): | ||
return self.api.GetDirectMessages(count=5) | ||
|
||
def search(self, q, count=1, result_type="mixed"): | ||
return self.api.GetSearch(term=q, count=count, result_type=result_type) | ||
|
||
def get_related_messages(self, text): | ||
results = search(text, count=50) | ||
replies = [] | ||
non_replies = [] | ||
|
||
for result in results["statuses"]: | ||
|
||
# Select only results that are replies | ||
if result["in_reply_to_status_id_str"] is not None: | ||
message = result["text"] | ||
replies.append(message) | ||
|
||
# Save a list of other results in case a reply cannot be found | ||
else: | ||
message = result["text"] | ||
non_replies.append(message) | ||
|
||
if len(replies) > 0: | ||
return replies | ||
|
||
return non_replies | ||
|
||
def reply(self, tweet_id, message): | ||
""" | ||
Reply to a tweet | ||
""" | ||
return self.api.PostUpdate(message, in_reply_to_status_id=tweet_id) | ||
|
||
def tweet_to_friends(self, username, slug, greetings, debug=False): | ||
""" | ||
Tweet one random message to the next friend in a list every hour. | ||
The tweet will not be sent and will be printed to the console when in | ||
debug mode. | ||
""" | ||
from time import time, sleep | ||
from random import choice | ||
|
||
# Get the list of robots | ||
robots = self.get_list_users(username, slug=slug) | ||
|
||
for robot in robots: | ||
message = ("@" + robot + " " + choice(greetings)).strip("\n") | ||
|
||
if debug is True: | ||
print(message) | ||
else: | ||
sleep(3600-time() % 3600) | ||
t.statuses.update(status=message) | ||
|
||
def has_responeded_to_message(self, message_id): | ||
# TODO | ||
pass | ||
|
||
def process_input(self): | ||
""" | ||
This method should check twitter for new mentions and | ||
return them as Statement objects. | ||
""" | ||
# Download a list of recent mentions | ||
mentions = self.get_mentions() | ||
|
||
print "MENTIONS:", mentions | ||
|
||
for mention in mentions: | ||
mention = Statement(mention.text) | ||
|
||
# Add the mention to the mention queue if a response has not been made | ||
if not self.has_responeded_to_message(mention): | ||
self.mention_queue.put(mention) | ||
|
||
def process_response(self, input_statement): | ||
return input_statement | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,3 +37,4 @@ | |
|
||
except (KeyboardInterrupt, EOFError, SystemExit): | ||
break | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,41 @@ | ||
from chatterbot import ChatBot | ||
from settings import TWITTER | ||
import time | ||
|
||
''' | ||
Respond to mentions on twitter. | ||
The bot will follow the user who mentioned it and | ||
favorite the post in which the mention was made. | ||
The bot will respond to mentions and direct messages on twitter. | ||
To use this example, create a new settings.py file. | ||
Define the following in settings.py: | ||
TWITTER = {} | ||
TWITTER["CONSUMER_KEY"] = "your-twitter-public-key" | ||
TWITTER["CONSUMER_SECRET"] = "your-twitter-sceret-key" | ||
''' | ||
|
||
|
||
chatbot = ChatBot("ChatterBot", | ||
storage_adapter="chatterbot.adapters.storage.JsonDatabaseAdapter", | ||
logic_adapter="chatterbot.adapters.logic.ClosestMatchAdapter", | ||
io_adapter="chatterbot.adapters.io.TwitterAdapter", | ||
database="../database.db") | ||
database="../database.db", | ||
twitter_consumer_key=TWITTER["CONSUMER_KEY"], | ||
twitter_consumer_secret=TWITTER["CONSUMER_SECRET"], | ||
twitter_access_token_key=TWITTER["ACCESS_TOKEN"], | ||
twitter_access_token_secret=TWITTER["ACCESS_TOKEN_SECRET"] | ||
) | ||
|
||
for mention in chatbot.get_mentions(): | ||
time.sleep(200) | ||
|
||
''' | ||
Check to see if the post has been favorited | ||
We will use this as a check for whether or not to respond to it. | ||
Only respond to unfavorited mentions. | ||
''' | ||
''' | ||
while True: | ||
try: | ||
user_input = chatbot.get_input() | ||
if not mention["favorited"]: | ||
screen_name = mention["user"]["screen_name"] | ||
text = mention["text"] | ||
response = chatbot.get_response(text) | ||
bot_input = chatbot.get_response(user_input) | ||
print(text) | ||
print(response) | ||
# Pause before checking for the next message | ||
time.sleep(25) | ||
chatbot.follow(screen_name) | ||
chatbot.favorite(mention["id"]) | ||
chatbot.reply(mention["id"], response) | ||
except (KeyboardInterrupt, EOFError, SystemExit): | ||
break | ||
''' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ fuzzywuzzy>=0.8.0 | |
jsondatabase>=0.0.7 | ||
nltk<4.0.0 | ||
pymongo>=3.0.3,<4.0.0 | ||
python-twitter>=2.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from unittest import TestCase | ||
from chatterbot.adapters.io import TwitterAdapter | ||
|
||
|
||
class TwitterAdapterTests(TestCase): | ||
pass | ||
|
||
''' | ||
def setUp(self): | ||
self.adapter = TwitterAdapter( | ||
twitter_consumer_key="blahblahblah", | ||
twitter_consumer_secret="nullvoidnullvoidnullvoid", | ||
twitter_access_token_key="blahblahblah", | ||
twitter_access_token_secret="nullvoidnullvoidnullvoid" | ||
) | ||
def test_get_mentions(self): | ||
from.twitter_data.mentions import MENTIONS | ||
mentions = self.adapter.get_mentions() | ||
''' | ||
|
Empty file.
Oops, something went wrong.