From c94d46885af36e47452ea6e93b656b5b10469fff Mon Sep 17 00:00:00 2001 From: Pakobbix Date: Fri, 15 Aug 2025 16:59:04 +0200 Subject: [PATCH] Buyable ships: Switched from starcitizen.tools to UEX for more reliable information --- databases/buyable_ships.db | Bin 0 -> 28672 bytes llm_tools/get_buyable_ships.py | 84 +++++++++++++++++ llm_tools/star_citizen_info_retrieval.py | 111 +++++++++-------------- 3 files changed, 125 insertions(+), 70 deletions(-) create mode 100644 databases/buyable_ships.db create mode 100644 llm_tools/get_buyable_ships.py diff --git a/databases/buyable_ships.db b/databases/buyable_ships.db new file mode 100644 index 0000000000000000000000000000000000000000..1d3ead5f3f708666df8162ca8a83430d2b177741 GIT binary patch literal 28672 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

t9T4&^;x8gpr86 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 literal 0 HcmV?d00001 diff --git a/llm_tools/get_buyable_ships.py b/llm_tools/get_buyable_ships.py new file mode 100644 index 0000000..a5731b5 --- /dev/null +++ b/llm_tools/get_buyable_ships.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 + +import requests +import sqlite3 + +# --- Configuration --- +API_URL = "https://api.uexcorp.space/2.0/vehicles_purchases_prices_all" +with open("uex_api_key", "r") as f: + BEARER_TOKEN = f.read().strip() + +DB_NAME = "buyable_ships.db" +TABLE_NAME = "buyable_ships" + +def setup_database(): + """ + Sets up the SQLite database and creates the table if it doesn't exist. + The table uses a composite primary key (id_vehicle, id_terminal) + to ensure each vehicle at each terminal has only one latest entry. + """ + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + + # Using "IF NOT EXISTS" prevents errors on subsequent runs. + # The schema is derived from your provided image. + # We use INSERT OR REPLACE later, so a primary key is important. + # (id_vehicle, id_terminal) is a good candidate for a unique key. + cursor.execute(f""" + CREATE TABLE IF NOT EXISTS {TABLE_NAME} ( + id_vehicle TEXT, + id_terminal TEXT, + price_buy REAL, + vehicle_name TEXT, + terminal_name TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id_vehicle, id_terminal) + ) + """) + conn.commit() + conn.close() + print("Database setup complete.") + +def fetch_data_from_api(): + """ + Fetches the latest vehicle purchase price data from the UAX Corp API. + Returns the data as a list of dictionaries or None if an error occurs. + """ + headers = {"Authorization": f"Bearer {BEARER_TOKEN}"} + try: + response = requests.get(API_URL, headers=headers) + response.raise_for_status() # Raise an error for HTTP errors + data = response.json() + return data.get("data", []) + except requests.RequestException as e: + print(f"Error fetching API data: {e}") + return None + +def save_data_to_db(data): + """ + Saves the fetched vehicle purchase price data to the SQLite database. + """ + conn = sqlite3.connect(DB_NAME) + cursor = conn.cursor() + + for item in data: + cursor.execute(f""" + INSERT OR REPLACE INTO {TABLE_NAME} (id_vehicle, id_terminal, price_buy, vehicle_name, terminal_name) + VALUES (?, ?, ?, ?, ?) + """, ( + item.get("id_vehicle"), + item.get("id_terminal"), + item.get("price_buy"), + item.get("vehicle_name"), + item.get("terminal_name") + )) + + conn.commit() + conn.close() + print("Data saved to database.") + +if __name__ == "__main__": + setup_database() + data = fetch_data_from_api() + if data: + save_data_to_db(data) diff --git a/llm_tools/star_citizen_info_retrieval.py b/llm_tools/star_citizen_info_retrieval.py index a1df8a4..89bc926 100644 --- a/llm_tools/star_citizen_info_retrieval.py +++ b/llm_tools/star_citizen_info_retrieval.py @@ -597,86 +597,57 @@ class Tools: self, __event_emitter__: Callable[[dict], Any] = None ): """ - Fetches all buyable ships, their prices, and locations from the Star Citizen Tools wiki. + Fetches all buyable ships, their prices, and locations from the buyable_ships.db database. """ emitter = EventEmitter(__event_emitter__) - api_url = "https://starcitizen.tools/api.php" + await emitter.progress_update("Fetching purchasable ships from the database...") ship_data = {} - page_title = "Purchasing_ships" + final_output = "No purchasable ships found in the database." - await emitter.progress_update(f"Fetching data from {page_title}...") - params = { - "action": "parse", - "page": page_title, - "format": "json", - "prop": "text", - } try: - response = await asyncio.to_thread(requests.get, api_url, params=params) - response.raise_for_status() - data = response.json() + conn = sqlite3.connect(self.db_path + "/buyable_ships.db") + cursor = conn.cursor() + cursor.execute( + "SELECT vehicle_name, price_buy, terminal_name FROM buyable_ships ORDER BY vehicle_name" + ) + rows = cursor.fetchall() + conn.close() - if "error" in data: - await emitter.error_update( - f"API Error for {page_title}: {data['error']['info']}" - ) - return - html_content = data.get("parse", {}).get("text", {}).get("*", "") - if not html_content: - await emitter.error_update(f"No content found for {page_title}.") - return + if not rows: + await emitter.error_update("No purchasable ships found in the database.") + print(final_output) + return final_output - await emitter.progress_update(f"Parsing data from {page_title}...") - soup = BeautifulSoup(html_content, "html.parser") - tables = soup.find_all("table", class_="wikitable") + await emitter.progress_update("Processing ship data...") + for row in rows: + ship_name, price, location = row + if ship_name not in ship_data: + ship_data[ship_name] = [] + ship_data[ship_name].append({"price": price, "location": location}) - for table in tables: - header_row = table.find("tr") - if not header_row: - continue - headers = [th.get_text(strip=True) for th in header_row.find_all("th")] + output_lines = [] + for ship_name, locations in sorted(ship_data.items()): + output_lines.append(f"\n--- {ship_name} ---") + output_lines.append("Buyable at:") + for item in locations: + output_lines.append( + f" - Location: {item['location']}, Price: {int(item['price'])} aUEC" + ) - rows = table.find_all("tr")[1:] - for row in rows: - cells = row.find_all("td") - if not cells or len(cells) < 3: - continue + final_output = "\n".join(output_lines) + await emitter.success_update( + f"Found {len(ship_data)} unique buyable ships." + ) - ship_name_tag = cells[1].find("a") - if not ship_name_tag or not ship_name_tag.get("title"): - continue - ship_name = ship_name_tag.get("title").strip() - price = cells[2].get_text(strip=True) + except sqlite3.Error as e: + error_message = f"Database error while fetching purchasable ships: {e}" + await emitter.error_update(error_message) + final_output = error_message + except Exception as e: + error_message = f"An unexpected error occurred: {e}" + await emitter.error_update(error_message) + final_output = error_message - if ship_name not in ship_data: - ship_data[ship_name] = [] - - location_headers = headers[3:] - for i, cell in enumerate(cells[3:]): - if "✔" in cell.get_text(): - location = location_headers[i] - ship_data[ship_name].append( - {"price": price + " aUEC (alpha United Earth Credits)", "location": location} - ) - - await emitter.success_update(f"Successfully processed {page_title}.") - - except requests.exceptions.RequestException as e: - await emitter.error_update(f"Error fetching data for {page_title}: {e}") - except json.JSONDecodeError: - await emitter.error_update(f"Error decoding JSON for {page_title}.") - - output_lines = [] - for ship_name, locations in sorted(ship_data.items()): - output_lines.append(f"\n--- {ship_name} ---") - output_lines.append("Buyable at:") - for item in locations: - output_lines.append( - f" - Location: {item['location']}, Price: {item['price']}" - ) - - final_output = "\n".join(output_lines) - await emitter.success_update(f"Found {len(ship_data)} unique buyable ships.") print(final_output) return final_output @@ -789,4 +760,4 @@ class Tools: if __name__ == "__main__": info_printer = Tools() - asyncio.run(info_printer.get_ship_owners("Perseus")) + asyncio.run(info_printer.list_rentable_ships())