from flask import Flask, render_template, request, redirect, url_for, jsonify import json import requests import logging import os import traceback import time import sys app = Flask(__name__) # Determine logging level if '--debug' in sys.argv: logging_level = logging.DEBUG else: logging_level = logging.INFO # Setup logging to file logging.basicConfig(filename='log.log', level=logging_level, format='%(asctime)s %(levelname)s:%(message)s') # File paths ASSORT_FILE_PATH = 'assort.json' QUEST_ASSORT_FILE_PATH = 'questassort.json' CACHE_FILE_PATH = 'item_cache.json' RUBLE_TPL_ID = '5449016a4bdc2d6f028b456f' # Load assort data try: with open(ASSORT_FILE_PATH) as f: assort_data = json.load(f) logging.info("Assort data loaded successfully") except Exception as e: logging.error(f"Error loading assort data: {e}") logging.error(traceback.format_exc()) try: with open(QUEST_ASSORT_FILE_PATH) as f: quest_assort_data = json.load(f) logging.info("Quest assort data loaded successfully") except json.JSONDecodeError as e: logging.error(f"Error loading quest assort data (malformed JSON): {e}") logging.error(traceback.format_exc()) quest_assort_data = {} except Exception as e: logging.error(f"Error loading quest assort data: {e}") logging.error(traceback.format_exc()) quest_assort_data = {} # Load cache or initialize an empty dictionary def load_item_cache(): global item_cache if os.path.exists(CACHE_FILE_PATH): try: with open(CACHE_FILE_PATH) as f: item_cache = json.load(f) logging.info("Item cache loaded successfully") except Exception as e: logging.error(f"Error loading item cache: {e}") logging.error(traceback.format_exc()) item_cache = {} else: item_cache = {} logging.info("Initialized empty item cache") load_item_cache() # Tarkov.dev API URL TARKOV_API_URL = "https://api.tarkov.dev/graphql" def get_ruble_image(): return get_item_details_cached(RUBLE_TPL_ID) @app.route('/') def index(): if not is_cache_complete(): return redirect(url_for('build_cache')) try: items = get_main_items_with_details(assort_data) ruble_image = get_ruble_image() return render_template('index.html', items=items, ruble_image=ruble_image) except Exception as e: logging.error(f"Error in index route: {e}") logging.error(traceback.format_exc()) return "Error loading items", 500 def get_main_items_with_details(assort_data): items = [] for item in assort_data['items']: if item['parentId'] == 'hideout': item_copy = item.copy() # Create a copy of the item to avoid modifying the original item_copy['details'] = get_item_details_cached(item_copy['_tpl']) item_copy['parts'] = get_item_parts_with_details(item_copy['_id'], assort_data) item_copy['barter_scheme'] = get_barter_scheme_with_details(item_copy['_id'], assort_data) item_copy['quest_requirement'] = get_quest_requirement(item_copy['_id']) item_copy['offer_name'] = item_copy['_id'] # Add offer name items.append(item_copy) logging.info(f"Main items with details: {items}") return items def get_item_details_cached(tpl): if tpl in item_cache: logging.info(f"Cache hit for tpl {tpl}") return item_cache[tpl] else: logging.info(f"Cache miss for tpl {tpl}, fetching from API") item_details = get_items_details([tpl]) if tpl in item_details: item_cache[tpl] = item_details[tpl] save_item_cache() return item_details.get(tpl, {}) def get_items_details(tpls): queries = "\n".join([ f'item{index}: item(id: "{tpl}") {{ id name shortName description basePrice image512pxLink wikiLink }}' for index, tpl in enumerate(tpls) ]) query = f"{{ {queries} }}" try: response = requests.post(TARKOV_API_URL, json={'query': query}) data = response.json() logging.info(f"Item details for tpls {tpls}: {data}") return {tpl: data['data'][f'item{index}'] for index, tpl in enumerate(tpls)} except Exception as e: logging.error(f"Error fetching item details for tpls {tpls}: {e}") logging.error(traceback.format_exc()) return {} def get_item_parts_with_details(parent_id, assort_data): parts = [] def fetch_parts(parent_id): sub_parts = [] for item in assort_data['items']: if item['parentId'] == parent_id: part_copy = item.copy() # Create a copy of the part to avoid modifying the original part_copy['details'] = get_item_details_cached(part_copy['_tpl']) part_copy['parts'] = fetch_parts(part_copy['_id']) # Fetch sub-parts recursively sub_parts.append(part_copy) return sub_parts parts = fetch_parts(parent_id) logging.info(f"Parts for parent_id {parent_id}: {parts}") return parts def get_barter_scheme_with_details(item_id, assort_data): barter_scheme = [] if 'barter_scheme' in assort_data: for scheme in assort_data['barter_scheme'].get(item_id, []): scheme_details = [] for req in scheme: req_copy = req.copy() # Create a copy of the req to avoid modifying the original req_details = get_item_details_cached(req_copy['_tpl']) req_copy['details'] = req_details scheme_details.append(req_copy) barter_scheme.append(scheme_details) logging.info(f"Barter scheme for item_id {item_id}: {barter_scheme}") return barter_scheme def get_quest_requirement(item_id): for quest_type, quests in quest_assort_data.items(): if item_id in quests: return { "type": quest_type, "quest": quests[item_id] } return None def get_all_item_ids(assort_data): ids = set() for item in assort_data['items']: ids.add(item['_tpl']) for part in item.get('parts', []): ids.add(part['_tpl']) for schemes in assort_data.get('barter_scheme', {}).values(): for scheme in schemes: for req in scheme: ids.add(req['_tpl']) return list(ids) def is_cache_complete(): item_ids = get_all_item_ids(assort_data) for item_id in item_ids: if item_id not in item_cache: return False return True def build_item_cache(): try: item_ids = get_all_item_ids(assort_data) item_details = get_items_details(item_ids) for tpl, details in item_details.items(): item_cache[tpl] = details save_item_cache() return True except Exception as e: logging.error(f"Error building item cache: {e}") logging.error(traceback.format_exc()) return False @app.route('/edit/', methods=['GET', 'POST']) def edit_item(item_id): try: if request.method == 'POST': # Handle form submission new_barter_scheme = [] tpl_list = request.form.getlist('barter_tpl') count_list = request.form.getlist('barter_count') scheme = [] for i in range(len(tpl_list)): scheme.append({ '_tpl': tpl_list[i], 'count': int(count_list[i]) }) new_barter_scheme.append(scheme) update_barter_scheme(item_id, new_barter_scheme) return redirect(url_for('index')) item = next((item for item in assort_data['items'] if item['_id'] == item_id), None) if item: item_copy = item.copy() # Create a copy of the item to avoid modifying the original item_copy['details'] = get_item_details_cached(item_copy['_tpl']) item_copy['parts'] = get_item_parts_with_details(item_id, assort_data) item_copy['barter_scheme'] = get_barter_scheme_with_details(item_id, assort_data) item_copy['quest_requirement'] = get_quest_requirement(item_id) ruble_image = get_ruble_image() return render_template('edit.html', item=item_copy, ruble_image=ruble_image) else: return "Item not found", 404 except Exception as e: logging.error(f"Error in edit_item route: {e}") logging.error(traceback.format_exc()) return "Error editing item", 500 def update_barter_scheme(item_id, new_barter_scheme): try: assort_data['barter_scheme'][item_id] = new_barter_scheme save_assort_data() except Exception as e: logging.error(f"Error updating barter scheme for item {item_id}: {e}") logging.error(traceback.format_exc()) def save_assort_data(): try: # Deep copy the assort data and remove 'details' key before saving cleaned_assort_data = json.loads(json.dumps(assort_data)) # Deep copy # Clean items for item in cleaned_assort_data['items']: if 'details' in item: del item['details'] # Clean barter_scheme for item_id, schemes in cleaned_assort_data.get('barter_scheme', {}).items(): for scheme in schemes: for part in scheme: if 'details' in part: del part['details'] # Backup the existing assort.json if not os.path.exists('./bkp'): os.makedirs('./bkp') backup_file_path = os.path.join('./bkp', f"assort_backup_{int(time.time())}.json") if os.path.exists(ASSORT_FILE_PATH): os.rename(ASSORT_FILE_PATH, backup_file_path) logging.info(f"Backup of assort data created at {backup_file_path}") with open(ASSORT_FILE_PATH, 'w') as f: json.dump(cleaned_assort_data, f, indent=2) # Use 2 spaces for indentation logging.info("Assort data saved successfully") except Exception as e: logging.error(f"Error saving assort data: {e}") logging.error(traceback.format_exc()) def save_item_cache(): try: with open(CACHE_FILE_PATH, 'w') as f: json.dump(item_cache, f, indent=2) # Use 2 spaces for indentation logging.info("Item cache saved successfully") except Exception as e: logging.error(f"Error saving item cache: {e}") logging.error(traceback.format_exc()) @app.route('/item_image/') def item_image(tpl): details = get_item_details_cached(tpl) return jsonify({'image512pxLink': details.get('image512pxLink', ''), 'name': details.get('name', '')}) @app.route('/search_items', methods=['GET']) def search_items(): query = request.args.get('query', '') if not query: return jsonify([]) search_query = f''' {{ items(name: "{query}") {{ id name gridImageLink }} }} ''' try: response = requests.post(TARKOV_API_URL, json={'query': search_query}) data = response.json() items = data['data'].get('items', []) return jsonify(items) except Exception as e: logging.error(f"Error searching for items with query '{query}': {e}") logging.error(traceback.format_exc()) return jsonify([]), 500 @app.route('/build_cache', methods=['GET']) def build_cache(): return render_template('build_cache.html') @app.route('/start_build_cache', methods=['POST']) def start_build_cache(): success = build_item_cache() if success: return jsonify({"status": "success"}) else: return jsonify({"status": "error"}), 500 if __name__ == '__main__': print("Webapp is hosted at http://127.0.0.1:5000") app.run(debug='--debug' in sys.argv)