Skip to content

Commit 6ecaea5

Browse files
authored
Prod Release v0.1 Merge pull request Jackiebibili#13 from Jackiebibili/develop
Prod Release v0.1
2 parents 85b36ba + 7df0e21 commit 6ecaea5

27 files changed

+734
-60
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
/**/js/node_modules
33
/**/js/antibot-simulation.js
44
/**/tmp
5+
/**/.DS_Store
56
.vscode
67
docker-compose.yml
78
entrypoint.sh

apps/pushnotification/constants.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from ...secret import MAILER_PW
2+
3+
port = 465 # For starttls
4+
smtp_server = "smtp.gmail.com"
5+
sender_email = "[email protected]"
6+
subject = "Message from Ticketmaster Ticket Tracker"
7+
app_password = MAILER_PW
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from datetime import datetime
2+
from ..ticketscraping.models.pick import Pick
3+
4+
def price_formatter(price):
5+
price_str = ''
6+
if type(price) is float or int:
7+
price_str = "$" + "{:.2f}".format(price)
8+
elif type(price) is str:
9+
price_str = price
10+
return price_str
11+
12+
def decimal_to_percent(num: float):
13+
return "{:.2f}".format(num*100) + "%"
14+
15+
def format_date(date: datetime):
16+
return date.isoformat()
17+
18+
def default_formatter(s: str):
19+
return s
20+
21+
def format_seat_columns(cols):
22+
if type(cols) is str:
23+
return cols
24+
elif type(cols) is list:
25+
return "(" + ",".join(cols) + ")"
26+
return '-'
27+
28+
def apply_format(s, formatter)->str:
29+
return formatter(s)
30+
31+
def apply(values: list, formatters: list, delimiter="\t"):
32+
if len(values) != len(formatters):
33+
raise Exception('values and formatters must have the same length')
34+
s = []
35+
for i in range(len(values)):
36+
s.append(apply_format(values[i], formatters[i]))
37+
return delimiter.join(s)
38+
39+
def format_full_seat(seat: dict, delimiter="\t"):
40+
price = seat.get("price", "n/a")
41+
section = seat.get("section", "n/a")
42+
row = seat.get("row", "n/a")
43+
seat_columns = seat.get("seat_columns", "n/a")
44+
last_modified = seat.get("last_modified", "n/a")
45+
return apply(
46+
[price, section, row, seat_columns, last_modified],
47+
[price_formatter, default_formatter, default_formatter,
48+
format_seat_columns, format_date],
49+
delimiter)
50+
51+
def format_price_only_seat(seat: dict, delimiter="\t"):
52+
price = seat.get("price", "n/a")
53+
last_modified = seat.get("last_modified", "n/a")
54+
return apply([price, last_modified], [price_formatter, format_date], delimiter)
55+
56+
def format_seat(seat: dict, price_only=False, delimiter="\t"):
57+
if price_only:
58+
return format_price_only_seat(seat, delimiter)
59+
else:
60+
return format_full_seat(seat, delimiter)
61+
62+
def format_seats(seats: list, price_only=False, delimiter="\t"):
63+
return "\n".join([format_seat(seat, price_only, delimiter) for seat in seats])
64+
65+
66+
def format_entire_mail(pick: Pick, target_price: int, percentile: float, rank: int, num_total: int, top_history_seats: list, same_seats: list):
67+
"""
68+
structure of message:
69+
1. greetings
70+
2. attributes of new seats
71+
3. top 3 comparable history seats
72+
4. exact same seats if possible
73+
5. signature
74+
"""
75+
p1 = (
76+
f"Hi!"
77+
)
78+
p2 = (
79+
f"Congratulations! Ticket tracker reminds you that your ticket subscription request with target price {price_formatter(target_price)} "
80+
f"found better budget seats (price, section, row, seats) at ({format_full_seat(vars(pick), delimiter=', ')}). "
81+
f"{decimal_to_percent(percentile)} of all comparable seats in the history are better than the newly found seats, that is, "
82+
f"they rank no.{rank} out of {num_total} comparable seats in the history."
83+
)
84+
p3 = (
85+
f"You can compare to history seats that are better than the newly found seats:"
86+
f"{chr(10)}"
87+
f"{format_seats(top_history_seats, price_only=False)}"
88+
) if len(top_history_seats) > 0 else ""
89+
p4 = (
90+
f"The newly found seats have history prices:"
91+
f"{chr(10)}"
92+
f"{format_seats(same_seats, price_only=True)}"
93+
) if len(same_seats) > 0 else ""
94+
p5 = (
95+
f"Bests,"
96+
f"{chr(10)}"
97+
f"Ticketmaster Ticket Tracker"
98+
)
99+
paras = list(filter(lambda p: len(p) > 0, [p1, p2, p3, p4, p5]))
100+
return "\n\n".join(paras)

apps/pushnotification/smtp.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from smtplib import SMTP_SSL
2+
from ssl import create_default_context
3+
from email.message import EmailMessage
4+
from . import constants
5+
6+
7+
def init_server():
8+
context = create_default_context()
9+
server = SMTP_SSL(constants.smtp_server, constants.port, context=context)
10+
return server
11+
12+
13+
def server_login(server: SMTP_SSL, password: str):
14+
return server.login(constants.sender_email, password)
15+
16+
17+
def server_send_email(server: SMTP_SSL, receiver_emails: list[str], message: str):
18+
em = EmailMessage()
19+
em['From'] = constants.sender_email
20+
em['To'] = receiver_emails
21+
em['subject'] = constants.subject
22+
23+
em.set_content(message)
24+
return server.sendmail(constants.sender_email, receiver_emails, em.as_string())
25+
26+
27+
def send_email(receiver_emails: list[str], messages: list[str]):
28+
if len(messages) == 0:
29+
return
30+
# print(messages[0])
31+
try:
32+
err = server_send_email(server, receiver_emails, messages[0])
33+
if err is not None:
34+
raise Exception('could not send email to the receiver')
35+
except Exception as ex:
36+
print(ex)
37+
38+
39+
server = init_server()
40+
41+
42+
def auth_server():
43+
global server
44+
server_login(server, constants.app_password)

apps/startup/apps.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,39 @@
11
from django.apps import AppConfig
2-
from ..ticketscraping.scraping import start
32
from datetime import datetime
43
from threading import Thread
4+
from multiprocessing import Process
5+
6+
7+
def run_prepare():
8+
# import module inside the child process to prevent execution in the parent process
9+
print(
10+
f"ticket scraping service started at {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
11+
12+
# start sender socket
13+
from apps.ticketscraping.schedulers.async_tasks_scheduler import async_tasks_scheduler
14+
conn_thread = Thread(target=async_tasks_scheduler.connect)
15+
conn_thread.start()
16+
# wait for async tasks handler to connect
17+
conn_thread.join()
18+
19+
# start itself (scraping)
20+
from apps.ticketscraping.scraping import start
21+
start()
22+
23+
24+
def run():
25+
# starter
26+
p = Process(target=run_prepare, daemon=True)
27+
p.start()
28+
# start receiver socket
29+
from apps.ticketscraping.connection.asyn_tasks_receiver import run
30+
conn_process = Process(target=run)
31+
conn_process.start()
532

633

734
class MyAppConfig(AppConfig):
835
name = "apps.startup"
936
verbose_name = "start tmtracker"
1037

1138
def ready(self):
12-
print(
13-
f"server started at {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}")
14-
Thread(target=start).start()
39+
run()

apps/storage/base.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ def find_one_and_update__(coll: collection.Collection, filter: dict, update=dict
3030
def find_one_and_delete__(coll: collection.Collection, filter: dict):
3131
return coll.find_one_and_delete(filter)
3232

33-
def find_one__(coll: collection.Collection, filter: dict, projection):
34-
return coll.find_one(filter=filter, projection=projection)
33+
def find_one__(coll: collection.Collection, filter: dict, projection, **kwargs):
34+
return coll.find_one(filter=filter, projection=projection, **kwargs)
3535

3636
def find_many__(coll: collection.Collection, filter: dict, projection, **kwargs):
3737
return coll.find(filter=filter, projection=projection, **kwargs)
3838

39+
def count_docs__(coll: collection.Collection, filter: dict):
40+
return coll.count_documents(filter=filter)
41+
42+
def estimated_count_docs__(coll: collection.Collection):
43+
return coll.estimated_document_count()
44+
3945
def watch__(coll: collection.Collection, **kwargs):
4046
return coll.watch(**kwargs)
4147

apps/storage/query.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from .storage import *
2+
import pymongo
3+
4+
# find the max value in a collection
5+
def find_max(collection_name, filter: dict, sort_key: str, db_name="tickets"):
6+
sort_seq = [(sort_key, pymongo.DESCENDING)]
7+
return find_one(collection_name, filter, db_name=db_name, sort=sort_seq)
8+
9+
# find the min value in a collection
10+
def find_min(collection_name, filter: dict, sort_key: str, db_name="tickets"):
11+
sort_seq = [(sort_key, pymongo.ASCENDING)]
12+
return find_one(collection_name, filter, db_name=db_name, sort=sort_seq)
13+
14+
def find_many_ascending_order(collection_name, filter: dict, sort_key: str, db_name="tickets"):
15+
sort_seq = [(sort_key, pymongo.ASCENDING)]
16+
return find_many(collection_name, filter, db_name=db_name, sort=sort_seq)

apps/storage/storage.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,29 @@ def find_one_and_delete(collection_name, filter: dict, db_name="tickets"):
5151
return find_one_and_delete__(coll, filter)
5252

5353
# find one
54-
def find_one(collection_name, filter: dict, projection=None, db_name="tickets"):
54+
def find_one(collection_name, filter: dict, projection=None, db_name="tickets", **kwargs):
5555
db = get_db_handle(db_name)
5656
coll = db[collection_name]
57-
return find_one__(coll, filter, projection)
57+
return find_one__(coll, filter, projection, **kwargs)
5858

5959
# find many
6060
def find_many(collection_name, filter: dict, projection=None, db_name="tickets", **kwargs):
6161
db = get_db_handle(db_name)
6262
coll = db[collection_name]
6363
return list(find_many__(coll, filter, projection, **kwargs))
6464

65+
# count with filter
66+
def count_docs(collection_name, filter: dict, db_name="tickets"):
67+
db = get_db_handle(db_name)
68+
coll = db[collection_name]
69+
return count_docs__(coll, filter)
70+
71+
# count all docs in a collection
72+
def estimated_count_docs(collection_name, db_name="tickets"):
73+
db = get_db_handle(db_name)
74+
coll = db[collection_name]
75+
return estimated_count_docs__(coll)
76+
6577
# watch changes
6678
def watch(collection_name, db_name="tickets", **kwargs):
6779
db = get_db_handle(db_name)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# start sockets
2+
from threading import Thread
3+
from multiprocessing import Process
4+
from apps.ticketscraping.connection.receiver_process import ReceiverProcess
5+
from apps.ticketscraping.constants import SERVICE_LOCALHOST, ASYNC_TASKS_RECEIVER_PORT
6+
7+
8+
def run_prepare():
9+
# start receiver socket
10+
from apps.ticketscraping.connection.mail_receiver import run
11+
conn_process = Process(target=run, daemon=True)
12+
conn_process.start()
13+
14+
# start sender socket
15+
from apps.ticketscraping.schedulers.mail_scheduler import mail_scheduler
16+
conn_thread = Thread(target=mail_scheduler.connect)
17+
conn_thread.start()
18+
# wait for mailer to connect
19+
conn_thread.join()
20+
21+
# start itself
22+
from apps.ticketscraping.tasks.asynchronous import run_async_tasks
23+
receiver = ReceiverProcess(run_async_tasks, SERVICE_LOCALHOST, ASYNC_TASKS_RECEIVER_PORT)
24+
receiver.connect()
25+
receiver.serve_forever()
26+
27+
28+
def run():
29+
# starter
30+
p = Process(target=run_prepare)
31+
p.start()
32+
33+
34+
if __name__ == '__main__':
35+
run()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from apps.ticketscraping.connection.receiver_process import ReceiverProcess
2+
from apps.pushnotification.smtp import send_email, auth_server
3+
from apps.ticketscraping.constants import SERVICE_LOCALHOST, MAIL_RECEIVER_PORT
4+
5+
def run():
6+
# start itself
7+
auth_server()
8+
receiver = ReceiverProcess(send_email, SERVICE_LOCALHOST, MAIL_RECEIVER_PORT)
9+
receiver.connect()
10+
receiver.serve_forever()
11+
12+
if __name__ == '__main__':
13+
run()

0 commit comments

Comments
 (0)