1515SYSTEM_PASSWORD = "001Arma!23"
1616DB_FILE = "portal_data.json"
1717
18- # --- DATABASE FUNCTIONS (PERSISTENCE) ---
18+ # --- DATABASE FUNCTIONS ---
1919def load_db ():
2020 if not os .path .exists (DB_FILE ):
2121 default_data = {
@@ -26,7 +26,7 @@ def load_db():
2626 "events" : [],
2727 "tutorials" : [],
2828 "announcements" : [],
29- "mod_library" : [] # New storage for Mod JSONs
29+ "mod_library" : []
3030 }
3131 with open (DB_FILE , 'w' ) as f :
3232 json .dump (default_data , f )
@@ -35,7 +35,7 @@ def load_db():
3535 try :
3636 with open (DB_FILE , 'r' ) as f :
3737 data = json .load (f )
38- # Migration checks
38+ # Ensure keys exist
3939 if "usernames" not in data : data ["usernames" ] = {}
4040 if "mod_library" not in data : data ["mod_library" ] = []
4141 return data
@@ -48,48 +48,44 @@ def save_db(data):
4848
4949DB = load_db ()
5050
51- # --- HELPER: WORKSHOP SCRAPER ---
51+ # --- HELPER: WORKSHOP SCRAPER (UPDATED FOR IMAGES) ---
5252def fetch_mod_details (mod_input ):
5353 """
54- Attempts to fetch Mod Name/Version from Arma Reforger Workshop.
55- Input can be a URL or just an ID.
54+ Fetches Name, Version, and IMAGE from Arma Reforger Workshop.
5655 """
57- # Extract ID from URL if full link provided
5856 mod_id = mod_input .strip ()
5957 if "reforger.armaplatform.com/workshop/" in mod_id :
60- # Extract ID from URL (e.g., .../workshop/59673B6FBB95459F-BetterTracers)
6158 try :
6259 mod_id = mod_id .split ("workshop/" )[1 ].split ("-" )[0 ]
6360 except :
64- return None , None , "Invalid URL format"
61+ return None , None , None , "Invalid URL format"
6562
6663 url = f"https://reforger.armaplatform.com/workshop/{ mod_id } "
6764
6865 try :
69- # Fake user agent to avoid immediate blocking
7066 headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' }
7167 response = requests .get (url , headers = headers , timeout = 5 )
7268
7369 if response .status_code == 200 :
7470 soup = BeautifulSoup (response .text , 'html.parser' )
7571
76- # 1. Try to find Title
77- # Reforger workshop structure changes, but usually title is in an H1 or OG tag
78- title = soup .find ("meta" , property = "og:title" )
79- mod_name = title ["content" ] if title else "Unknown Mod"
72+ # 1. Title
73+ title_tag = soup .find ("meta" , property = "og:title" )
74+ mod_name = title_tag ["content" ] if title_tag else "Unknown Mod"
8075
81- # 2. Try to find Version
82- # This is harder to scrape without rendering JS, so we default to "1.0.0" if not found
83- # Sometimes it's in a specific div class. We'll try a generic search.
76+ # 2. Image
77+ img_tag = soup .find ("meta" , property = "og:image" )
78+ mod_img = img_tag ["content" ] if img_tag else None
79+
80+ # 3. Version (Best Guess / Default)
8481 mod_version = "1.0.0"
85- # Advanced scraping would go here, but static scraping is limited on React sites.
8682
87- return mod_id , mod_name , mod_version
83+ return mod_id , mod_name , mod_img , mod_version
8884 else :
89- return mod_id , None , f"Failed to reach Workshop ( Status { response .status_code } ) "
85+ return mod_id , None , None , f" Status { response .status_code } "
9086
9187 except Exception as e :
92- return mod_id , None , str (e )
88+ return mod_id , None , None , str (e )
9389
9490# --- LOCAL SESSION STATE ---
9591if "logged_in" not in st .session_state :
@@ -100,7 +96,6 @@ def fetch_mod_details(mod_input):
10096 st .session_state .page = "view_announcements"
10197if "selected_mod_id" not in st .session_state :
10298 st .session_state .selected_mod_id = None
103- # Editor State
10499if "editor_content" not in st .session_state :
105100 st .session_state .editor_content = "[\n \n ]"
106101
@@ -389,129 +384,124 @@ def get_mod_status():
389384 else : st .error ("User not found." )
390385 st .table (pd .DataFrame (DB ['role_db' ].items (), columns = ["Email" , "Role" ]))
391386
392- # --- NEW: JSON EDITOR / MOD MANAGER PAGE ---
387+ # --- JSON EDITOR / MOD MANAGER PAGE ---
393388elif st .session_state .page == "json_editor" :
394389 st .title ("📝 Mod Configuration Studio" )
395390 if user_role != "SUPER_ADMIN" :
396391 st .error ("Access Denied." )
397392 else :
398- # Layout: Left = Editor, Right = Library
399393 col_editor , col_library = st .columns ([2 , 1 ])
400394
401395 with col_editor :
402396 st .subheader ("Config Editor" )
403397 st .caption ("Construct your server 'mods' array here." )
404- # Text area connected to session state to persist changes during clicks
405398 json_text = st .text_area ("JSON Output" , value = st .session_state .editor_content , height = 600 , key = "main_json_editor" )
406- # Update session state manually if user types in box
407399 st .session_state .editor_content = json_text
408-
409- if st .button ("📋 Copy to Clipboard (Manual)" , help = "Ctrl+A, Ctrl+C" ):
410- st .info ("Select all text above and copy it." )
411400
412401 with col_library :
413402 st .subheader ("📦 Mod Library" )
414403
404+ # SEARCH BAR
405+ search_query = st .text_input ("🔍 Search Library" , placeholder = "Type mod name..." ).lower ()
406+
415407 tab_lib , tab_import = st .tabs (["Saved Mods" , "Import/Fetch" ])
416408
417409 # --- TAB: SAVED MODS ---
418410 with tab_lib :
419- if not DB ['mod_library' ]:
420- st .info ("Library is empty." )
411+ # Filter mods based on search
412+ filtered_mods = [
413+ m for m in DB ['mod_library' ]
414+ if search_query in m .get ('name' , '' ).lower ()
415+ or search_query in m .get ('modId' , '' ).lower ()
416+ ]
417+
418+ if not filtered_mods :
419+ if search_query : st .info ("No mods found." )
420+ else : st .info ("Library is empty." )
421421 else :
422- for idx , mod in enumerate ( DB [ 'mod_library' ]) :
422+ for mod in filtered_mods :
423423 with st .container (border = True ):
424+ # Display Image if available
425+ if mod .get ('image_url' ):
426+ st .image (mod ['image_url' ], use_container_width = True )
427+
424428 st .write (f"**{ mod .get ('name' , 'Unknown' )} **" )
425- st .caption (f"ID: { mod .get ('modId' )} | v{ mod .get ('version' )} " )
429+ st .caption (f"v{ mod .get ('version' )} " )
430+
431+ # DISPLAY CODE BLOCK (This enables the COPY button)
432+ # We create a clean dictionary for the copy paste
433+ clean_mod = {
434+ "modId" : mod .get ('modId' ),
435+ "name" : mod .get ('name' ),
436+ "version" : mod .get ('version' )
437+ }
438+ st .code (json .dumps (clean_mod , indent = 4 ), language = 'json' )
426439
427440 c_add , c_del = st .columns ([3 , 1 ])
428441 with c_add :
429- if st .button ("➕ Add" , key = f"add_mod_{ idx } " ):
430- # Logic to insert into editor
431- # We parse current editor content to list, append, dump back
442+ if st .button ("➕ Insert to Editor" , key = f"add_{ mod ['modId' ]} " ):
432443 try :
433- # Clean up current content to ensure it's a list
434444 current_str = st .session_state .editor_content .strip ()
435445 if not current_str : current_str = "[]"
436-
437- # Basic heuristic: If it ends with ']', remove ']', add comma, add obj, add ']'
438- # Or better: Try to load as json, append, dump
439- # Note: This is a simple append logic
440- new_snippet = json .dumps (mod , indent = 4 )
441-
446+ new_snippet = json .dumps (clean_mod , indent = 4 )
442447 if current_str .endswith ("]" ):
443- # It's a list. Insert before last bracket
444- if len (current_str ) > 2 : # Not empty list
445- updated_str = current_str [:- 1 ] + ",\n " + new_snippet + "\n ]"
446- else : # Empty list []
447- updated_str = "[\n " + new_snippet + "\n ]"
448- else :
449- # Just append it
450- updated_str = current_str + ",\n " + new_snippet
451-
448+ if len (current_str ) > 2 : updated_str = current_str [:- 1 ] + ",\n " + new_snippet + "\n ]"
449+ else : updated_str = "[\n " + new_snippet + "\n ]"
450+ else : updated_str = current_str + ",\n " + new_snippet
452451 st .session_state .editor_content = updated_str
453452 st .rerun ()
454- except Exception as e :
455- st .error (f"Error appending: { e } " )
456-
453+ except : pass
457454 with c_del :
458- if st .button ("🗑️" , key = f"del_mod_{ idx } " ):
459- DB ['mod_library' ].pop (idx )
455+ if st .button ("🗑️" , key = f"del_{ mod ['modId' ]} " ):
456+ # Find index in main DB list (not filtered list)
457+ real_idx = DB ['mod_library' ].index (mod )
458+ DB ['mod_library' ].pop (real_idx )
460459 save_db (DB )
461460 st .rerun ()
462461
463462 # --- TAB: IMPORT / FETCH ---
464463 with tab_import :
465- st .write ("**Method 1: Fetch from Workshop**" )
464+ st .write ("**Fetch from Workshop**" )
466465 search_input = st .text_input ("Workshop ID or URL" , placeholder = "59673B6FBB95459F" )
467466 if st .button ("🔍 Fetch & Save" ):
468467 if search_input :
469- mid , mname , mver = fetch_mod_details (search_input )
468+ mid , mname , mimg , mver = fetch_mod_details (search_input )
470469 if mname :
471- new_mod = {"modId" : mid , "name" : mname , "version" : mver }
470+ new_mod = {"modId" : mid , "name" : mname , "version" : mver , "image_url" : mimg }
472471 DB ['mod_library' ].append (new_mod )
473472 save_db (DB )
474473 st .success (f"Saved: { mname } " )
475474 else :
476475 st .error (f"Error: { mver } " )
477- # Fallback form
478476 st .warning ("Could not scrape. Enter manually below." )
479477
480- with st .expander ("Manual Entry / Fallback " ):
478+ with st .expander ("Manual Entry" ):
481479 m_id = st .text_input ("Mod ID" )
482480 m_name = st .text_input ("Mod Name" )
483481 m_ver = st .text_input ("Version" , value = "1.0.0" )
484- if st .button ("Save Manual Entry " ):
482+ if st .button ("Save Manual" ):
485483 if m_id and m_name :
486484 DB ['mod_library' ].append ({"modId" : m_id , "name" : m_name , "version" : m_ver })
487485 save_db (DB )
488486 st .success ("Saved!" )
489487 st .rerun ()
490488
491489 st .divider ()
492- st .write ("**Method 2: Batch Paste**" )
493- batch_text = st .text_area ("Paste existing JSON blob here " , height = 150 )
490+ st .write ("**Batch Paste**" )
491+ batch_text = st .text_area ("Paste existing JSON blob" , height = 150 )
494492 if st .button ("Process Batch" ):
495- # Regex to find JSON objects with modId
496493 try :
497- # Find all blocks that look like {"modId": ...}
498- # This regex is loose to allow for messy input
499494 matches = re .findall (r'\{[^{}]*"modId"[^{}]*\}' , batch_text , re .DOTALL )
500495 count = 0
501496 for match in matches :
502497 try :
503- # Try to parse each match
504498 mod_obj = json .loads (match )
505499 if "modId" in mod_obj :
506500 DB ['mod_library' ].append (mod_obj )
507501 count += 1
508502 except : pass
509-
510503 if count > 0 :
511504 save_db (DB )
512505 st .success (f"Imported { count } mods!" )
513506 st .rerun ()
514- else :
515- st .warning ("No valid mod objects found." )
516- except Exception as e :
517- st .error (str (e ))
507+ except Exception as e : st .error (str (e ))
0 commit comments