diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..0367d7f --- /dev/null +++ b/Pipfile @@ -0,0 +1,53 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +appnope = "==0.1.0" +certifi = "==2017.4.17" +chardet = "==3.0.4" +click = "==6.7" +decorator = "==4.0.11" +falcon = "==1.2.0" +future = "==0.16.0" +google-api-python-client = "==1.6.2" +httplib2 = "==0.10.3" +hug = "==2.3.0" +idna = "==2.6" +ipython = "==6.1.0" +itsdangerous = "==0.24" +jedi = "==0.10.2" +oauth2client = "==4.1.1" +parsedatetime = "==2.4" +parso = "==0.1.0" +pexpect = "==4.2.1" +pickleshare = "==0.7.4" +ptyprocess = "==0.5.1" +pyasn1 = "==0.2.3" +pyasn1-modules = "==0.0.9" +python-mimeparse = "==1.6.0" +pytz = "==2017.2" +requests = "==2.18.4" +rsa = "==3.4.2" +simplegeneric = "==0.8.1" +six = "==1.10.0" +traitlets = "==4.3.2" +twilio = "==6.8.1" +uritemplate = "==3.0.0" +urllib3 = "==1.21.1" +wcwidth = "==0.1.7" +Flask = "==0.12.2" +ipython_genutils = "==0.2.0" +Jinja2 = "==2.9.6" +MarkupSafe = "==1.0" +prompt_toolkit = "==1.0.14" +Pygments = "==2.2.0" +PyJWT = "==1.5.0" +PySocks = "==1.6.7" +Werkzeug = "==0.12.2" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..e7fd01d --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,354 @@ +{ + "_meta": { + "hash": { + "sha256": "3ebef50f16c7bb5fb3eca23f8217d8d093c03d7392e668b62489bd280d9977fb" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "appnope": { + "hashes": [ + "sha256:5b26757dc6f79a3b7dc9fab95359328d5747fcb2409d331ea66d0272b90ab2a0", + "sha256:8b995ffe925347a2138d7ac0fe77155e4311a0ea6d6da4f5128fe4b3cbe5ed71" + ], + "index": "pypi", + "version": "==0.1.0" + }, + "certifi": { + "hashes": [ + "sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010", + "sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a" + ], + "index": "pypi", + "version": "==2017.4.17" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "index": "pypi", + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "index": "pypi", + "version": "==6.7" + }, + "decorator": { + "hashes": [ + "sha256:73cbaadb8bc4e3c65fe1100773d56331a2d756cc0f5c7b9d8d5d5223fe04f600", + "sha256:953d6bf082b100f43229cf547f4f97f97e970f5ad645ee7601d55ff87afdfe76" + ], + "index": "pypi", + "version": "==4.0.11" + }, + "falcon": { + "hashes": [ + "sha256:53bb5119d2513613eef6868c829ad47606615ff619590e05f7068a955867173d", + "sha256:6f125ce285a8e9124147497a8d4c202a7316ee550d3ceb003f64d2c5740a9805" + ], + "index": "pypi", + "version": "==1.2.0" + }, + "flask": { + "hashes": [ + "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", + "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" + ], + "index": "pypi", + "version": "==0.12.2" + }, + "future": { + "hashes": [ + "sha256:e39ced1ab767b5936646cedba8bcce582398233d6a627067d4c6a454c90cfedb" + ], + "index": "pypi", + "version": "==0.16.0" + }, + "google-api-python-client": { + "hashes": [ + "sha256:8c2f50f8057571a5f817c74820cadb754d47799c5a4ea463c1500fe8e092c1ae", + "sha256:c0f4bcf398c937a43f382eecb384ba9b3a97ef9c24b156420b1b396fcaadd0af" + ], + "index": "pypi", + "version": "==1.6.2" + }, + "httplib2": { + "hashes": [ + "sha256:e404d3b7bd86c1bc931906098e7c1305d6a3a6dcef141b8bb1059903abb3ceeb" + ], + "index": "pypi", + "version": "==0.10.3" + }, + "hug": { + "hashes": [ + "sha256:2d3b56368a9c07804a244067f08f5c194d555ca05c90ad48445c72d38fb6b7a1" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "idna": { + "hashes": [ + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + ], + "index": "pypi", + "version": "==2.6" + }, + "ipython": { + "hashes": [ + "sha256:5c53e8ee4d4bec27879982b9f3b4aa2d6e3cfd7b26782d250fa117f85bb29814", + "sha256:cd44c7f5ccb2b349ffd6ede10d8e4204c9ab1ede4a8a6c40220cfeb07069171a" + ], + "index": "pypi", + "version": "==6.1.0" + }, + "ipython-genutils": { + "hashes": [ + "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", + "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8" + ], + "index": "pypi", + "version": "==0.2.0" + }, + "itsdangerous": { + "hashes": [ + "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + ], + "index": "pypi", + "version": "==0.24" + }, + "jedi": { + "hashes": [ + "sha256:7abb618cac6470ebbd142e59c23daec5e6e063bfcecc8a43a037d2ab57276f4e", + "sha256:96678411f2ffa444da3a5e7fdd4adc513b728a4a4617b30308be5c950722424b" + ], + "index": "pypi", + "version": "==0.10.2" + }, + "jinja2": { + "hashes": [ + "sha256:2231bace0dfd8d2bf1e5d7e41239c06c9e0ded46e70cc1094a0aa64b0afeb054", + "sha256:ddaa01a212cd6d641401cb01b605f4a4d9f37bfc93043d7f760ec70fb99ff9ff" + ], + "index": "pypi", + "version": "==2.9.6" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "index": "pypi", + "version": "==1.0" + }, + "oauth2client": { + "hashes": [ + "sha256:ed0c670f09db32444f843d5d2f92df00733751dfec9a28b0fd3f00693ff36a04", + "sha256:fd02b705092b76f443028328eaed366135f26c0d3e52bdbf66e649a944938ca1" + ], + "index": "pypi", + "version": "==4.1.1" + }, + "parsedatetime": { + "hashes": [ + "sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b", + "sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094" + ], + "index": "pypi", + "version": "==2.4" + }, + "parso": { + "hashes": [ + "sha256:b573acb69f66a970197b5fdbbdfad3b8a417a520e383133b2b4e708f104bfc9a", + "sha256:c5279916bb417aa2bf634648ff895cf35dce371d7319744884827bfad06f8d7b" + ], + "index": "pypi", + "version": "==0.1.0" + }, + "pexpect": { + "hashes": [ + "sha256:3d132465a75b57aa818341c6521392a06cc660feb3988d7f1074f39bd23c9a92", + "sha256:f853b52afaf3b064d29854771e2db509ef80392509bde2dd7a6ecf2dfc3f0018" + ], + "index": "pypi", + "version": "==4.2.1" + }, + "pickleshare": { + "hashes": [ + "sha256:84a9257227dfdd6fe1b4be1319096c20eb85ff1e82c7932f36efccfe1b09737b", + "sha256:c9a2541f25aeabc070f12f452e1f2a8eae2abd51e1cd19e8430402bdf4c1d8b5" + ], + "index": "pypi", + "version": "==0.7.4" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:7281b5199235adaef6980942840c43753e4ab20dfe41338da634fb41c194f9d8", + "sha256:82c7f8e07d7a0411ff5367a5a8ff520f0112b9179f3e599ee8ad2ad9b943d911", + "sha256:cc66413b1b4b17021675d9f2d15d57e640b06ddfd99bb724c73484126d22622f" + ], + "index": "pypi", + "version": "==1.0.14" + }, + "ptyprocess": { + "hashes": [ + "sha256:0530ce63a9295bfae7bd06edc02b6aa935619f486f0f1dc0972f516265ee81a6", + "sha256:464cb76f7a7122743dd25507650db89cd447c51f38e4671602b3eaa2e38e05ae" + ], + "index": "pypi", + "version": "==0.5.1" + }, + "pyasn1": { + "hashes": [ + "sha256:0439b9bd518418260c2641a571f0e07fce4370cab13b68f19b5e023306c03cad", + "sha256:738c4ebd88a718e700ee35c8d129acce2286542daa80a82823a7073644f706ad" + ], + "index": "pypi", + "version": "==0.2.3" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:9d5008b0c3dc5685aac7977951e7fb222736d5fd817c810fa0cfb08c5fd71630", + "sha256:be0e4157e4a53551279d6c6e366b080527f5fd068616835b4abf32c14f657f5f" + ], + "index": "pypi", + "version": "==0.0.9" + }, + "pygments": { + "hashes": [ + "sha256:78f3f434bcc5d6ee09020f92ba487f95ba50f1e3ef83ae96b9d5ffa1bab25c5d", + "sha256:dbae1046def0efb574852fab9e90209b23f556367b5a320c0bcb871c77c3e8cc" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "pyjwt": { + "hashes": [ + "sha256:ad60a3fb9b393667864ed4b8abc9c3b570747f80bf77a113ead2fbaf0f0cedf3", + "sha256:fd182b728d13f04c289d9b2623d09256d356c9b4a6778018001454a954d7c54b" + ], + "index": "pypi", + "version": "==1.5.0" + }, + "pysocks": { + "hashes": [ + "sha256:18842328a4e6061f084cfba70f6950d9140ecf7418b3df7cef558ebb217bac8d", + "sha256:d00329f27efa157db7efe3ca26fcd69033cd61f83822461ee3f8a353b48e33cf" + ], + "index": "pypi", + "markers": "python_version >= '3.0'", + "version": "==1.6.7" + }, + "python-mimeparse": { + "hashes": [ + "sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78", + "sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282" + ], + "index": "pypi", + "version": "==1.6.0" + }, + "pytz": { + "hashes": [ + "sha256:d1d6729c85acea5423671382868627129432fba9a89ecbb248d8d1c7a9f01c67", + "sha256:f5c056e8f62d45ba8215e5cb8f50dfccb198b4b9fbea8500674f3443e4689589" + ], + "index": "pypi", + "version": "==2017.2" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "index": "pypi", + "version": "==2.18.4" + }, + "rsa": { + "hashes": [ + "sha256:25df4e10c263fb88b5ace923dd84bf9aa7f5019687b5e55382ffcdb8bede9db5", + "sha256:43f682fea81c452c98d09fc316aae12de6d30c4b5c84226642cf8f8fd1c93abd" + ], + "index": "pypi", + "version": "==3.4.2" + }, + "simplegeneric": { + "hashes": [ + "sha256:dc972e06094b9af5b855b3df4a646395e43d1c9d0d39ed345b7393560d0b9173" + ], + "index": "pypi", + "version": "==0.8.1" + }, + "six": { + "hashes": [ + "sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1", + "sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a" + ], + "index": "pypi", + "version": "==1.10.0" + }, + "traitlets": { + "hashes": [ + "sha256:9c4bd2d267b7153df9152698efb1050a5d84982d3384a37b2c1f7723ba3e7835", + "sha256:c6cb5e6f57c5a9bdaa40fa71ce7b4af30298fbab9ece9815b5d995ab6217c7d9" + ], + "index": "pypi", + "version": "==4.3.2" + }, + "twilio": { + "hashes": [ + "sha256:919a603186afb69c25746a65e9b1956e02531d4a763b311d546c4857fdf16e67", + "sha256:f146ac865bfdca3a79deb07aa3176d0a66a110a2ad55201792938e1af452d9ac" + ], + "index": "pypi", + "version": "==6.8.1" + }, + "uritemplate": { + "hashes": [ + "sha256:01c69f4fe8ed503b2951bef85d996a9d22434d2431584b5b107b2981ff416fbd", + "sha256:1b9c467a940ce9fb9f50df819e8ddd14696f89b9a8cc87ac77952ba416e0a8fd", + "sha256:c02643cebe23fc8adb5e6becffe201185bf06c40bda5c0b4028a93f1527d011d" + ], + "index": "pypi", + "version": "==3.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:8ed6d5c1ff9d6ba84677310060d6a3a78ca3072ce0684cb3c645023009c114b1", + "sha256:b14486978518ca0901a76ba973d7821047409d7f726f22156b24e83fd71382a5" + ], + "index": "pypi", + "version": "==1.21.1" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "index": "pypi", + "version": "==0.1.7" + }, + "werkzeug": { + "hashes": [ + "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26", + "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d" + ], + "index": "pypi", + "version": "==0.12.2" + } + }, + "develop": {} +} diff --git a/basic_sitter_bot.py b/basic_sitter_bot.py index cbccca5..0462770 100644 --- a/basic_sitter_bot.py +++ b/basic_sitter_bot.py @@ -49,11 +49,13 @@ def add_sitter(body: str) -> Tuple[str, str]: assert len(num_only) == 10 - sitters[lowercase_name] = f'+1{num_only}' + phone_number = f'+1{num_only}' + sitters[lowercase_name] = {'num': phone_number, + 'name': lowercase_name} - return name, sitters[lowercase_name] + return name, phone_number if __name__ == '__main__': - app.run(debug=True, port=8000) + app.run(debug=True, port=8000, use_reloader=False) diff --git a/basic_sitter_bot_with_pickle.py b/basic_sitter_bot_with_pickle.py index 9e62c62..4e2eec2 100644 --- a/basic_sitter_bot_with_pickle.py +++ b/basic_sitter_bot_with_pickle.py @@ -11,13 +11,13 @@ BOOKER_NUM = os.getenv('MY_TWILIO_NUM') twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN')) -app = Flask(__name__) -app.config.from_object(__name__) - sitters = {} if os.path.exists('sitters.p'): sitters = pickle.load(open('sitters.p', 'rb')) +app = Flask(__name__) +app.config.from_object(__name__) + @app.route('/bot', methods=['POST']) def bot(): from_ = request.values.get('From') @@ -91,5 +91,5 @@ if __name__ == '__main__': sitter_list = 'Your sitters are ' + ' and '.join( f'{sitter_name.title()}' for sitter_name in sitters) + '.' twilio_client.api.account.messages.create(to=MY_CELL, from_=BOOKER_NUM, body=sitter_list) - app.run(debug=True, port=8000) - + print(sitter_list) + app.run(debug=True, port=8000, use_reloader=False) diff --git a/sitter_bot.py b/sitter_bot.py index 74709f1..29cce76 100644 --- a/sitter_bot.py +++ b/sitter_bot.py @@ -14,7 +14,7 @@ from twilio.twiml.messaging_response import MessagingResponse twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN')) MY_CELL = os.getenv('MY_CELL') -BOOKER_NUM = os.getenv('MY_TWILIO_NUM') +BOT_NUM = os.getenv('MY_TWILIO_NUM') COUNTRY_CODE = f'+{os.getenv("TWILIO_COUNTRY_CODE")}' TIMEOUT_MINUTES = 120 @@ -70,11 +70,11 @@ def bot() -> str: else: try: - start_time, end_time = request_booking(body) + start_datetime, end_time = request_booking(body) except ValueError: response = 'Please specify an end time (e.g. "tomorrow 5pm to 10pm").' else: - booking_string = make_booking_string(start_time, end_time) + booking_string = make_booking_string(start_datetime, end_time) response = f'Okay, I will reach out to the sitters about sitting on {booking_string}.' if response is None: @@ -165,8 +165,8 @@ def accept_or_decline(sitter_name: str, body: str) -> str: return f'Okay, no problem, {sitter_name.title()}! Next time.' -def make_booking_string(start_time: datetime.datetime, end_time: datetime.time) -> str: - start_time_and_date_string = start_time.strftime('%-m/%-d from %-I:%M%p') +def make_booking_string(start_datetime: datetime.datetime, end_time: datetime.time) -> str: + start_time_and_date_string = start_datetime.strftime('%-m/%-d from %-I:%M%p') end_time_string = end_time.strftime('%-I:%M%p') return f'{start_time_and_date_string} to {end_time_string}' @@ -236,12 +236,12 @@ def book_forever(): def offer_booking(sitter_dict: dict, booking_string: str) -> None: message = f'{sitter_dict["name"].title()}, are you available to babysit on {booking_string}?' twilio_client.api.account.messages.create(to=sitter_dict['num'], - from_=BOOKER_NUM, + from_=BOT_NUM, body=message) def update_client(string: str) -> None: - twilio_client.api.account.messages.create(to=MY_CELL, from_=BOOKER_NUM, body=string) + twilio_client.api.account.messages.create(to=MY_CELL, from_=BOT_NUM, body=string) def has_phone_num(string): @@ -249,19 +249,19 @@ def has_phone_num(string): def request_booking(body: str) -> Tuple[datetime.datetime, datetime.time]: - session_start, session_end = parse_booking_request(body) + session_start_datetime, session_end_time = parse_booking_request(body) global bookings bookings = load_from_pickle('bookings') - bookings[(session_start, session_end)] = {'offered': dict()} + bookings[(session_start_datetime, session_end_time)] = {'offered': dict()} persist_bookings() - return session_start, session_end + return session_start_datetime, session_end_time def parse_booking_request(body: str) -> Tuple[datetime.datetime, datetime.time]: start_string, end_string = body.split(' to ') - session_start = cal.parseDT(start_string)[0] + session_start_datetime = cal.parseDT(start_string)[0] session_end_time = datetime.time(cal.parse(end_string)[0].tm_hour) - return session_start, session_end_time + return session_start_datetime, session_end_time def add_sitter(body: str) -> Tuple[str, str]: @@ -306,5 +306,5 @@ if __name__ == '__main__': 'I\'ll try to book one of our sitters!') p = Process(target=book_forever) p.start() - app.run(debug=True, port=8000) + app.run(debug=True, port=8000, use_reloader=False) p.join() diff --git a/sitter_bot_book_forever_simple.py b/sitter_bot_book_forever_simple.py new file mode 100644 index 0000000..07887d3 --- /dev/null +++ b/sitter_bot_book_forever_simple.py @@ -0,0 +1,263 @@ +import datetime +from multiprocessing import Process +import os +from pprint import pprint +import time +from typing import Tuple, Dict + +import parsedatetime as pdt +import pickle + +from flask import request, Flask +from twilio.rest import Client as TwilioClient +from twilio.twiml.messaging_response import MessagingResponse + +twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN')) + +MY_CELL = os.getenv('MY_CELL') +BOT_NUM = os.getenv('MY_TWILIO_NUM') +COUNTRY_CODE = f'+{os.getenv("TWILIO_COUNTRY_CODE")}' +TIMEOUT_MINUTES = 120 + +help_add = 'You can add a sitter by giving me their first name and 10-digit phone number' +help_text = help_add + ', or book a sitter by ' \ + 'specifying a date and time. You can also remove a sitter from the list ' \ + 'with "delete" or "remove" and then their first name.' + +app = Flask(__name__) +app.config.from_object(__name__) + +cal = pdt.Calendar() + + +class TheresAlreadyAnActiveBooking(Exception): + pass + + +def load_from_pickle(var_name: str) -> dict: + payload = {} + if os.path.exists(f'{var_name}.p'): + payload = pickle.load(open(f'{var_name}.p', 'rb')) + return payload + + +sitters, bookings = load_from_pickle('sitters'), load_from_pickle('bookings') +sitters_num_name_lookup = {v['num']: k for k, v in sitters.items()} + + +@app.route('/bot', methods=['POST']) +def bot() -> str: + from_ = request.values.get('From') + body = request.values.get('Body').lower() + + resp = MessagingResponse() + response = '' + + if from_ == MY_CELL: + + if has_phone_num(body): + + try: + sitter_name, sitter_num = add_sitter(body) + except (AssertionError, ValueError): + response = 'Sorry, did you mean to add a sitter? Please try again.' + else: + response = f'Okay, I added {sitter_name.title()} to sitters, with phone # {sitter_num}. ' + + elif any(remove_word in body for remove_word in ['remove', 'delete']): + + try: + sitter_name = remove_sitter(body) + except KeyError: + response = 'No such sitter. Please write "delete [sitter\'s first name]."' + else: + response = f'Okay, I removed {sitter_name.title()} from the sitters.' + + else: + try: + start_datetime, end_time = request_booking(body) + except ValueError: + response = 'Please specify an end time (e.g. "tomorrow 5pm to 10pm").' + except TheresAlreadyAnActiveBooking: + response = 'Please wait until the current booking is either booked or expires.' + else: + booking_string = make_booking_string(start_datetime, end_time) + response = f'Okay, I will reach out to the sitters about sitting on {booking_string}.' + + if response is None: + response = 'I wasn\'t sure what to do with your input. ' + help_text + + else: + + sitter_name = sitters_num_name_lookup.get(from_) + if sitter_name is not None: + response = accept_or_decline(sitter_name, body) + + resp.message(response) + + return str(resp) + + +def accept_or_decline(sitter_name: str, body: str) -> str: + body = body.strip() + + global bookings + global sitters + + bookings, sitters = load_from_pickle('bookings'), load_from_pickle('sitters') + sitter = sitters[sitter_name] + + sitter_offers = [k for k, v in bookings.items() + if sitter_name in v['offered'] + if v['offered'][sitter_name] not in ['yes', 'no']] + + if len(sitter_offers) == 0: + update_client(f'there\'s more than one booking on offer, so I\'m confused!') + return f'Sorry, {sitter_name.title()}, there are no pending gigs.' + + elif len(sitter_offers) > 1: + return f'I\'m not sure which offer you\'re responding to!' + + offer = sitter_offers[0] + booking = bookings[offer] + + if body not in ['yes', 'no', 'n', 'y']: + return f'Hm, I\'m not sure what you meant, {sitter_name.title()}. Please write "yes" or "no".' + + if body in ['yes', 'y']: + + booking_string = make_booking_string(*offer) + + if any(booking['offered'][sitter_] == 'yes' + for sitter_ in booking['offered'].keys()): + return f'Sorry, {sitter_name.title()}, it looks like {booking_string} is already booked.' + + booking['offered'][sitter_name] = 'yes' + persist_bookings() + update_client(f'{sitter_name.title()} agreed to babysit on {booking_string}!') + return f'Awesome, {sitter_name.title()}! See you on {booking_string}.' + + booking['offered'][sitter_name] = 'no' + persist_bookings() + return f'Okay, no problem, {sitter_name.title()}! Next time.' + + +def make_booking_string(start_datetime: datetime.datetime, end_time: datetime.time) -> str: + start_time_and_date_string = start_datetime.strftime('%-m/%-d from %-I:%M%p') + end_time_string = end_time.strftime('%-I:%M%p') + return f'{start_time_and_date_string} to {end_time_string}' + + +def book_forever(): + while True: + + sitters_, bookings_ = load_from_pickle('sitters'), load_from_pickle('bookings') + + if sitters_ and bookings_: + + for booking_start_and_end, offered_dict in bookings_.items(): + + booking = bookings_[booking_start_and_end] + offers = booking['offered'] + + if any(v == 'yes' for k, v in offers.items()): + continue + + if len(sitters_) == len(offers): + continue + + booking_string = make_booking_string(*booking_start_and_end) + + for sitter_name, sitter_dict in sitters_.items(): + if sitter_name not in offers: + offer_booking(sitter_dict, booking_string) + offers[sitter_name] = datetime.datetime.now() + update_client( + f'Okay, I offered {booking_string} to {sitter_name.title()}.') + + pprint(bookings_) + + persist_bookings(bookings_) + + # time.sleep(60) + time.sleep(5) + + +def offer_booking(sitter_dict: dict, booking_string: str) -> None: + message = f'{sitter_dict["name"].title()}, are you available to babysit on {booking_string}?' + twilio_client.api.account.messages.create(to=sitter_dict['num'], + from_=BOT_NUM, + body=message) + +def update_client(string: str) -> None: + twilio_client.api.account.messages.create(to=MY_CELL, from_=BOT_NUM, body=string) + + +def has_phone_num(string): + return len([char for char in string if char.isnumeric()]) == 10 + + +def request_booking(body: str) -> Tuple[datetime.datetime, datetime.time]: + session_start_datetime, session_end_time = parse_booking_request(body) + global bookings + bookings = load_from_pickle('bookings') + if bookings: + raise TheresAlreadyAnActiveBooking + bookings[(session_start_datetime, session_end_time)] = {'offered': dict()} + persist_bookings() + return session_start_datetime, session_end_time + + +def parse_booking_request(body: str) -> Tuple[datetime.datetime, datetime.time]: + start_string, end_string = body.split(' to ') + session_start_datetime = cal.parseDT(start_string)[0] + session_end_time = datetime.time(cal.parse(end_string)[0].tm_hour) + return session_start_datetime, session_end_time + + +def add_sitter(body: str) -> Tuple[str, str]: + name, *num_parts = body.split(' ') + + num_only = ''.join(char + for num in num_parts + for char in num if char.isnumeric()) + + lowercase_name = name.lower() + + assert len(num_only) == 10 + + phone_number = f'{COUNTRY_CODE}{num_only}' + sitters[lowercase_name] = {'num': phone_number, + 'name': lowercase_name} + + persist_sitters() + return name, phone_number + + +def remove_sitter(body: str) -> str: + sitter_first_name = body.split(' ')[1] + sitter = sitters.get(sitter_first_name) + if sitter is None: + raise KeyError + del sitters[sitter_first_name] + persist_sitters() + return sitter_first_name + + +def persist_sitters(): + pickle.dump(sitters, open('sitters.p', 'wb')) + + +def persist_bookings(bookings_: dict = None): + pickle.dump(bookings_ if bookings_ is not None else bookings, open('bookings.p', 'wb')) + + +if __name__ == '__main__': + update_client('Hi, this is Babysitter Bot, on the job! Send me a date with time range and ' + 'I\'ll try to book one of our sitters!') + if not sitters: + update_client('Please add at least one babysitter.') + p = Process(target=book_forever) + p.start() + app.run(debug=True, port=8000, use_reloader=False) + p.join() diff --git a/sitterbot_starter.py b/sitterbot_starter.py index a057cd0..34fac68 100644 --- a/sitterbot_starter.py +++ b/sitterbot_starter.py @@ -4,19 +4,19 @@ from flask import Flask, request from twilio.rest import Client as TwilioClient from twilio.twiml.messaging_response import MessagingResponse -twilio_client = TwilioClient(os.getenv('TWILIO_SID'), os.getenv('TWILIO_TOKEN')) +twilio_client = TwilioClient(os.getenv("TWILIO_SID"), os.getenv("TWILIO_TOKEN")) app = Flask(__name__) app.config.from_object(__name__) -@app.route('/bot', methods=['POST']) + +@app.route("/bot", methods=["POST"]) def bot(): - from_ = request.values.get('From') - body = request.values.get('Body').lower() + body = request.values.get("Body").lower() resp = MessagingResponse() - resp.message(f'ok, you said {body}.') + resp.message(f"ok, you said {body}.") return str(resp) -if __name__ == '__main__': - app.run(debug=True, port=8000) +if __name__ == "__main__": + app.run(debug=True, port=8000)