Skip to content

Commit

Permalink
Merge pull request #4 from MikeCroall/dev
Browse files Browse the repository at this point in the history
Pull Dev into Master for intial (markov - not tensorflow) release
  • Loading branch information
MikeCroall committed Dec 7, 2016
2 parents 6d9e0a9 + 4c0812e commit e5950ee
Show file tree
Hide file tree
Showing 25 changed files with 4,655 additions and 41 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -93,5 +93,9 @@ ENV/
# PyCharm
.idea/

<<<<<<< HEAD
twilio.txt
=======
# API Keys
src/apiKeys.py
src/apiKeys.py
>>>>>>> f00e51d0c203db61c74af7a7033113a1aec80e47
1 change: 1 addition & 0 deletions .slugignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/Android
Binary file added Android/ScreenCaps/TrumpetMan.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Android/TrumpBot/app/app-release.apk
Binary file not shown.
4 changes: 2 additions & 2 deletions Android/TrumpBot/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ android {
applicationId "co.brookesoftware.mike.trumpbot"
minSdkVersion 18
targetSdkVersion 24
versionCode 2
versionName "0.1"
versionCode 4
versionName "0.1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
Expand Down
1 change: 1 addition & 0 deletions Android/TrumpBot/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package="co.brookesoftware.mike.trumpbot">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package co.brookesoftware.mike.trumpbot;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
Expand All @@ -16,10 +21,7 @@
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.InetAddress;
import java.util.ArrayList;

public class ActivityChat extends AppCompatActivity {
Expand Down Expand Up @@ -58,30 +60,46 @@ public void onClick(View v) {
addMessageToList(getString(R.string.You), message);

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
String url = "http://www.google.com";
String uri = String.format("%1$s?q=%2$s",
url,
message);
String url = "https://trumpedupkicks.herokuapp.com/response";
String uri = String.format("%1$s?q=%2$s", url, message);
StringRequest stringRequest = new StringRequest(Request.Method.GET, uri,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
addMessageToList("TrumpBot", response);
addMessageToList(getString(R.string.app_name), response);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
addMessageToList("TrumpBot", "I'm broken! That didn't work!");
addMessageToList(getString(R.string.app_name), "WROOONG!\n\n" +
"Seriously though, this is an error message." +
"Are you sure you're connected to the internet?");
}
});
//queue.add(stringRequest);
queue.add(stringRequest);
}

addMessageToList(getString(R.string.app_name), "WROOONG!");
}
});
}

@Override
protected void onStart() {
super.onStart();
if (!isNetworkAvailable()) {
new AlertDialog.Builder(ActivityChat.this)
.setTitle("No Internet")
.setMessage("We couldn't find an active internet connection. " +
"Please double check your connection and try again!")
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
}

private void addMessageToList(String sender, String message) {
senders.add(sender);
messages.add(message);
Expand All @@ -91,13 +109,20 @@ private void addMessageToList(String sender, String message) {
lstMessages.setSelection(chatListAdapter.getCount() - 1);
}

private void showShortToast(String msg) {
Toast toast = Toast.makeText(this, msg, Toast.LENGTH_SHORT);
private boolean isNetworkAvailable() {
ConnectivityManager connectivityManager
= (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
return activeNetworkInfo != null && activeNetworkInfo.isConnected();
}

public void showShortToast(String msg) {
Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT);
toast.show();
}

private void showLongToast(String msg) {
Toast toast = Toast.makeText(this, msg, Toast.LENGTH_LONG);
public void showLongToast(String msg) {
Toast toast = Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG);
toast.show();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public ChatListAdapter(Context context, ArrayList<String> data) {
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

public void setData(ArrayList<String> data){
public void setData(ArrayList<String> data) {
this.data = data.toArray(new String[data.size()]);
}

public void setUserdata(ArrayList<String> data){
public void setUserdata(ArrayList<String> data) {
this.userdata = data.toArray(new String[data.size()]);
}

Expand All @@ -48,13 +48,11 @@ public long getItemId(int position) {

@Override
public View getView(int position, View convertView, ViewGroup parent) {
View vi = convertView;
if (vi == null) {
if (userdata[position].equals("TrumpBot")) {
vi = inflater.inflate(R.layout.trump_row, null);
} else {
vi = inflater.inflate(R.layout.row, null);
}
View vi;
if (userdata[position].equals("TrumpBot")) {
vi = inflater.inflate(R.layout.trump_row, null);
} else {
vi = inflater.inflate(R.layout.row, null);
}
TextView text = (TextView) vi.findViewById(R.id.text);
text.setText(data[position]);
Expand Down
2 changes: 1 addition & 1 deletion Android/TrumpBot/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.android.tools.build:gradle:2.2.3'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: python server.py
41 changes: 30 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
# TrumpBot
## A chat bot to emulate Donald Trump's speech

###Goals
### Download the App (Android 4.3+)
The Android app is now available on the play store! You can find it [here](https://play.google.com/store/apps/details?id=co.brookesoftware.mike.trumpbot) or by searching for TrumpBot.

### Goals
+ API that receives a string, returns a string response in the style of Donald Trump
* /response will return a Trump style statement.
* /response?q=Query will return Trump's thoughts on your Query
+ Automatically determine Donald Trump's style via tweet analysis (TensorFlow)
+ Text message wrapper for API calls (Twilio)
+ Web wrapper for API calls
+ Android App wrapper for API calls (eventually...)
* /response will return a Trump style statement. - Done
* /response?q=Query will return Trump's thoughts on your Query - Done
+ Automatically determine Donald Trump's style via tweet analysis (TensorFlow) - Not quite... see below
+ Text message wrapper for API calls (Twilio) - Done
+ Web wrapper for API calls - Done
+ Android App wrapper for API calls (eventually...) - Done

#### API Functionality
+ Train Neural Network
+ getResponse : input string -> output string

### Problems We Encountered
+ Tensorflow didn't seem to work, so after many hours of attempted (and failed) bug fixes, we switched from LSTM networks to simple Markov Chains.
+ Twitter has a limit on the number of tweets you can download. An alternative method of harvesting tweets was found.

###Dependencies
### Dependencies
+ Python 3.5.2
+ Flask Python Package
+ TensorFlow Python Package
+ Twython Python Package
+ tflearn Python Package
+ h5py Python Package
+ scipy Python Package
+ Ngrok (temporary, for tunelling local server to the internet)
+ OhmGeek's fork of markov-sentence-generator (for generating the sentences)

###Running the bot
### Running the bot
On Linux, simply open terminal, navigate to the folder where `run.sh` is located
```
$ cd /path/to/server/
Expand All @@ -34,3 +43,13 @@ Finally, run the script
```
$ ./run.sh
```

### Twilio
To use Twilio, you will need to create an account, and go through all the Twilio sign up procedure (including gettting a phone number).

Once you have done this, go to the phone.py script and enter the URI of the main bot server.

```
$ python3 phone.py
```
This will run the phone server. Then enter the URI of the phone server in the section called 'Web Hooks' on Twilio.
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
click==6.6
Flask==0.11.1
Flask-Cors==3.0.2
h5py==2.6.0
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
numpy==1.11.2
six==1.10.0
Werkzeug==0.11.11
1 change: 1 addition & 0 deletions runtime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-3.5.2
131 changes: 131 additions & 0 deletions senGen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
#!/usr/bin/python

import re
import random
import sys


class SentenceGenerator(object):

def __init__(self):
self.tempMapping = {}
self.mapping = {}
self.starts = []

def fixCaps(self, word):
if word.isupper() and word != "I":
word = word.lower()
elif word[0].isupper():
word = word.lower().capitalize()
else:
word = word.lower()
return word

def toHashKey(self, lst):
return tuple(lst)

def wordlist(self, filename):
f = open(filename, 'r')
wordlist = [self.fixCaps(w)
for w in re.findall(r"#?[\w']+|[.,!?;]", f.read())]
f.close()
return wordlist

def addItemToTempMapping(self, history, word):
while len(history) > 0:
first = self.toHashKey(history)
if first in self.tempMapping:
if word in self.tempMapping[first]:
self.tempMapping[first][word] += 1.0
else:
self.tempMapping[first][word] = 1.0
else:
self.tempMapping[first] = {}
self.tempMapping[first][word] = 1.0
history = history[1:]

# Building and normalizing the mapping.
def buildMapping(self, wordlist, markovLength):
self.starts.append(wordlist[0])
for i in range(1, len(wordlist) - 1):
if i <= markovLength:
history = wordlist[: i + 1]
else:
history = wordlist[i - markovLength + 1: i + 1]
follow = wordlist[i + 1]
# if the last elt was a period, add the next word to the start list
if history[-1] == "." and follow not in ".,!?;":
self.starts.append(follow)
self.addItemToTempMapping(history, follow)
# Normalize the values in tempMapping, put them into mapping
for first, followset in self.tempMapping.items():
total = sum(followset.values())
# Normalizing here:
self.mapping[first] = dict([(k, v / total)
for k, v in followset.items()])

# Returns the next word in the sentence (chosen randomly),
# given the previous ones.
def next(self, prevList):
sum = 0.0
retval = ""
index = random.random()
# Shorten prevList until it's in mapping
while self.toHashKey(prevList) not in self.mapping and len(prevList) != 0:
prevList.pop(0)
# Get a random word from the mapping, given prevLis
if(len(prevList) == 0):
prevList = ['wall']
for k, v in self.mapping[self.toHashKey(prevList)].items():
sum += v
if sum >= index and retval == "":
retval = k
return retval

def genSentence(self, markovLength, startWord=None):

if(startWord is None):
startWord = "wall"
# Start with a random "starting word"
curr = startWord
sent = curr.capitalize()
prevList = [curr]
if(len(self.next([curr])) == 0):
curr = "wall"
sent = "WALL"
prevList = [curr]
# Keep adding words until we hit a period
while (curr not in "."):
curr = self.next(prevList)
prevList.append(curr)
# if the prevList has gotten too long, trim it
if len(prevList) > markovLength:
prevList.pop(0)
if (curr not in ".,!?;"):
sent += " " # Add spaces between words (but not punctuation)
sent += curr
return sent


# These mappings can get fairly large -- they're stored globally to
# save copying time.

# (tuple of words) -> {dict: word -> number of times the word appears following the tuple}
# Example entry:
# ('eyes', 'turned') => {'to': 2.0, 'from': 1.0}
# Used briefly while first constructing the normalized mapping
# (tuple of words) -> {dict: word -> *normalized* number of times the word appears following the tuple}
# Example entry:
# ('eyes', 'turned') => {'to': 0.66666666, 'from': 0.33333333}

# Contains the set of words that can start sentences

# We want to be able to compare words independent of their capitalization.
# affect processing time too negatively.
# Returns the contents of the file, split into a list of words and
# (some) punctuation.
# Self-explanatory -- adds "word" to the "tempMapping" dict under "history".
# tempMapping (and mapping) both match each word to a list of possible next
# words.
# Given history = ["the", "rain", "in"] and word = "Spain", we add "Spain" to
# the entries for ["the", "rain", "in"], ["rain", "in"], and ["in"].

0 comments on commit e5950ee

Please sign in to comment.