From 7825abf11c0758ada072a6cb714acc53cd075ba8 Mon Sep 17 00:00:00 2001 From: Brandon Cornejo Date: Mon, 28 May 2018 22:43:59 -0500 Subject: [PATCH] Initial commit --- config.py | 3 + discworld.db | Bin 0 -> 17408 bytes discworld/__init__.py | 9 + discworld/models.py | 95 ++++++++ discworld/shop.py | 97 +++++++++ discworld/static/favicon.ico | Bin 0 -> 785 bytes discworld/templates/layout.html | 76 +++++++ discworld/templates/shop_dashboard.html | 205 ++++++++++++++++++ discworld/templates/shop_dataentry.html | 17 ++ discworld/templates/shop_product_entries.html | 119 ++++++++++ discworld/views.py | 36 +++ uwsgi.ini | 11 + wsgi.py | 4 + 13 files changed, 672 insertions(+) create mode 100644 config.py create mode 100644 discworld.db create mode 100644 discworld/__init__.py create mode 100644 discworld/models.py create mode 100644 discworld/shop.py create mode 100644 discworld/static/favicon.ico create mode 100644 discworld/templates/layout.html create mode 100644 discworld/templates/shop_dashboard.html create mode 100644 discworld/templates/shop_dataentry.html create mode 100644 discworld/templates/shop_product_entries.html create mode 100644 discworld/views.py create mode 100644 uwsgi.ini create mode 100644 wsgi.py diff --git a/config.py b/config.py new file mode 100644 index 0000000..8613f9d --- /dev/null +++ b/config.py @@ -0,0 +1,3 @@ +DEBUG = False +SQLALCHEMY_TRACK_MODIFICATIONS = False +SQLALCHEMY_DATABASE_URI = 'sqlite:///../discworld.db' diff --git a/discworld.db b/discworld.db new file mode 100644 index 0000000000000000000000000000000000000000..c33e3a8a768c155496a3e8beaef631f86c7fbd3d GIT binary patch literal 17408 zcmeHNO>84c6|U}b?AZBnoSz9{S*CY}FbSmF_0#{b%Vsm4A==sOZe|8)F9av<#LYPF z&~_%1)$9reabF}3D}>O1!~qZ-IB-BnNN`2K!i5VQKpYU_u#3b64!p8Gad$h8k`vlh z_e{0ho$tMR@2h&RtLypptGE2Nr|bsdezmP|ltGe&o>vrvP#szc+6c5fv}tG=XipC5 z$;BC;b)^1faRUDrVd*`DC-JY~BR&4F3_M!Ex%p?Fk-qvyySm-*TJ_*y%WJm7EBnD3VFY#tZ&?@+^lRW8!z8cHtycKwW1XAVfAS1AoO=U<#Vf> zYoA@+Tw-K-0MTj(JNNpCIG|RAiFh6Qy|eyOB_?_h2DQVT_STTTU)fxLX?628<;BWt z%93AO7WUkDd9$*9b3@!)>WeQco0S`t&C14F<#yMv-rqWMh2^g>DL22kC_TE?=cb3~ zu+Y1f2l+YCGnAobb-zyzB?)%gLA%=6YWS_TcS5l6<22vyZmhq0x1uZwjmw2)OU}-} zxF{jNS@RyWzSMwt-l`t9gUnq5QV|2EfuIW#HlrT$;%sinbe72!1$uN-H=DgXZ4CbOu>yJM{PJ zZDl7oIPgNH8t!jbo3&CYgRb>N#P@2mTAa$Dt7toT;5YY_5Kg6HAJfw~JDEW$+6z6e zsWkj1ocDsG=9P&IT1TzAzrXJZL1ou()s@yxwc#n-LG8G8=(nfFGiVV7#}I&_a0U-`kxn2=!4%>eZu0F)s{jdPi*qOxxH&S7{Tg1L1RgtfU` z29;5*8s1a38=$ZasWJ$gvss~U*Khd3WTjs9!vnur7|EaoRNb!b9JXdN9fyKOO$8aS zzorD+{v|93ZFtpo9jt}i)An}S{(Wyj7K9#v8YS2jR3AfkK>%L2<|JVOaL^R@z=Y7> z-VT}t7|j{^7~yZqKfsQBEdLr`gLr=ne;x<;YxsNepYhM|2EK&fk^hDNaq*!~B~k`X zW#AH~QZ$gd8DVAtThgfsVY-B`^#pqPpj5LYsDpg4|8K0 zg%_o0Dj4soj%0!{p*m{6NCX98Q6vxKg`Q{{7@fmqX&?*agsx-~$QJN|)Xf1S9XApw zAR`DI%m7#rcxnQWXK^_{5dUIvkMX-=h$iuW@V~p4r;b>9Gi4xU-~+(G__N@|#4?<+ zvpsi4;j;)W2Tfdy_kFQ67=tGE0H&aczfVFN?=`U(Al@NlXz7tMkTUQ-WI(+C&!SZc zzk|PvTX+r6%72xAB0rKhfMmk49D*=2iqWyI*jNz$kus&W^r?TkjE?x)gtzCGFimH#pC(Fe%y#;&X0u$xQ ziwP9U6BiR0)y5MZ?*@nAi3?nzmP?(!$K%@Ah45oqA&HMtG@!qr<&*d`?a6DS7s8L0 zbATT{;RX!KY1s>PkVWbU2t;RSbjl6rAJH;N{bv%CQSn98V^zKgTvnxyHTjcxjLql) z>j&aFy^%7IGLSNmGLSNmGLSNmGLSNmGLSNmGLSNmGLSNmGLSNmGVp<6pu7M7EQ0<2 z$GC}~!$tW|@{i>&%dg0a^e*iGzXkjMRKn;F=rP(yqH@?jv}cn$pWUrqTP0Lwq_g$R zU2bTGWpm5I^wU|iC^p+~2lo~Fki$Q-w?7{hp)PYkgy{|`(odZ&LM?@uF4bLPYNkys zOP|u*gjLZlVtC7dUhs+oC4laBZ>I#%-R0ld3g z-BsSJjtr|Uo4lwRiT&YjJ*5LDyu%K0nW5>rshcFH z>cj1i!MSBpv!wAe$n4^Y?NX}QHZdGBtuesQulBc}pOk|l_Vfg}qjP(O#>tQP8q>}h zgnusYG0g++7~6Ru4_7D z)Zsa@GH6iOg+U3zsbfx+KiT1PNO>*V1-HQgtDt`3R1XI9a|1kZM!WHUUDW@+jlYOp zJPz^yBl%5vL%uBiQ~HIpB@w6rO6d2U`rq|_@b|A)mg8d>DVM|ygm@+_uP&X_K&o?t zjg_yRMF2AuMqO&UoN7QA<3>*Xczl9K3xG^lsO{>O%WVyck<`#j>SDs2$apXaAL4T6 zGNM^JGC1H7dZ?{1=5pPoOrsXFD4kmQSe(pZ=5W*E zytJak(Zh5TL+5Oyd^y3TCw_VRq*f@5f$F6L|24z39Hz@i{b-!bXfY{{hxpX9+S46& z`m;64P7JR*mIGt;k%an4sSx1Aa4C0nt`U(O^-1lig!<^%Lh^-qU~qUCXqG`avt;$d zfR7gPB4ozk+$Oe_TUm&cISkDxvq)C^5TH-V?43x9YBwDb{dqPIQ$gS(nf*y}9FWm1 zn4GjoFM&+rff=PVR$jM=ZrVk4F5&aA@;W>X3`Z96pOj}MFF_{N>xp@N@?=hEH+0>w zC)Blb!nsMPQ7FFv@Y8-&0Lzwyeg-QGD^p%QuK;t1!(i?E{8ip9Inrh z&wxl`EqLN}LP7@#S|rb%N20qVw>dZX%y|bm#ib0S45SRC45SRC45SRC45SRC47~pt F_#dJ(n@s=! literal 0 HcmV?d00001 diff --git a/discworld/__init__.py b/discworld/__init__.py new file mode 100644 index 0000000..bdabc8a --- /dev/null +++ b/discworld/__init__.py @@ -0,0 +1,9 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config.from_object('config') + +db = SQLAlchemy(app) + +from . import views diff --git a/discworld/models.py b/discworld/models.py new file mode 100644 index 0000000..ebdf3cc --- /dev/null +++ b/discworld/models.py @@ -0,0 +1,95 @@ +from datetime import datetime + +from . import ( + db, + shop +) + + +class ShopProduct(db.Model): + id = db.Column(db.Integer, primary_key = True) + name = db.Column(db.String(100), unique=True, nullable=False) + total_listed = db.Column(db.Integer) + total_sold = db.Column(db.Integer) + entries = db.relationship('ShopEntry', backref='product', lazy=True, + order_by="desc(ShopEntry.date)") + + def __repr__(self): + return ''.format(self.name) + + @property + def latest_entry(self): + return ShopEntry.query.filter_by( + product_id=self.id).order_by(ShopEntry.date.desc()).first() + + @property + def total_stocked(self): + entries = ShopEntry.query.filter_by(product_id=self.id).order_by( + ShopEntry.date.asc()) + + last_stock = 0 + stocked_count = 0 + for entry in entries: + diff = last_stock - entry.stock + if diff < 0: + stocked_count += abs(diff) + last_stock = entry.stock + return stocked_count + + @property + def total_sold(self): + entries = ShopEntry.query.filter_by(product_id=self.id).order_by( + ShopEntry.date.asc()) + + last_stock = 0 + sold_count = 0 + for entry in entries: + diff = last_stock - entry.stock + if diff > 0: + sold_count += diff + last_stock = entry.stock + return sold_count + + @property + def total_earned(self): + entries = ShopEntry.query.filter_by(product_id=self.id).order_by( + ShopEntry.date.asc()) + + last_stock = 0 + earned_count = 0.00 + for entry in entries: + diff = last_stock - entry.stock + if diff > 0: + earned_count += (entry.price * diff) + last_stock = entry.stock + return earned_count + + +class ShopEntry(db.Model): + id = db.Column(db.Integer, primary_key=True) + stock = db.Column(db.Integer, unique=False, nullable=False) + raw_price = db.Column(db.String(20), unique=False, nullable=False) + raw_stock = db.Column(db.String(2), unique=False, nullable=False) + date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) + product_id = db.Column(db.Integer, db.ForeignKey('shop_product.id'), + nullable=False) + + def __repr__(self): + return ''.format( + self.product.name, + self.id + ) + + @property + def stock(self): + stock_map = { + 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, + 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, + 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14 + } + return stock_map[self.raw_stock] + + @property + def price(self): + brass = shop.convert_lancre_to_brass(self.raw_price) + return shop.convert_brass_to_am(brass) diff --git a/discworld/shop.py b/discworld/shop.py new file mode 100644 index 0000000..5a47584 --- /dev/null +++ b/discworld/shop.py @@ -0,0 +1,97 @@ +import re + +from . import ( + db, + models +) + + +def convert_lancre_to_brass(raw_price): + coins = { + 'penny': 0, + 'shilling': 0, + 'crown': 0, + 'sovereign': 0, + 'hedgehog': 0 + } + + def noz(num): + if num and num is not '-': + return int(num) + return 0 + + # Figure out which special notation we have + if "LC" in raw_price: + pattern = r'^LC (\d+)\|(\d+|-)\|(\d+|-)$' + match = re.match(pattern, raw_price) + groups = match.groups() + + coins['penny'] = noz(groups[2]) + coins['shilling'] = noz(groups[1]) + coins['crown'] = noz(groups[0]) + elif "LSov" in raw_price: + pattern = r'^LSov (\d+)\|(\d+|-)\|(\d+|-)\|(\d+|-)$' + match = re.match(pattern, raw_price) + groups = match.groups() + + coins['penny'] = noz(groups[3]) + coins['shilling'] = noz(groups[2]) + coins['crown'] = noz(groups[1]) + coins['sovereign'] = noz(groups[0]) + elif "LH" in raw_price: + pattern = r'^LH (\d+)\|(\d+|-)\|(\d+|-)\|(\d+|-)\|(\d+|-)$' + match = re.match(pattern, raw_price) + groups = match.groups() + + coins['penny'] = noz(groups[4]) + coins['shilling'] = noz(groups[3]) + coins['crown'] = noz(groups[2]) + coins['sovereign'] = noz(groups[1]) + coins['hedgehog'] = noz(groups[0]) + + # Convert to brass + brass_coins = ( + (coins['hedgehog'] * 248832) + + (coins['sovereign'] * 20736) + + (coins['crown'] * 1728) + + (coins['shilling'] * 144) + + (coins['penny'] * 12) + ) + return brass_coins + +def convert_brass_to_am(brass_price): + # 12 brass coins to am pennies + pennies = brass_price / 4 + return (pennies/100) + +def parse_shop_output(data): + pattern = r'^\s{3}?\w{2}\)\sAn*\s([\w\s-]+) for (L\w{1,3} [\d\-|]+);\s(\w+)\sleft\.$' + matches = [m.groups() for m in re.finditer(pattern, data, re.MULTILINE)] + + if not matches: + return + + # Iterate over each product line in the data + seen_products = [] + for m in matches: + product = models.ShopProduct.query.filter_by(name=m[0]).first() + if not product: + # If we didn't find a product, create it + product = models.ShopProduct(name=m[0]) + db.session.add(product) + # Add a ShopEntry for this row only if stock has changed + if not product.latest_entry or product.latest_entry.raw_stock != m[2]: + entry = models.ShopEntry(raw_price=m[1], raw_stock=m[2], product=product) + db.session.add(entry) + seen_products.append(product) + + # Check all products against seen, record sellouts + products = models.ShopProduct.query.all() + for product in products: + if product not in seen_products and product.latest_entry.stock != 0: + entry = models.ShopEntry( + raw_price=product.latest_entry.raw_price, + raw_stock='zero', product=product + ) + db.session.add(entry) + db.session.commit() diff --git a/discworld/static/favicon.ico b/discworld/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7647521e22874a9a1ddda9cbc56c8abc39685cfb GIT binary patch literal 785 zcmV+s1Md8ZP)!?Wyb{U=f zIA_k8IlHybOp^pb3pQt+b})l>B#+3@sD3K|J3qy?6WL8?(J0Fm>W zF9DSC0%i5sm05!Ost>u{;6j|{@7Z-yIjXkuJZA$CQ8&X$9vW!aq0ka`3b-vG;}Az*=X zo9Jl>9t(oo2CQX?3J|~9$U*_XwM9!K3H&mDh7qpnqOl{ zTF(lb79}S+_3OvCnhF?+G%~YcI6q>@MYG;GrOY94)K3gdB5aUqKa;3U=K9E zmu=Tz)a?Vd76EGx*@dr2%upM$uu+a=8_%ZJZn3(1V_qOn`X^iy@!H$CKe~CPW#kP#Za-1LuZBk + + + Ramtops Remedies and Reagents + + + + + + + + + + + + + + + +
+ +

+ Ramtops Remedies and Reagents +

+ + +
+
{% block left_top_bar %} {% endblock %}
+
{% block center_top_bar %} {% endblock %}
+ +
+ + + {% block content %} {% endblock %} + + +
+ © 2017 by Ruhsbaar +    |    + Created for Ramtops Remedies and Reagents +    |    + DiscworldMUD +
+
+ + {% block pagescripts %} {% endblock %} + + diff --git a/discworld/templates/shop_dashboard.html b/discworld/templates/shop_dashboard.html new file mode 100644 index 0000000..4d54793 --- /dev/null +++ b/discworld/templates/shop_dashboard.html @@ -0,0 +1,205 @@ +{% extends "layout.html" %} + +{% block left_top_bar %} + +{% endblock %} + +{% block center_top_bar %} + Total Funds Earned: + +{% endblock %} + +{% block content %} + + + + + + + + + + + + + + + {% for product in products %} + {% if product.latest_entry != None %} + + + + + + + + + + {% endif %} + {% endfor %} + +
Product NameCurrent StockTotal StockTotal SoldTotal EarnedLatest PriceLast Update
{{ product.latest_entry.stock }}{{ product.total_stocked }}{{ product.total_sold }}A${{ product.total_earned }} + + A${{ product.latest_entry.price }} + + {{ product.latest_entry.date.isoformat() }}
+{% endblock %} + +{% block pagescripts %} + + +{% endblock %} diff --git a/discworld/templates/shop_dataentry.html b/discworld/templates/shop_dataentry.html new file mode 100644 index 0000000..3483531 --- /dev/null +++ b/discworld/templates/shop_dataentry.html @@ -0,0 +1,17 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ + +
+ + +
+ +
+{% endblock %} diff --git a/discworld/templates/shop_product_entries.html b/discworld/templates/shop_product_entries.html new file mode 100644 index 0000000..2d83dfa --- /dev/null +++ b/discworld/templates/shop_product_entries.html @@ -0,0 +1,119 @@ +{% extends "layout.html" %} + +{% block left_top_bar %} + +{% endblock %} + +{% block center_top_bar %} +

{{ product.name }}

+{% endblock %} + +{% block content %} + + + + + + + + + + + + {% for entry in product.entries %} + + + + + + + {% endfor %} + + +
StockPrice (Raw)Price (Cvt)Update
{{ entry.stock }}{{ entry.raw_price }}A${{ entry.price }}{{ entry.date.isoformat() }}
{{ product.entries | length }} total entries.
+{% endblock %} + +{% block pagescripts %} + + + +{% endblock %} diff --git a/discworld/views.py b/discworld/views.py new file mode 100644 index 0000000..8c84e01 --- /dev/null +++ b/discworld/views.py @@ -0,0 +1,36 @@ +import re + +from flask import ( + request, + url_for, + redirect, + render_template +) + +from . import ( + db, + app, + models, + shop +) + + +@app.route('/') +def shop_dashboard(): + return render_template('shop_dashboard.html', + products=models.ShopProduct.query.all()) + +@app.route('/shop/data', methods=['POST','GET']) +def shop_dataentry(): + if request.method == 'POST': + if request.form.get('password') == 'r3m3di3s' and request.form.get('mission-data'): + data = request.form['mission-data'] + clean_data = data.replace('\r\n', '\n') + shop.parse_shop_output(clean_data) + return redirect(url_for('shop_dashboard')) + return render_template('shop_dataentry.html') + +@app.route('/shop/product/') +def shop_product_entries(product_id): + product = models.ShopProduct.query.filter_by(id=product_id).first_or_404() + return render_template('shop_product_entries.html', product=product) diff --git a/uwsgi.ini b/uwsgi.ini new file mode 100644 index 0000000..3507337 --- /dev/null +++ b/uwsgi.ini @@ -0,0 +1,11 @@ +[uwsgi] +module = wsgi:application + +master = true +processes = 4 + +socket = discworld.sock +chmod-scoket = 660 +vacuum = true + +die-on-term = true diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..27fe4cd --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +from discworld import app as application + +if __name__ == "__main__": + application.run()