diff --git a/account.py b/account.py index 6316ec1..33b8c53 100644 --- a/account.py +++ b/account.py @@ -196,6 +196,40 @@ def updateNotifications(token, notifications): return True +def updateDomainNotifications(token, domain, notifications): + # Get userID from userToken + if (token.count('$') != 1): + return False + token = token.split('$') + userID = token[0] + token = token[1] + + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,)) + user = cursor.fetchall() + cursor.close() + conn.close() + + print(domain) + print(notifications) + + # Read json token from first user + user = user[0][0] + user = json.loads(user) + for userDomain in user: + if (userDomain['name'] == domain): + userDomain['notifications'] = notifications + + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(user), userID,)) + conn.commit() + cursor.close() + conn.close() + + return True + def updateNotificationProvider(token, provider,account): # Get userID from userToken if (token.count('$') != 1): diff --git a/domains.py b/domains.py new file mode 100644 index 0000000..f0b0baf --- /dev/null +++ b/domains.py @@ -0,0 +1,253 @@ +import mysql.connector +from mysql.connector import Error +import dotenv +import os +import json +import time +import smtplib +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +import requests +import re + +dotenv.load_dotenv() + +db_config = { + 'host': os.getenv('DB_HOST'), + 'database': os.getenv('DB_NAME'), + 'user': os.getenv('DB_USER'), + 'password': os.getenv('DB_PASSWORD') +} + +def addDomain(userID:int, domain:str): + # Get user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,)) + user = cursor.fetchall() + cursor.close() + conn.close() + + # Read json domains from first user + user = user[0][0] + userDomains = json.loads(user) + + # Check if domain is already in use + for userDomain in userDomains: + if (userDomain['name'] == domain): + return False + + # Add domain to user + userDomains.append({ + "name": domain, + "status": "pending" + }) + + # Update user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID)) + conn.commit() + cursor.close() + conn.close() + return True + + +def verifyDomain(domain:str): + # Remove any trailing slashes + domain = domain.rstrip('/') + # Remove any protocol + domain = domain.replace('https://', '') + domain = domain.replace('http://', '') + domain = domain.lower() + # Verify domain contains only valid characters + if (re.match("^[a-z0-9.-]*$", domain) == None): + return False + return domain + +def deleteDomain(userID:int, domain:str): + # Get user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("SELECT domains FROM users WHERE id = %s", (userID,)) + user = cursor.fetchall() + cursor.close() + conn.close() + + # Read json domains from first user + user = user[0][0] + userDomains = json.loads(user) + + # Check if domain is already in use + for userDomain in userDomains: + if (userDomain['name'] == domain): + userDomains.remove(userDomain) + break + + # Update user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID)) + conn.commit() + cursor.close() + conn.close() + return True + +def syncDomains(): + # Verify connection to HSD node + try: + r = requests.get('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP') + + ':' + os.getenv('HSD_PORT')) + if (r.status_code != 200): + return "HSD node is not responding" + # Check to make sure the node is synced + data = r.json() + if (data['chain']['progress'] < 0.999): + return "HSD node is not synced" + + except: + return False + + # Get all users + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("SELECT id, domains FROM users") + users = cursor.fetchall() + cursor.close() + conn.close() + + # Loop through users + for user in users: + userID = user[0] + userDomains = json.loads(user[1]) + # Loop through user domains + for userDomain in userDomains: + # Check if domain is pending + if (userDomain['status'] == 'pending'): + try: + print('Importing domain: ' + userDomain['name']) + r = requests.post('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP') + + ':' + os.getenv('HSD_WALLET_PORT'), json={'method': 'importname', 'params': [userDomain['name']]}) + + if (r.status_code == 200): + # Update domain status + userDomain['status'] = 'added' + + # Set some defaults + userDomain['transfering'] = 0 + userDomain['next'] = 'none' + userDomain['when'] = 0 + + # Update user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID)) + conn.commit() + cursor.close() + conn.close() + + except: + pass + + # Get domain info + r = requests.post('http://x:' + os.getenv('HSD_API_KEY') + '@' + os.getenv('HSD_IP') + + ':' + os.getenv('HSD_PORT'), json={'method': 'getnameinfo', 'params': [userDomain['name']]}) + print(json.dumps(r.json(), indent=4)) + if (r.status_code == 200): + data = r.json() + # Check if domain is registered + info = data['result']['info'] + if (userDomain['transfering'] != info['transfer']): + # Update domain status + alert('transfer', userDomain['name'], userID) + userDomain['transfering'] = info['transfer'] + + if 'stats' in info: + if 'blocksUntilExpire' in info['stats']: + # Update domain status + userDomain['next'] = 'expires' + + previous = userDomain['when'] + userDomain['when'] = info['stats']['blocksUntilExpire'] + + if (crossTimeAlert(userDomain['next'], previous, userDomain['when'])): + alert('expire', userDomain['name'], userID) + + elif 'blocksUntilBidding' in info['stats']: + # Update domain status + userDomain['next'] = 'opens for bidding' + userDomain['when'] = info['stats']['blocksUntilBidding'] + + # Update user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("UPDATE users SET domains = %s WHERE id = %s", (json.dumps(userDomains), userID)) + conn.commit() + cursor.close() + conn.close() + + + + else: + return "Failed to get info about domain: " + userDomain['name'] + "
" + str(r.text) + return "Finished syncing domains" + +def alert(event,domain,user): + # TODO this later + pass + +def crossTimeAlert(event,was, now): + # Check for each of these times to see if we should alert + month = 4320 + week = 1008 + + # If the time crossed the month mark + if (was > month and now <= month): + return True + # If the time crossed the week mark + if (was > week and now <= week): + return True + + return False + + + +def getCachedDomainInfo(domain): + # Get domain info from user domains + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor() + cursor.execute("SELECT domains FROM users") + users = cursor.fetchall() + cursor.close() + conn.close() + + # Loop through users + for user in users: + userDomains = json.loads(user[0]) + # Loop through user domains + for userDomain in userDomains: + if (userDomain['name'] == domain): + return userDomain + return False + + + +def blocksToTime(blocks): + # Get minutes + minutes = blocks * 10 + years, minutes = divmod(minutes, 525600) + days, minutes = divmod(minutes, 1440) + hours, minutes = divmod(minutes, 60) + + # Build the string + time_string = "" + if years: + time_string += f"{years} {'year' if years == 1 else 'years'}, " + if days: + time_string += f"{days} {'day' if days == 1 else 'days'}, " + if hours and not years: + time_string += f"{hours} {'hour' if hours == 1 else 'hours'}, " + if minutes and not years and not days: + time_string += f"{minutes} {'min' if minutes == 1 else 'mins'}" + + return time_string.rstrip(', ') diff --git a/main.py b/main.py index 409b58d..49dca9f 100644 --- a/main.py +++ b/main.py @@ -9,6 +9,7 @@ import time import json import account import threading +import domains dotenv.load_dotenv() @@ -107,7 +108,20 @@ def dashboard(): if (account.verifyUser(request.cookies.get('user_token'))): # Get user data user = account.getUser(request.cookies.get('user_token')) - domains = user['domains'] + error = "" + if (request.args.get('error')): + error = request.args.get('error') + success = "" + if (request.args.get('success')): + success = request.args.get('success') + + + # For each domain in domains get only the name + domains = [] + for domain in user['domains']: + domains.append(domain['name']) + + notifications = user['notifications'] email = user['email'] + ' Test' @@ -140,7 +154,8 @@ def dashboard(): return render_template('dashboard.html', domains=domains, notifications=notifications, email=email, discord=discord, telegram=telegram, - expiry_week=expiry_week,expiry_month=expiry_month) + expiry_week=expiry_week,expiry_month=expiry_month, + error=error,success=success,admin=user['admin']) return redirect('/login') @@ -200,30 +215,161 @@ def notification_options(): if (account.verifyUser(request.cookies.get('user_token'))): # Get user data user = account.getUser(request.cookies.get('user_token')) - notifications = user['notifications'] expiry_week = { - "email": False, - "discord": False, - "telegram": False + "email": False, + "discord": False, + "telegram": False } expiry_month = { "email": False, "discord": False, "telegram": False } - for key in request.form: - if (key.endswith('_week')): - key = key[:-5] - expiry_week[key] = True - elif (key.endswith('_month')): - key = key[:-6] - expiry_month[key] = True - - notifications['expiry_week'] = expiry_week - notifications['expiry_month'] = expiry_month - account.updateNotifications(request.cookies.get('user_token'), notifications) - return redirect('/dashboard') + if not request.form.get('domain'): + notifications = user['notifications'] + for key in request.form: + if (key.endswith('_week')): + key = key[:-5] + expiry_week[key] = True + elif (key.endswith('_month')): + key = key[:-6] + expiry_month[key] = True + + notifications['expiry_week'] = expiry_week + notifications['expiry_month'] = expiry_month + account.updateNotifications(request.cookies.get('user_token'), notifications) + return redirect('/dashboard') + else: + notifications = { + "expiry_week": { + "email": False, + "discord": False, + "telegram": False + }, + "expiry_month": { + "email": False, + "discord": False, + "telegram": False + } + } + domainList = user['domains'] + domain = request.form.get('domain').lower() + + + for key in request.form: + if (key.endswith('_week')): + key = key[:-5] + notifications['expiry_week'][key] = True + elif (key.endswith('_month')): + key = key[:-6] + notifications['expiry_month'][key] = True + + account.updateDomainNotifications(request.cookies.get('user_token'), + domain,notifications) + return redirect('/' + domain + '/info') + + +#region domains +@app.route('/new-domain', methods=['POST']) +def new_domain(): + # Check if user is logged in + if 'user_token' in request.cookies: + if (account.verifyUser(request.cookies.get('user_token'))): + # Get user data + user = account.getUser(request.cookies.get('user_token')) + domain = request.form.get('domain') + # Verify domain + domain = domains.verifyDomain(domain) + if (domain): + # Add domain to user + if (domains.addDomain(user['id'], domain)): + return redirect('/dashboard?success=Domain added') + else: + return redirect('/dashboard?error=Unable to add domain') + else: + return redirect('/dashboard?error=Invalid domain') + + return redirect('/login') + +@app.route('//delete') +def delete_domain(domain): + # Check if user is logged in + if 'user_token' in request.cookies: + if (account.verifyUser(request.cookies.get('user_token'))): + # Get user data + user = account.getUser(request.cookies.get('user_token')) + # Delete domain from user + if (domains.deleteDomain(user['id'], domain)): + return redirect('/dashboard?success=Domain deleted') + else: + return redirect('/dashboard?error=Unable to delete domain') + + return redirect('/login') + +@app.route('/sync') +def sync_domains(): + # Check if user is logged in + if 'user_token' in request.cookies: + if (account.verifyUser(request.cookies.get('user_token'))): + # Get user data + user = account.getUser(request.cookies.get('user_token')) + if (user['admin'] == False): + return redirect('/dashboard?error=You are not an admin') + + # Sync domains + result =domains.syncDomains() + return redirect('/dashboard?error=' + result) + + return redirect('/login') + +@app.route('//info') +def domain(domain): + # Check if user is logged in + if 'user_token' in request.cookies: + if (account.verifyUser(request.cookies.get('user_token'))): + # Get domain info + domainInfo = domains.getCachedDomainInfo(domain) + print(domainInfo) + if (domainInfo): + if (domainInfo['status'] == 'pending'): + return redirect('/dashboard?error=Domain is pending
Please wait a few minutes') + + + next = domainInfo['next'] + when_blocks = domainInfo['when'] + when_time = domains.blocksToTime(when_blocks) + transfering = domainInfo['transfering'] == 1 + + expiry_week = { + "email": False, + "discord": False, + "telegram": False + } + expiry_month = { + "email": False, + "discord": False, + "telegram": False + } + if ('notifications' in domainInfo): + if ('expiry_week' in domainInfo['notifications']): + expiry_week = domainInfo['notifications']['expiry_week'] + + if ('expiry_month' in domainInfo['notifications']): + expiry_month = domainInfo['notifications']['expiry_month'] + + return render_template('info.html', domain=str(domain).capitalize(), + next=next,when_blocks=when_blocks,when_time=when_time, + transfering=transfering,expiry_week=expiry_week, + expiry_month=expiry_month) + else: + return render_template('info.html', domain=str(domain).capitalize()) + + return redirect('/login') + + + +#endregion @app.route('/assets/') def send_assets(path): diff --git a/templates/dashboard.html b/templates/dashboard.html index 8dbc547..ddd59f2 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -4,7 +4,7 @@ - Products - HNS Alert + Dashboard - HNS Alert @@ -12,25 +12,30 @@ - - - - - - - - - + + + + + + + + + - +
+

{{error | safe}}

+

{{success | safe}}

Dashboard

@@ -77,6 +82,29 @@
+
+
+
+
+

Domains

    +
  • +
    +
    +
  • + +{% for domain in domains %} +
  • + {{domain}}/
    + Info + Delete +
    +
  • + {% endfor %} +
+
+
+
+
@@ -99,7 +127,7 @@
-
HNS Alert
+
HNS Alert

A Woodburn/ project

@@ -109,8 +137,8 @@
- - + + \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 4481e43..a2b1096 100644 --- a/templates/index.html +++ b/templates/index.html @@ -20,22 +20,22 @@ "url": "https://alert.woodburn.au" } - - - - - - - - - + + + + + + + + + - +
@@ -48,9 +48,9 @@
-
-
-
+
+
+
@@ -164,7 +164,7 @@

Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.

-
+

John Smith

Erat netus

@@ -175,7 +175,7 @@

Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.

-
+

John Smith

Erat netus

@@ -186,7 +186,7 @@

Nisi sit justo faucibus nec ornare amet, tortor torquent. Blandit class dapibus, aliquet morbi.

-
+

John Smith

Erat netus

@@ -251,7 +251,7 @@
-
HNS Alert
+
HNS Alert

A Woodburn/ project

@@ -261,8 +261,8 @@
- - + + \ No newline at end of file diff --git a/templates/info.html b/templates/info.html new file mode 100644 index 0000000..fd4d3d9 --- /dev/null +++ b/templates/info.html @@ -0,0 +1,112 @@ + + + + + + + {{domain}}/ - HNS Alert + + + + + + + + + + + + + + + + + + + + + + +
+

{{domain}}/ Info

+
+
+
+
+
+
+
+
+
+

{{domain}}/ Status

+

{{domain}} {{next}} in {{when_blocks}} blocks (~{{when_time}})
{%if transfering %}
{{domain}} is currently transferring
{%endif%}

+
+
+
+
+
+

Notification options

+
These are per domain options. These options will override global settings once you save them
+
+
    +
  • Domain expiry 1 week notice{% for platform, enabled in expiry_week.items() %} +
    + + +
    +{% endfor %}
  • +
  • Domain expiry 1 month notice{% for platform, enabled in expiry_month.items() %} +
    + + +
    +{% endfor %}
  • +
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/templates/link.html b/templates/link.html index 11dd482..074248a 100644 --- a/templates/link.html +++ b/templates/link.html @@ -4,7 +4,7 @@ - Products - HNS Alert + Link Service - HNS Alert @@ -12,22 +12,22 @@ - - - - - - - - - + + + + + + + + + - +
@@ -83,7 +83,7 @@
-
HNS Alert
+
HNS Alert

A Woodburn/ project

@@ -93,8 +93,8 @@
- - + + \ No newline at end of file diff --git a/templates/login.html b/templates/login.html index 6ebc68b..66b9a59 100644 --- a/templates/login.html +++ b/templates/login.html @@ -12,22 +12,22 @@ - - - - - - - - - + + + + + + + + + - +
@@ -77,7 +77,7 @@
-
HNS Alert
+
HNS Alert

A Woodburn/ project

@@ -87,8 +87,8 @@
- - + + \ No newline at end of file diff --git a/templates/signup.html b/templates/signup.html index 69fbb33..63475d2 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -12,22 +12,22 @@ - - - - - - - - - + + + + + + + + + - +
@@ -49,7 +49,7 @@
-

Already have an account? Log in

+

Already have an account? Log in

@@ -77,7 +77,7 @@
-
HNS Alert
+
HNS Alert

A Woodburn/ project

@@ -87,8 +87,8 @@
- - + + \ No newline at end of file