├── tests
├── __init__.py
├── __main__.py
└── web_test.py
├── Procfile
├── requirements.txt
├── static
├── img
│ ├── header.png
│ ├── pineapplecut.png
│ ├── white_plaster.png
│ ├── whitediamond.png
│ ├── header-380x127.png
│ ├── header-460x154.png
│ ├── header-540x181.png
│ └── header-620x207.png
├── images
│ ├── favicon.ico
│ ├── apple-touch-icon.png
│ ├── social-thumbnail.png
│ ├── apple-touch-icon-72x72.png
│ └── apple-touch-icon-114x114.png
├── javascripts
│ ├── tabs.js
│ ├── app.js
│ └── jquery-1.5.1.min.js
└── stylesheets
│ ├── layout.css
│ ├── index.css
│ ├── base.min.css
│ ├── 960.css
│ ├── skeleton.min.css
│ ├── base.css
│ └── skeleton.css
├── .gitignore
├── local_settings.py
├── README.md
├── app.py
└── templates
└── index.html
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: python app.py
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Flask==0.7.2
2 | Twilio==3.3.2
3 |
--------------------------------------------------------------------------------
/static/img/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/header.png
--------------------------------------------------------------------------------
/static/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/images/favicon.ico
--------------------------------------------------------------------------------
/static/img/pineapplecut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/pineapplecut.png
--------------------------------------------------------------------------------
/static/img/white_plaster.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/white_plaster.png
--------------------------------------------------------------------------------
/static/img/whitediamond.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/whitediamond.png
--------------------------------------------------------------------------------
/static/img/header-380x127.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/header-380x127.png
--------------------------------------------------------------------------------
/static/img/header-460x154.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/header-460x154.png
--------------------------------------------------------------------------------
/static/img/header-540x181.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/header-540x181.png
--------------------------------------------------------------------------------
/static/img/header-620x207.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/img/header-620x207.png
--------------------------------------------------------------------------------
/static/images/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/images/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/images/social-thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/images/social-thumbnail.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | build
3 | include
4 | lib
5 | .Python
6 | *.pyc
7 | *.project
8 | *.pydevproject
9 | *.pyo
10 | *.mp3
11 | *.swp
12 |
--------------------------------------------------------------------------------
/static/images/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/images/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/static/images/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RobSpectre/Reasons-Sonya-Is-Awesome/HEAD/static/images/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/tests/__main__.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import web_test
3 |
4 | def runTests(verbosity=2):
5 | # Loader
6 | loader = unittest.TestLoader()
7 |
8 | # Suite
9 | suite = unittest.TestSuite()
10 |
11 | # Load test suite
12 | suite.addTests(loader.loadTestsFromModule(web_test))
13 |
14 | # Run test suite
15 | runner = unittest.TextTestRunner(verbosity=verbosity)
16 | return runner.run(suite)
17 |
18 | if __name__ == "__main__":
19 | runTests()
20 |
--------------------------------------------------------------------------------
/local_settings.py:
--------------------------------------------------------------------------------
1 | '''
2 | Configuration Settings
3 |
4 | Includes keys for Twilio, etc. Second stanza intended for Heroku deployment.
5 | '''
6 |
7 | # Uncommet to configure in file.
8 | #ACCOUNT_SID = "ACxxxxxxxxxxxxx"
9 | #AUTH_TOKEN = "yyyyyyyyyyyyyyyy"
10 | #SONYA_APP_SID = "APzzzzzzzzz"
11 | #SONYA_CALLER_ID = "+17778889999"
12 |
13 |
14 | # Begin Heroku configuration - configured through environment variables.
15 | import os
16 | ACCOUNT_SID = os.environ['ACCOUNT_SID']
17 | AUTH_TOKEN = os.environ['AUTH_TOKEN']
18 | SONYA_APP_SID = os.environ['SONYA_APP_SID']
19 | SONYA_CALLER_ID = os.environ['SONYA_CALLER_ID']
20 |
--------------------------------------------------------------------------------
/static/javascripts/tabs.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V1.1
3 | * Copyright 2011, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 8/17/2011
8 | */
9 |
10 |
11 | $(document).ready(function() {
12 |
13 | /* Tabs Activiation
14 | ================================================== */
15 |
16 | var tabs = $('ul.tabs');
17 |
18 | tabs.each(function(i) {
19 |
20 | //Get all tabs
21 | var tab = $(this).find('> li > a');
22 | tab.click(function(e) {
23 |
24 | //Get Location of tab's content
25 | var contentLocation = $(this).attr('href');
26 |
27 | //Let go if not a hashed one
28 | if(contentLocation.charAt(0)=="#") {
29 |
30 | e.preventDefault();
31 |
32 | //Make Tab Active
33 | tab.removeClass('active');
34 | $(this).addClass('active');
35 |
36 | //Show Tab Content & add active class
37 | $(contentLocation).show().addClass('active').siblings().hide().removeClass('active');
38 |
39 | }
40 | });
41 | });
42 | });
--------------------------------------------------------------------------------
/static/javascripts/app.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V1.0.2
3 | * Copyright 2011, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 5/20/2011
8 | */
9 |
10 |
11 | $(document).ready(function() {
12 |
13 | /* Tabs Activiation
14 | ================================================== */
15 | var tabs = $('ul.tabs'),
16 | tabsContent = $('ul.tabs-content');
17 |
18 | tabs.each(function(i) {
19 | //Get all tabs
20 | var tab = $(this).find('> li > a');
21 | tab.click(function(e) {
22 |
23 | //Get Location of tab's content
24 | var contentLocation = $(this).attr('href') + "Tab";
25 |
26 | //Let go if not a hashed one
27 | if(contentLocation.charAt(0)=="#") {
28 |
29 | e.preventDefault();
30 |
31 | //Make Tab Active
32 | tab.removeClass('active');
33 | $(this).addClass('active');
34 |
35 | //Show Tab Content
36 | $(contentLocation).show().siblings().hide();
37 |
38 | }
39 | });
40 | });
41 |
42 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reasons Sonya Is Awesome
2 |
3 | A web, voice and SMS app delivering random reasons why my friend Sonya is
4 | awesome.
5 |
6 | Delivered for her birthday, January 2012.
7 |
8 |
9 | ## Summary
10 |
11 | Happy birthday Sonya! I cut you some codez.
12 |
13 | Text or call (718)285-0684 to use the mobile experience.
14 |
15 | ## Getting Started
16 |
17 | Feel free to personalize this project for the special people in your life.
18 |
19 | 1. Create a Twilio account if you do not already have one.
20 | 2. Buy a phone number.
21 | 3. Create a TwIML application. Associate your phone number with this appliation.
22 | 4. In local_settings.py uncomment lines 8-11. Changes the variables to the ones you have set up on your Twilio account.
23 | You will see your ACCOUNT SID and your AUTH TOKEN on the top of your Twilio account Dashboard. The APP SID is the name of your TwIML application.
24 |
25 | ```python
26 | Uncommet to configure in file.
27 | ACCOUNT_SID = "ACxxxxxxxxxxxxx"
28 | AUTH_TOKEN = "yyyyyyyyyyyyyyyy"
29 | SONYA_APP_SID = "APzzzzzzzzz"
30 | SONYA_CALLER_ID = "+17778889999"
31 | ```
32 | 5. Set you your Voice Request URL for your Twilio number to Application_url/voice
33 |
34 | For example
35 |
36 | http://immense-oasis-2092.herokuapp.com/voice
37 |
38 | And your SMS Request URL to Application_url/sms
39 |
40 | For example
41 |
42 | http://immense-oasis-2092.herokuapp.com/sms
43 |
44 |
45 | ## Technology
46 |
47 | I'm using a bunch of fun stuff here:
48 |
49 | * [Flask](http://flask.pocoo.org/)
50 | * [Heroku](http://www.heroku.com)
51 | * [Twilio](http://www.twilio.com)
52 | * [Skeleton](http://www.getskeleton.com)
53 |
54 |
55 | ## Credits
56 | * Authors: [Rob Spectre](http://www.brooklynhacker.com) and [Alex
57 | Aizenberg](http://www.build-a-beard.com)
58 | * Artist: [Brendan O'Brien](http://partoftheprocess.ca)
59 | * Documentation: [Veronica Ray](https://github.com/mathonsunday)
60 | * License: [Mozilla Public License](http://www.mozilla.org/MPL/)
61 | * Ruby / Sinatra Port: [Steven Chau](https://github.com/whereisciao/Reasons-Sonya-Is-Awesome)
62 |
--------------------------------------------------------------------------------
/static/stylesheets/layout.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Skeleton V1.0.2
3 | * Copyright 2011, Dave Gamache
4 | * www.getskeleton.com
5 | * Free to use under the MIT license.
6 | * http://www.opensource.org/licenses/mit-license.php
7 | * 5/20/2011
8 | */
9 |
10 | /* Table of Content
11 | ==================================================
12 | #Site Styles
13 | #Page Styles
14 | #Media Queries
15 | #Font-Face */
16 |
17 | /* #Site Styles
18 | ================================================== */
19 |
20 | /* #Page Styles
21 | ================================================== */
22 |
23 | /* #Media Queries
24 | ================================================== */
25 |
26 | /* iPad Portrait/Browser */
27 | @media only screen and (min-width: 768px) and (max-width: 991px) {}
28 |
29 | /* Mobile/Browser */
30 | @media only screen and (max-width: 767px) {}
31 |
32 | /* Mobile Landscape/Browser */
33 | @media only screen and (min-width: 480px) and (max-width: 767px) {}
34 |
35 | /* Anything smaller than standard 960 */
36 | @media only screen and (max-width: 959px) {}
37 |
38 | /* iPad Portrait Only */
39 | @media only screen and (min-width: 768px) and (max-width: 991px) and (max-device-width: 1000px) {}
40 |
41 | /* Mobile Only */
42 | @media only screen and (max-width: 767px) and (max-device-width: 1000px) {}
43 |
44 | /* Mobile Landscape Only */
45 | @media only screen and (min-width: 480px) and (max-width: 767px) and (max-device-width: 1000px) {}
46 |
47 |
48 | /* #Font-Face
49 | ================================================== */
50 | /* This is the proper syntax for an @font-face file
51 | Just create a "fonts" folder at the root,
52 | copy your FontName into code below and remove
53 | comment brackets */
54 |
55 | /* @font-face {
56 | font-family: 'FontName';
57 | src: url('../fonts/FontName.eot');
58 | src: url('../fonts/FontName.eot?iefix') format('eot'),
59 | url('../fonts/FontName.woff') format('woff'),
60 | url('../fonts/FontName.ttf') format('truetype'),
61 | url('../fonts/FontName.svg#webfontZam02nTh') format('svg');
62 | font-weight: normal;
63 | font-style: normal; }
64 | */
--------------------------------------------------------------------------------
/static/stylesheets/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: url('/static/img/white_plaster.png') top center repeat;
3 | font-family: Helvetica, Verdana, sans-serif;
4 | }
5 |
6 | #page {
7 | z-index 1;
8 | width: 100%;
9 | min-height: 300px;
10 | background: rgb(230,230,230);
11 | box-shadow: 0 0 4px rgb(120,120,120);
12 | background-color: #eeeeee;
13 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #cccccc));
14 | background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
15 | background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
16 | background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
17 | background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
18 | background-image: linear-gradient(top, #eeeeee, #cccccc);
19 | border: 1px solid #ccc;
20 | border-bottom: 1px solid #bbb;
21 | -webkit-border-radius: 0 0 3px 3px;
22 | -moz-border-radius: 3px;
23 | -o-border-radius: 3px;
24 | border-radius: 3px;
25 | }
26 | #page ul {
27 | list-style-type: none;
28 | padding: 25px 10px;
29 | margin-bottom: 50px;
30 | margin-left: 4em;
31 | }
32 | #page ul li {
33 | line-height: 1.5em;
34 | margin-top: 5px;
35 | font-size: 2.0em;
36 | font-weight: bold;
37 | color: rgb(60,60,60);
38 | text-shadow: 0 0 1px rgb(120,120,120);
39 | }
40 |
41 | #footer p {
42 | color: black;
43 | }
44 |
45 | #footer a, #footer a:visited, #footer a:hover {
46 | color: #5c57a6;
47 | }
48 |
49 | #call-options li {
50 | display: inline;
51 | }
52 |
53 | #call-options, #call-status {
54 | display: none;
55 | }
56 |
57 | #call-button, #option1, #option2, #option3, #option4 {
58 | font-size: 24px;
59 | padding: 8px 24px;
60 | }
61 |
62 | #header {
63 | background: url('/static/img/header.png');
64 | width: 780px;
65 | height: 261px;
66 | z-index: 3;
67 | margin-bottom: -18px;
68 | }
69 |
70 | #social {
71 | z-index: 5;
72 | }
73 |
74 | @media only screen and (max-width: 959px) {
75 | #header {
76 | background: url('/static/img/header-620x207.png');
77 | width: 620px;
78 | height: 207px;
79 | }
80 |
81 | #page {
82 | z-index: 1;
83 | }
84 | }
85 |
86 | @media only screen and (min-width: 480px) and (max-width: 767px) {
87 | #header {
88 | background: url('/static/img/header-540x181.png');
89 | width: 540px;
90 | height: 181px;
91 | margin-bottom: -11px;
92 | margin-left: -60px;
93 | z-index: 3;
94 | }
95 |
96 | #page {
97 | z-index: 1;
98 | }
99 |
100 | #page ul {
101 | margin-left: 2em;
102 | }
103 |
104 | #page ul li {
105 | line-height: 1.2em;
106 | font-size: 1.7em;
107 | }
108 | }
109 |
110 | @media only screen and (max-width: 479px) {
111 | #header {
112 | background: url('/static/img/header-380x127.png');
113 | width: 380px;
114 | height: 127px;
115 | margin-bottom: -10px;
116 | margin-left: -40px;
117 | z-index: 3;
118 | }
119 |
120 | #page {
121 | z-index: 1;
122 | }
123 |
124 | #page ul {
125 | margin-left: 1em;
126 | }
127 |
128 | #page ul li {
129 | line-height: 1.0em;
130 | font-size: 1.5em;
131 | }
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/tests/web_test.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | try:
3 | from app import app
4 | except ImportError:
5 | import sys
6 | import os.path
7 | sys.path.append(os.path.abspath('.'))
8 | from app import app
9 |
10 |
11 | class WebTest(unittest.TestCase):
12 | def setUp(self):
13 | self.app = app.test_client()
14 | self.app.debug = True
15 |
16 | def assertTwiML(self, response):
17 | self.assertTrue("" in response.data, "Did not find " \
18 | ": %s" % response.data)
19 | self.assertTrue("" in response.data, "Did not find " \
20 | ": %s" % response.data)
21 | self.assertEqual("200 OK", response.status)
22 |
23 | def sms(self, body, path='/sms', number='+15555555555'):
24 | params = {
25 | 'SmsSid': 'SMtesting',
26 | 'AccountSid': 'ACtesting',
27 | 'From': number,
28 | 'To': '+16666666666',
29 | 'Body': body,
30 | 'ApiVersion': '2010-04-01',
31 | 'Direction': 'inbound'}
32 | return self.app.post(path, data=params)
33 |
34 | def call(self, path='/voice', number='+15555555555', digits=None):
35 | params = {
36 | 'CallSid': 'CAtesting',
37 | 'AccountSid': 'ACtesting',
38 | 'From': number,
39 | 'To': '+16666666666',
40 | 'CallStatus': 'ringing',
41 | 'ApiVersion': '2010-04-01',
42 | 'Direction': 'inbound'}
43 | if digits:
44 | params['Digits'] = digits
45 | return self.app.post(path, data=params)
46 |
47 |
48 | class Test_Voice(WebTest):
49 | def test_voice(self):
50 | response = self.call()
51 | self.assertTwiML(response)
52 | self.assertTrue(" in response: %s" % response.data)
56 | self.assertTrue("action=\"/gather\"" in response.data, "Did not find " \
57 | "action attribute in response: %s" % response.data)
58 | self.assertTrue(" in response: %s" % response.data)
60 |
61 | def test_repeat(self):
62 | response = self.call('/gather', digits='1')
63 | self.assertTwiML(response)
64 | self.assertTrue(':' in response.data, "Did not find " \
65 | "repeated reason in response: %s" % response.data)
66 |
67 | def test_repeatHangup(self):
68 | response = self.call('/gather', digits='2')
69 | self.assertTwiML(response)
70 | self.assertTrue(' in response: %s" % response.data)
72 |
73 | def test_songsWrongInput(self):
74 | for i in range(5, 10):
75 | response = self.call('/gather', digits=str(i))
76 | self.assertTwiML(response)
77 | self.assertTrue(" in response: %s" % response.data)
79 |
80 | def test_songsWeirdInput(self):
81 | characters = ["#", "*"]
82 | for character in characters:
83 | response = self.call('/gather', digits=character)
84 | self.assertTwiML(response)
85 | self.assertTrue(" in response: %s" % response.data)
87 |
88 |
89 | class Test_Sms(WebTest):
90 | def test_sms(self):
91 | response = self.sms("Testing.")
92 | self.assertTwiML(response)
93 | self.assertTrue(":" in response.data, "Did not find " \
94 | "a preface colon in response: %s" % response.data)
95 |
96 | def test_smsHelp(self):
97 | response = self.sms("help")
98 | self.assertTwiML(response)
99 | self.assertTrue("GIMME" in response.data, "Did not find " \
100 | "GIMME in response: %s" % response.data)
101 |
102 |
103 | class Test_Web(WebTest):
104 | def test_index(self):
105 | response = self.app.get('/')
106 | self.assertTrue("