- """
- Prerequisites:
- We need to make some changes to the service.
- I added a webrequest that is immediately made after restart to trigger @app.before_first_request
- Also the lockfile gets reset each time the server gets reset.
- File = /etc/init.d/apache2
- """
- from flask import Flask
- from flask import request
- from flask import render_template
- from flask import Markup
- from email.mime.text import MIMEText
- import smtplib
- import urllib.parse
- import re
- from time import sleep
- from bs4 import BeautifulSoup
- import urllib.request
- import threading
- import peewee
- from peewee import *
- """
- GLOBAL VARIABLES
- """
- #Option for logging DEV is for development, PROD is for stable
- DEVOPTION = "[DEV]"
- #Version variable
- VERSION = "0.8-Beta-DEV-STAGING"
- #Environment
- ENV = "digireminder-t"
- #SSL used?
- SSL = "http://"
- #Database
- DB = ""
- #Website title
- TITLE = "Digireminder-Staging"
- # Form that is shown, if digitec is available.
- FORM_ENABLED = Markup("""<form action="." method="POST">
- <div class="form-group">
- <label for="inputlg-alias">Produktlink</label>
- <input class="form-control input-lg" id="inputlg-alias" type="text" name="text" required>
- </div>
- <div class="form-group">
- <label for="inputlg-secret">Email</label>
- <input class="form-control input-lg" id="inputlg-secret" type="text" name="email" required>
- </div>
- <input type="submit" class="btn btn-primary btn-lg" name="my-form" value="Remind me!">
- </form>""")
- # Form that is shown, if digitec is not available.
- FORM_DISABLED = Markup("""
- <div class="card card-inverse card-danger text-center">
- <div class="card-block">
- <blockquote class="card-blockquote">
- <p>Digitec.ch ist nicht erreichbar. Versuche es später wieder.</p>
- </blockquote>
- </div>
- </div>
- <br/>
- <form action="." method="POST">
- <fieldset disabled>
- <div class="form-group">
- <label for="inputlg-alias">Produktlink</label>
- <input class="form-control input-lg" id="inputlg-alias" type="text" name="text" required>
- </div>
- <div class="form-group">
- <label for="inputlg-secret">Email</label>
- <input class="form-control input-lg" id="inputlg-secret" type="text" name="email" required>
- </div>
- <input type="submit" class="btn btn-primary btn-lg" name="my-form" value="Remind me!">
- </fieldset>
- </form>""")
- #This variable defines the db we want to write into
- db = MySQLDatabase(host='localhost', user='user',passwd='password', database=DB)
- """
- We use peewee so we don't have to make queries.
- This model makes coding a lot easier.
- """
- class Job(peewee.Model):
- link = peewee.CharField()
- email = peewee.CharField()
- class Meta:
- indexes = (
- (( 'link', 'email'), True),
- )
- database = db
- app = Flask(__name__)
- #This will be executed first.
- @app.before_first_request
- def activate_job():
- #v0.8 try to create table in db.
- try:
- db.get_conn()
- Job.create_table()
- db.close()
- except:
- pass
- #Define path of lock file.
- filename = "dir"
- """
- This def will run in the background to restart jobs that have been cancelled by a server event in background.
- It connects to the Database, collects each existent job and spawns a thread for each job using mail and link as params.
- We also pass a flag so no duplicate job gets created in the database.
- """
- def run_job():
- print(DEVOPTION +" ------Restarting old tasks...")
- db.get_conn()
- for job in Job:
- #Delay 5 seconds for stability.
- sleep(5)
- getAvailabilityThread = threading.Thread(target=timerThread, args=(job.email, job.link, False))
- getAvailabilityThread.start()
- db.close()
- """
- In activate_job, we open a pseudo lockfile. If it says locked then a thread is spawned to start old tasks.
- Also it writes another value to the file so the next request doesn't spawn another thread to start old tasks.
- """
- print(DEVOPTION +" ------Checking if thread has been executed...")
- with open(filename, "r+") as f:
- filedata = f.read().replace('\n', '')
- if filedata == "locked":
- print(DEVOPTION +" ------Writing to file...")
- thread = threading.Thread(target=run_job)
- thread.start()
- f.seek(0)
- f.write("unlocked")
- f.truncate()
- """
- This def just sends a mail using receiver and productlink as parameters.
- """
- def sendmail(receiver, productlink):
- server = smtplib.SMTP("purpl3.net", 587)
- server.ehlo()
- server.starttls()
- server.login("user", "pw")
- recipients = [receiver]
- sender = "no-reply@purpl3.net"
- subject = "Produkt bei digitec.ch ist nun verfügbar! | Digireminder " + VERSION
- body = '<html><body>'"Hallo, du erhälst dieses Mail, weil du auf https://digireminder.purpl3.net einen Reminder für ein Produkt erstellt hast.<br/>Das Produkt ist jetzt oder wieder an Lager: "+productlink+"<br/><br/>Mit freundlichen Grüssen<br/>Purpl3.net"'</body></html>'
- msg = MIMEText(body.encode('utf-8'), 'html', 'UTF-8')
- msg['Subject'] = subject
- msg['From'] = sender
- msg['Content-Type'] = "text/html; charset=utf-8"
- msg['To'] = ", ".join(recipients)
- try:
- server.sendmail("no-reply@purpl3.net", receiver, str(msg))
- except:
- return
- server.quit()
- """
- This function first connects to the db to create a job using receiver and productlink as params
- The param createNewJob just sets a flag if a new job need to be created. (This needs to be there so that old jobs dont create duplicate entries in the db)
- After creating the job it starts a webrequest to digitec.ch to check whether the provided product is available
- If it is available the job inside the database gets deleted and the mail is sent out to the requestor.
- """
- def getAvailability(receiver, productlink, createNewJob):
- #Define headers and request
- header = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'}
- req = urllib.request.Request(productlink, headers=header)
- #Create job in database
- if createNewJob == True and urllib.request.urlopen(req).getcode() == 200:
- print(DEVOPTION + " ------New request found!")
- db.get_conn()
- try:
- job = Job(link=productlink, email=receiver)
- job.save()
- except:
- print(DEVOPTION +" ------Error creating job in database!")
- db.close()
- #Make request and check if available
- try:
- with urllib.request.urlopen(req) as url:
- s = url.read()
- soup = BeautifulSoup(s,"html.parser")
- avail_div = soup.find("div", id="Availability")
- svg = avail_div.find("svg", class_="digicon digicon--extra digicon--green")
- #If available, delete job from database and send mail
- if svg != None:
- print(DEVOPTION +" ------Available!")
- #Clean up job from database
- db.get_conn()
- try:
- for job in Job:
- if job.link == productlink and job.email == receiver:
- job.delete_instance()
- except:
- print(DEVOPTION +" ------Error deleting job in database!")
- db.close()
- sendmail(receiver, productlink)
- return True
- else:
- return False
- except:
- print(DEVOPTION + " ------Website not found!")
- """
- This is the def that spawns the getAvailability def in the background with email and link as params
- It will check every 3 hours for a change on the website.
- """
- def timerThread(receiver, productlink, createNewJob):
- print(DEVOPTION +" ------Started new thread!")
- while getAvailability(receiver, productlink, createNewJob) == False:
- print(DEVOPTION +" ------Checking for availability...")
- sleep(10800)
- print(DEVOPTION +" ------Finished thread")
- """
- The default route. It loads the startpage. Bevore it makes a chekc to see if digitec is available and loads the corresponding html form.
- If digitec is not available then the disabled form will be loaded. Otherwise the normal form loads.
- """
- @app.route('/')
- def my_form():
- header = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'}
- req = urllib.request.Request("https://www.digitec.ch", headers=header)
- try:
- if urllib.request.urlopen(req).getcode() != 200:
- return render_template("my-form.html", version = VERSION, title = TITLE, form = FORM_DISABLED)
- else:
- return render_template("my-form.html", version = VERSION, title = TITLE, form = FORM_ENABLED)
- except:
- print(DEVOPTION + " ------Digitec is not available right now.")
- return render_template("my-form.html", version = VERSION, title = TITLE, form = FORM_DISABLED)
- #return render_template("my-form.html", version = VERSION, title = TITLE, form = FORM)
- """
- The post method for the default route.
- This reads the arguments provided by the form the user sends and puts them into variables.
- Then it performs some checks:
- It checks first if the job is already in the database - if not it returns JOB EXISTS
- If no job exists, check if the link is from digitec.ch - if it is then spawn timerThread with email and link and show the user some feedback by loading success.html
- """
- @app.route('/', methods=['POST'])
- def my_form_post():
- #v0.8 strip whitespace from string to prevent multiple entries for same item
- email = request.form['email'].strip().replace(" ", "")
- text = request.form['text'].strip().replace(" ", "")
- #Check if exists in DB
- db.get_conn()
- try:
- for job in Job.filter(email=email):
- if job.link == text and job.email == email:
- return render_template("duplicate.html", title = TITLE)
- except:
- print(DEVOPTION +" ------Error! Something went wrong in the POST method.")
- db.close()
- if urllib.parse.urlparse(text).hostname == "www.digitec.ch" and re.search("/product/", text) != None and re.search("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email) != None:
- getAvailabilityThread = threading.Thread(target=timerThread, args=(email, text, True))
- getAvailabilityThread.start()
- return render_template("success.html", title = TITLE)
- else:
- print(DEVOPTION +" ------Wrong URL or Email entered!")
- return render_template("error_hostname.html", title = TITLE)
- @app.route('/changelog')
- def changelog():
- return render_template("changelog.html", title = TITLE)
- """
- This def reacts to requests and starts the start_loop if it detects a request.
- start_loop is programmed to only take the first request as a start signal using a mechanism using a lock file.
- """
- def start_runner():
- def start_loop():
- not_started = True
- while not_started:
- try:
- #This will initiate the first job before first "official" request.
- r = urllib.request.urlopen(SSL+ENV+".purpl3.net/")
- if r.getcode() == 200:
- print('Server started, quiting start_loop')
- not_started = False
- except:
- print('Server not yet started')
- sleep(2)
- thread = threading.Thread(target=start_loop)
- thread.start()
- if __name__ == '__main__':
- app.run()
Recent Pastes