from flask import Flask, render_template, send_from_directory, make_response, request, redirect, session, g, json, send_file, abort import os import random import socket import oracledb import traceback import requests from logging.config import dictConfig import secrets from werkzeug.middleware.proxy_fix import ProxyFix import io import json from expiring_dict import ExpiringDict capcha_site = '6LcJpHMgAAAAAMQLNY_g8J2Kv_qmCGureRN_lbGl' capcha_site_sec = '6LcJpHMgAAAAAIUf4Jg_7NvawQKZoLoVypDU6-d8' capcha_site_url='https://www.google.com/recaptcha/api/siteverify' site = 'salvagedb.com' app_path = os.path.dirname(os.path.realpath(__file__)) app = Flask(__name__) app_debug : bool = os.environ.get('APP_DEBUG',False) app.debug = os.environ.get('APP_DEBUG',False) app.secret_key = secrets.token_hex() app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1) os.environ['NLS_LANG'] = 'American_America.AL32UTF8' #Cache app.cache = ExpiringDict(60*60*24) try: gips = open('gips.txt').read().split('\n') except: gips=[] if app_debug: print(gips) def save_request(request): req_data = {} req_data['endpoint'] = request.endpoint req_data['method'] = request.method req_data['cookies'] = request.cookies req_data['data'] = request.data req_data['headers'] = dict(request.headers) req_data['headers'].pop('Cookie', None) req_data['args'] = request.args req_data['form'] = request.form req_data['remote_addr'] = request.remote_addr return req_data dictConfig( { "version": 1, "formatters": { "default": { "format": "[%(asctime)s] [%(levelname)s | %(module)s] %(message)s", "datefmt": " %d/%m/%Y %H:%M:%S" }, }, "handlers": { "console": { "class": "logging.StreamHandler", "formatter": "default", }, "file": { "class": "logging.handlers.TimedRotatingFileHandler", "filename": app_path + "/logs/app.log", "when": "D", "interval": 10, "backupCount": 5, "formatter": "default", }, }, "root": {"level": "DEBUG", "handlers": ["console", "file"]}, } ) @app.after_request def after_request(response): if request.cookies.get('user_id', None) == None: response.set_cookie('user_id', str(random.randint(0, 10000000000)), max_age=60 * 60 * 24 * 365 * 10) return response def init_session(connection, requestedTag_ignored): pass # cursor = connection.cursor() # cursor.execute(""" # ALTER SESSION SET # TIME_ZONE = 'UTC' # NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI'""") def start_pool(): pool_min = 4 pool_max = 4 pool_inc = 0 if app_debug: print('Connecting to {}:{}/{}'.format(os.environ.get("DB_HOST"), os.environ.get("DB_PORT"), os.environ.get("DB_DBNAME"))) pool = oracledb.create_pool( user=os.environ.get("DB_USERNAME"), password=os.environ.get("DB_PASSWORD"), dsn='{}:{}/{}'.format(os.environ.get("DB_HOST"), os.environ.get("DB_PORT"), os.environ.get("DB_DBNAME")), #params=sample_env.get_pool_params(), min=pool_min, max=pool_max, increment=pool_inc, session_callback=init_session, ) return pool @app.route("/sitemap.xml") def sitemap(): conn = pool.acquire() cur = conn.cursor() cur.execute('select distinct trunc(pg/40000)+1 nm from mv_pages'); nm = cur.fetchall() return render_template('sitemap.xml', site=site, siten=nm) @app.route("/sitemaps/.xml") def sitemaps_xml(sitemap_id): try: id: int = int(sitemap_id) conn = pool.acquire() cur = conn.cursor() cur.execute('select pg from mv_pages where sitemap_number = :p1', {'p1': id}) sitemap_pages = cur.fetchall() return render_template('sitemaps.xml', site=site, sitemap_pages=sitemap_pages) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/") def index_html(): try: if 'maxnum' in app.cache: if app_debug: print('Find in cache max_num={}'.format(app.cache['maxnum'])) cnt = app.cache['maxnum'] else: conn = pool.acquire() cur = conn.cursor() cur.execute('select max(num) from salvagedb') cnt = "{:,}".format(cur.fetchone()[0]) app.cache['maxnum'] = cnt return render_template('index.html', site=site, cnt=cnt ,capcha_site=capcha_site) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/privacy.html") def privacy_html(): try: return render_template('privacy.html', site=site) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/robots.txt") def robot_txt(): try: return render_template('robots.txt', site=site) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/decode", methods = ['POST']) def decode(): try: vin = request.form.get('q').strip() g_respone = request.form['g-recaptcha-response'] capcha_check = requests.post(url=f'{capcha_site_url}?secret={capcha_site_sec}&response={g_respone}').json() if capcha_check['success'] == False or capcha_check['score'] <0.5: app.logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}') app.logger.info(f'Bad google answer: {capcha_check}') abort(401) return redirect(f'/detail/{vin}.html', 301) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/decodevin.html") def decodevin_html(): try: return render_template('decodevin.html', site=site,capcha_site=capcha_site) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/database/page.html") def database_page_prc(page_num): try: pg = int(page_num) conn = pool.acquire() cur = conn.cursor() user_ip = get_ip(request) ## определение ip клиента try: returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 1, 0, 0]) except: print(request) app.logger.error(traceback.format_exc()) cur.execute('select max(pg) from mv_pages') max_page = int(cur.fetchall()[0][0]) cur.execute("""select rownum,s.vin, COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make, COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model, COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year from salvagedb s where num between (select mi from mv_pages where pg = :p1) and (select ma from mv_pages where pg = :p1)""", {'p1': pg}) pgf = cur.fetchall() return render_template('database.html', site=site, cur_page=pg, max_page=max_page, pg=pgf) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/detail/.html") def detail_vin(vin): try: conn = pool.acquire() cur = conn.cursor() user_ip = get_ip(request) ## определение ip клиента try: returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 0, 0, 1]) except: print(request) app.logger.error(traceback.format_exc()) cur.execute("""select 'None', COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='5'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Body Class'),'UNKNOWN') body, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='13'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Model'),'UNKNOWN') engine, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='9'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Number of Cylinders'),'UNKNOWN') celindr, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='15'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Drive Type'),'-') drive from (select substr(:p1,1,10) svin, :p1 vin from dual) s """, {'p1': vin}) res = cur.fetchall() return render_template('details.html', site=site, vin=vin, det=res, capcha_site=capcha_site) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route('/search', methods = ['GET']) def search(): try: user_ip = get_ip(request) ## определение ip клиента vin = request.args.get('q') ua = request.headers.get('User-Agent') app.logger.info(f'AgeNt: {ua}') gmedia = False if ua == 'Mediapartners-Google': app.logger.info(f'Find MediaPartner Aget {ua}') if user_ip in gips: gmedia = True else: gmedia = False try: f = open('ips.txt','a') f.write(f'{user_ip}\n') finally: f.close() g_respone = request.args.get('g-recaptcha-response') capcha_check = requests.post(url=f'{capcha_site_url}?secret={capcha_site_sec}&response={g_respone}').json() if capcha_check['success'] == False or capcha_check['score'] <0.5: app.logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}') app.logger.info(f'Bad google answer: {capcha_check}') if gmedia ==False: req_data = save_request(request) app.logger.info(json.dumps(req_data, indent=4, default=str)) return 'bad req', 401 if len(vin) != 17: return 'bad vin!', 500 conn = pool.acquire() cur = conn.cursor() returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 0, 1, 0]) cur.execute("""select 'None', COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='5'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Body Class'),'UNKNOWN') body, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='13'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Model'),'UNKNOWN') engine, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='9'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Number of Cylinders'),'UNKNOWN') celindr, COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='15'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Drive Type'),'-') drive from (select substr(:p1,1,10) svin, :p1 vin from dual) s """, {'p1': vin}) res = cur.fetchall() cur.execute("""select rownum, t.vin, t.title, t.odo, t.odos, t.dem1, t.dem2, t.year||'/'||t.month from salvagedb t where vin = :p1 and svin = substr(:p1,1,10) """, {'p1': vin}) his = cur.fetchall() return render_template('search.html', site=site, vin=vin, det=res, his=his) except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 ## API @app.route("/api/search") def api_search(): try: access_code = request.args.get('access_code', None) vin = request.args.get('vin', None) user_ip = get_ip(request) ## определение ip клиента # базовые проверки входящих аргументов if len(vin) != 17 or vin is None: ret = {'status': 'incorrect vin'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response if len(access_code) > 16 or access_code is None: ret = {'status': 'incorrect access_code'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response conn = pool.acquire() cur = conn.cursor() ## проверяем access_code cur.execute( 'select t.summ, t.found_price, t.notfound_price, t.decode_price from restfact t where access_code = :p1', {'p1': str(access_code)}) res = cur.fetchone() summ = res[0] found_price = res[1] notfound_price = res[2] decode_price = res[3] ### не достаточно средств if summ <= 0: ret = {'status': 'You have insufficient balance.'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response ## ищем в истории cur.execute('select t.odo,t.odos,t.title,t.dem1,t.dem2,t.year,t.month from salvagedb t where vin =:p1 and svin = :p2', {'p1': vin.upper(), 'p2': vin.upper()[:10]}) res = cur.fetchall() if len(res) > 0: # found dat = [] for it in res: dat.append({ 'odometer': it[0], 'odometer_status': it[1], 'title': it[2], 'damage1': it[3], 'damage2': it[4], 'add_to_db': '{}-{}'.format(it[5], it[6]) }) ret = { 'status': 'found', 'vin': vin.upper(), 'cost': found_price, 'records': dat } response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)", {'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'FOUND', 'p5': found_price}) conn.commit() return response else: # nor found ret = {'status': 'NOT FOUND', 'cost': notfound_price} if app.debug: app.logger.debug(json.dumps(ret)) response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)", {'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'NOT FOUND', 'p5': notfound_price}) conn.commit() return response except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route("/api/restfact") def api_restfact(): access_code = request.args.get('access_code', None) if len(access_code) > 16 or access_code is None: ret = {'status': 'incorrect access_code'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response conn = pool.acquire() cur = conn.cursor() cur.callproc('make_billing') cur.execute( 'select t.summ, t.found_price, t.notfound_price, t.decode_price from restfact t where access_code = :p1', {'p1': str(access_code)}) res = cur.fetchone() summ = res[0] if len(res) <= 0: ret = {'status': 'incorrect access_code'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response else: ret = { 'access_code': access_code, 'restfact': summ } response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response @app.route("/api/restfact_detail") def restfact_detail(): access_code = request.args.get('access_code', None) if len(access_code) > 16 or access_code is None: ret = {'status': 'incorrect access_code'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response conn = pool.acquire() cur = conn.cursor() cur.callproc('make_billing') cur.execute('select rownum from restfact t where access_code = :p1', {'p1': str(access_code)}) res = cur.fetchall() if len(res) <= 0: ret = {'status': 'incorrect access_code'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response cur.execute("select vin, ip, status, dt, cost from billing where access_code = :p1 and dt > CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE) - 45 and made = 1 order by id", {'p1': str(access_code)}) res = cur.fetchall() if len(res) <= 0: ret = {'status': 'billing not found'} response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response else: dat = [] for it in res: dat.append( { 'VIN': it[0], 'IP': it[1], 'STATUS': it[2], 'DATETIME': it[3].isoformat(), 'COST': it[4] } ) ret = { 'access_code': access_code, 'report': dat } response = app.response_class( response=json.dumps(ret), status=200, mimetype='application/json' ) if app.debug: app.logger.debug(json.dumps(ret)) return response @app.route("/ads.txt") def ads_txt(): try: return render_template('ads.txt') except: app.logger.error(traceback.format_exc()) return 'bad request!', 500 @app.route('/favicon.ico') def logo(): """Serves the logo image.""" with open(app_path+"/static/favicon.ico", 'rb') as bites: return send_file( io.BytesIO(bites.read()), attachment_filename='favicon.ico', mimetype='image/x-icon' ) def get_ip(req) -> str: if 'X-Forwarded-For' in req.headers: proxy_data = req.headers['X-Forwarded-For'] ip_list = proxy_data.split(',') user_ip = req.headers.get("Cf-Connecting-Ip") or ip_list[0] # first address in list is User IP else: user_ip = req.headers.get("Cf-Connecting-Ip") or req.remote_addr return user_ip def get_addr(req) -> str: try: return socket.gethostbyaddr(req)[0] except: return req if __name__ == '__main__': # Start a pool of connections pool = start_pool() app.run(port=int(os.environ.get('PORT', '8080')))