import all

This commit is contained in:
ph0sgene committed 2025-10-09 12:35:28 -07:00
commit 881baaa1ed
25 files changed
+1779

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import aiohttp
import json
import dotenv
import os
import importlib.metadata
dotenv.load_dotenv()
api_key = os.getenv("BRICKOWL")
abrickowl_version = importlib.metadata.version("abrickowlapi")
class ABrickowlAPI:
def __init__(self, value):
self.user_agent = f"abrickowlapi/{abrickowl_version}"
self.headers = {"content-type": "application/x-www-form-urlencoded"}
self.base_url = "https://api.brickowl.com/v1/"
self.session = aiohttp.ClientSession()
return self
def set_valid(self, set_id: int):
"""
Verify if a LEGO set ID is valid.
"""
if len(set_id) == 5:
return True
else:
return False
@classmethod
async def catalog_list(self):
"""
Fetch the list of LEGO sets from the Brickowl API.
"""
async with self.session.get(
f"{self.base_url}catalog/list/view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Error fetching list, {response.status}")
@classmethod
async def catalog_lookup(self, set_id: int):
"""
Fetch details from the brickowl API for a specific LEGO set.
set_id: The LEGO set ID to look up.
returns JSON response containing set details.
"""
async with self.session.get(
f"{self.base_url}catalog/lookup/{set_id}view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Error fetching set {set_id}, {response.status}")
@classmethod
async def catalog_availability(self, set_id: int):
"""
Fetch availability information for a specific LEGO set.
"""
async with self.session.get(
f"{self.base_url}catalog/availability/{set_id}view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(
f"Error fetching fetching availability for {set_id}, {response.status}"
)
@classmethod
async def catalog_id_lookup(self, set_id: int):
async with self.session.get(
f"{self.base_url}catalog/id_lookup/{set_id}view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"ID lookup failed for set {set_id}, {response.status}")
@classmethod
async def catalog_bulk(self):
async with self.session.get(
f"{self.base_url}catalog/bulk/view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Error fetching bulk, {response.status}")
@classmethod
async def catalog_search(self, query: str):
async with self.session.get(
f"{self.base_url}catalog/search/{query}view?key={api_key}",
headers=self.headers,
user_agent=self.user_agent,
) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Search term {set_id} failed, {response.status}")
# Usage:
# obj = await ABrickowlAPI.catalog_lookup(value)
+137
View File
@@ -0,0 +1,137 @@
"""
abricksetapi.py - Asynchronous Brickset API Client
This module provides asynchronous interactions with the Brickset API.
"""
import asyncio
import os
import aiohttp
import json
import dotenv
from typing import Dict, Any
from asyncio_throttle.throttler import Throttler
dotenv.load_dotenv()
class AsyncBricksetAPI:
"""Asynchronous client for the Brickset API"""
def __init__(self, debug: bool = False):
self.username = os.getenv("BRICKSET_USERNAME")
self.password = os.getenv("BRICKSET_PASS")
self.api_key = os.getenv("BRICKSET_API_KEY")
self.debug = debug
self.headers = {"UserAgent": "Edderkop/0.1"}
self.throttler = Throttler(rate_limit=500, period=60)
self.user_hash = None
async def login(self) -> Dict[str, Any]:
"""Asynchronously authenticate with Brickset API"""
url = "https://brickset.com/api/v3.asmx/login"
params = {
"username": self.username,
"password": self.password,
"apiKey": self.api_key,
}
async with aiohttp.ClientSession() as session:
async with session.get(
url, params=params, headers=self.headers # type: ignore
) as response:
if self.debug:
print(f"[@DEBUG] Login response status: {response.status}")
data = await response.read()
result = json.loads(data.decode("utf-8"))
self.user_hash = result.get("hash")
return result
async def get_set(self, set_number: str) -> Dict[str, Any]:
"""
Fetch LEGO set information asynchronously
Args:
set_number (str): LEGO set number
Returns:
Dict containing set information
"""
if not self.user_hash:
await self.login()
url = "https://brickset.com/api/v3.asmx/getSets"
params = {
"apiKey": self.api_key,
"userHash": self.user_hash,
"params": json.dumps({"setNumber": f"{set_number}-1"}),
}
async with aiohttp.ClientSession() as session:
async with session.get(
url, params=params, headers=self.headers
) as response:
if self.debug:
print(f"[@DEBUG] Get set response status: {response.status}")
if response.status == 200:
data = await response.read()
result = json.loads(data.decode("utf-8"))
if not result.get("sets"):
raise ValueError(f"No data found for set {set_number}")
set_data = result["sets"][0]
our_set = {
"set_num": set_data["number"],
"set_name": set_data["name"],
"set_theme": set_data["theme"],
"set_image": set_data["image"]["thumbnailURL"],
"set_barcode": set_data["barcode"]["UPC"],
"set_retailprice": round(
set_data["LEGOCom"]["US"]["retailPrice"]
),
}
# Calculate adjusted price (10% markup)
retail_price = our_set["set_retailprice"]
our_set["set_adjustredprice"] = retail_price + (
(10 / 100) * retail_price
)
# Set PW inventory name with theme
our_set["pw_name"] = (
f"{our_set['set_num']} - {our_set['set_theme'].upper()} {our_set['set_name']}"
)
# Flag for manual price review if needed
if our_set["set_retailprice"] >= 110:
our_set["requires_manual_price"] = True
return our_set
elif response.status == 500:
raise Exception(
"Internal Server Error: Please check API key and parameters."
)
else:
raise Exception(f"Unexpected response: {response.status}")
async def throttled_get_set(self, set_number: str) -> Dict[str, Any]:
"""Get set information with rate limiting"""
async with self.throttler:
return await self.get_set(set_number)
async def main():
"""Example usage"""
client = AsyncBricksetAPI(debug=True)
try:
set_info = await client.throttled_get_set("10349")
print(json.dumps(set_info, indent=2))
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
asyncio.run(main())
+135
View File
@@ -0,0 +1,135 @@
import os
import dotenv
import json
import asyncio
import uuid
import re
import get_sq_env
def get_set_number(text: str) -> int:
# Remove all letters, keeping only digits and spaces
numbers = re.findall(r"\d{5}", text)
if not numbers:
raise ValueError("No five-digit number found in string")
return int(numbers[0])
SQUARE_TOKEN = get_sq_env.SQUARE_TOKEN
LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID
ENVIRONMENT = get_sq_env.environment
from square import AsyncSquare
from square.environment import SquareEnvironment
client = AsyncSquare(token=SQUARE_TOKEN, environment=ENVIRONMENT) # type: ignore
async def get_itm(sku: str):
"""
Asynchronously retrieves item details from Square Catalog using a SKU.
Args:
sku (str): The SKU or text filter to search for the item.
Returns:
dict: A dictionary containing the item's name and ID.
Raises:
IndexError: If no items are found matching the SKU.
Exception: If there is an error communicating with the Square API.
"""
result = await client.catalog.search_items(text_filter=sku, limit=1)
result_json = json.loads(result.model_dump_json())
name = result_json["items"][0]["item_data"]["name"]
itm_id = result_json["items"][0]["id"]
return {"name": name, "id": itm_id}
async def update_itm(lego_set: dict):
"""
Asynchronously updates an item in the Square catalog based on the provided LEGO set information.
Args:
lego_set (dict): A dictionary containing LEGO set details. Expected keys include:
- "sets": List of set dictionaries, each with "name", "number", "barcode" (with "UPC"), and "image" (with "thumbnailURL").
- "pw_name": The name associated with the LEGO set in another system.
- "set_adjustredprice": The adjusted price for the set.
Returns:
bool: True if the update operation was successful.
Raises:
KeyError: If expected keys are missing from the input dictionary.
Exception: For errors during Square API interaction.
"""
set_name = lego_set["sets"][0]["name"]
set_number = lego_set["sets"][0]["number"]
set_upc = lego_set["sets"][0]["barcode"]["UPC"]
set_image = lego_set["sets"][0]["image"]["thumbnailURL"]
pw_name = lego_set["pw_name"]
set_adjustedprice = lego_set["set_adjustredprice"]
result = await client.catalog.search_items(text_filter=set_number, limit=1)
itm_id = json.loads(result.model_dump_json())["items"][0]["id"]
# await sq.catalog.object.upsert()
return True
async def acat2id(cat_id: str):
"""
Return a list of dictionaries containing item IDs and names within the specified category ID.
Args:
cat_id (str): The category ID to search for items.
Returns:
list[dict]: A list of dictionaries, each containing:
- 'id': The item's ID
- 'name': The item's name
"""
result = await client.catalog.search(
object_types=["ITEM"],
query={
"exact_query": {
"attribute_name": "category_id",
"attribute_value": cat_id,
}
},
)
result_json = json.loads(result.model_dump_json())
items = [
{
"id": obj["id"],
"name": obj.get("item_data", {}).get("name", "Unknown"),
"number": get_set_number(obj.get("item_data", {}).get("name", ""))
}
for obj in result_json.get("objects", [])
]
return items
async def test() -> None:
task = asyncio.create_task(acat2id(LEGO_CAT_ID)) # type: ignore
await asyncio.sleep(1) # Give the task some time to complete
result = await task
print("OBJECTS IN LEGO CATEGORY:")
for item in result:
print(f"ID: {item['id']}, Name: {item['name']}")
# eturn
# pass
"""
result = await sq.catalog.search_items(text_filter="71433", limit=1)
result_json = json.loads(result.model_dump_json())
name = result_json["items"][0]["item_data"]["name"]
print(f"Item name: {name}")"""
if __name__ == "__main__":
print(f"@[DEBUG] ENV:\t{ENVIRONMENT}")
print(f"@[DEBUG] CATID:\t{LEGO_CAT_ID}")
asyncio.run(test())
+20
View File
@@ -0,0 +1,20 @@
import subprocess
def generate(text, font="smblock", filter_effect=None, colors=None):
command = ["toilet", "-f", font]
if filter_effect:
command.extend(["-F", filter_effect])
if colors:
# Toilet uses a specific format for color arguments, e.g., --gay or --metal
# For general colors, you might need to use specific filters or direct color codes.
# This example focuses on filters that inherently use colors.
pass # More complex color handling would be needed for arbitrary colors
command.append(text)
try:
result = subprocess.run(command, capture_output=True, text=True, check=True)
return result.stdout
except subprocess.CalledProcessError as e:
return f"Error executing toilet: {e}\n{e.stderr}"
+60
View File
@@ -0,0 +1,60 @@
import os
import requests
import json
import dotenv
dotenv.load_dotenv()
USERNAME = os.getenv("BRICKSET_USERNAME")
PASS = os.getenv("BRICKSET_PASS")
BRICKSET_API_KEY = os.getenv("BRICKSET_API_KEY")
debug = False # Set to True to enable debug output
def login(username, password):
url = "https://brickset.com/api/v3.asmx/login"
headers = {"UserAgent": "Edderkop/0.1"}
params = {
"UserAgent": "Edderkop/0.1",
"username": username,
"password": password,
"apiKey": BRICKSET_API_KEY,
}
response = requests.get(url, params=params, headers=headers)
if debug:
print(f"[@DEBUG] Login response: {response.status_code} - {response.text}\n")
return response.json()
def get_set(set_number: int) -> dict:
login_response = login(USERNAME, PASS)
url = f"https://brickset.com/api/v3.asmx/getSets?apiKey={BRICKSET_API_KEY}&userHash={login_response["hash"]}&params={{'setNumber' : '{set_number}-1'}}"
response = requests.get(url)
if response.status_code == 200:
data = response.json()
our_set = {
"set_num": data["sets"][0]["number"],
"set_name": data["sets"][0]["name"],
"set_theme": data["sets"][0]["theme"],
"set_image": data["sets"][0]["image"]["thumbnailURL"],
"set_barcode": data["sets"][0]["barcode"]["UPC"],
"set_retailprice": round(data["sets"][0]["LEGOCom"]["US"]["retailPrice"]),
}
# add PW 10% b4 tax
our_set.update(
{"set_adjustredprice": our_set + ((10 / 100) * our_set["set_retailprice"])}
)
our_set["pw_name"] = (
f"{our_set['set_num']} - {our_set["set_theme"].upper()} {our_set['set_name']}"
)
if our_set["set_retailprice"] >= 110:
our_set["requires_manual_price"] = True
return our_set
elif response.status_code == 500:
raise Exception(
"Internal Server Error: Please check the API key and parameters."
)
else:
raise Exception("Unexpected response from the API.")
+221
View File
@@ -0,0 +1,221 @@
import aiosqlite
import logging
from typing import Optional, List, Dict, Any
import json
import os
logger = logging.getLogger(__name__)
# Get the absolute path to edderkop.db in the project root
DEFAULT_DB_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "edderkop.db"
)
if not os.path.exists(DEFAULT_DB_PATH):
raise FileNotFoundError(f"Database file not found at {DEFAULT_DB_PATH}")
print('Please run "src/scripts/EDDERKOP.SQL" to setup the database.')
os.exit(0)
class AsyncSchedulerDB:
"""
Asynchronous SQLite database manager for APScheduler.
Handles job storage, retrieval, and database operations using the main edderkop.db database.
"""
def __init__(self, db_path: str = DEFAULT_DB_PATH):
"""
Initialize the database manager.
Args:
db_path (str, optional): Path to the SQLite database file.
Defaults to edderkop.db in project root.
"""
self.db_path = db_path
self._initialized = False
logger.info(f"Using database at: {self.db_path}")
async def initialize(self):
"""Initialize the database schema if it doesn't exist."""
if self._initialized:
return
async with aiosqlite.connect(self.db_path) as db:
# Create apscheduler jobs table if it doesn't exist
await db.execute(
"""
CREATE TABLE IF NOT EXISTS apscheduler_jobs (
id VARCHAR(191) NOT NULL,
next_run_time FLOAT,
job_state BLOB NOT NULL,
PRIMARY KEY (id)
)
"""
)
await db.commit()
self._initialized = True
logger.info(f"Initialized scheduler database at {self.db_path}")
async def get_all_jobs(self) -> List[Dict[str, Any]]:
"""
Retrieve all jobs from the database.
Returns:
List[Dict[str, Any]]: List of job dictionaries
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM apscheduler_jobs ORDER BY next_run_time"
) as cursor:
rows = await cursor.fetchall()
return [
{
"id": row["id"],
"next_run_time": row["next_run_time"],
"job_state": json.loads(row["job_state"]),
}
for row in rows
]
async def get_job(self, job_id: str) -> Optional[Dict[str, Any]]:
"""
Retrieve a specific job by ID.
Args:
job_id (str): The ID of the job to retrieve
Returns:
Optional[Dict[str, Any]]: Job dictionary if found, None otherwise
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
async with db.execute(
"SELECT * FROM apscheduler_jobs WHERE id = ?", (job_id,)
) as cursor:
row = await cursor.fetchone()
if row:
return {
"id": row["id"],
"next_run_time": row["next_run_time"],
"job_state": json.loads(row["job_state"]),
}
return None
async def add_job(
self, job_id: str, next_run_time: float, job_state: Dict[str, Any]
) -> bool:
"""
Add a new job to the database.
Args:
job_id (str): Unique identifier for the job
next_run_time (float): Unix timestamp for next scheduled run
job_state (Dict[str, Any]): Job state dictionary
Returns:
bool: True if successful, False otherwise
"""
await self.initialize()
try:
async with aiosqlite.connect(self.db_path) as db:
await db.execute(
"INSERT INTO apscheduler_jobs (id, next_run_time, job_state) VALUES (?, ?, ?)",
(job_id, next_run_time, json.dumps(job_state)),
)
await db.commit()
return True
except aiosqlite.IntegrityError:
logger.error(f"Job with ID {job_id} already exists")
return False
except Exception as e:
logger.error(f"Error adding job: {e}")
return False
async def update_job(
self, job_id: str, next_run_time: float, job_state: Dict[str, Any]
) -> bool:
"""
Update an existing job in the database.
Args:
job_id (str): ID of the job to update
next_run_time (float): New next run time
job_state (Dict[str, Any]): Updated job state
Returns:
bool: True if successful, False if job not found
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(
"""UPDATE apscheduler_jobs
SET next_run_time = ?, job_state = ?
WHERE id = ?""",
(next_run_time, json.dumps(job_state), job_id),
)
await db.commit()
return cursor.rowcount > 0
async def remove_job(self, job_id: str) -> bool:
"""
Remove a job from the database.
Args:
job_id (str): ID of the job to remove
Returns:
bool: True if job was removed, False if not found
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute(
"DELETE FROM apscheduler_jobs WHERE id = ?", (job_id,)
)
await db.commit()
return cursor.rowcount > 0
async def remove_all_jobs(self) -> int:
"""
Remove all jobs from the database.
Returns:
int: Number of jobs removed
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
cursor = await db.execute("DELETE FROM apscheduler_jobs")
await db.commit()
return cursor.rowcount
async def get_due_jobs(self, now: float) -> List[Dict[str, Any]]:
"""
Get all jobs that are due to run.
Args:
now (float): Current timestamp
Returns:
List[Dict[str, Any]]: List of due jobs
"""
await self.initialize()
async with aiosqlite.connect(self.db_path) as db:
db.row_factory = aiosqlite.Row
async with db.execute(
"""SELECT * FROM apscheduler_jobs
WHERE next_run_time <= ?
ORDER BY next_run_time""",
(now,),
) as cursor:
rows = await cursor.fetchall()
return [
{
"id": row["id"],
"next_run_time": row["next_run_time"],
"job_state": json.loads(row["job_state"]),
}
for row in rows
]
+116
View File
@@ -0,0 +1,116 @@
import os
import requests
import json
import dotenv
from typing import Dict, Any
dotenv.load_dotenv()
class BricksetAPI:
"""Class for interacting with the Brickset API"""
def __init__(self, debug: bool = False):
self.username = os.getenv("BRICKSET_USERNAME")
self.password = os.getenv("BRICKSET_PASS")
self.api_key = os.getenv("BRICKSET_API_KEY")
self.debug = debug
self.headers = {"UserAgent": "Edderkop/0.1"}
self.user_hash = None
def login(self) -> Dict[str, Any]:
"""Login to Brickset API and get user hash"""
url = "https://brickset.com/api/v3.asmx/login"
params = {
"username": self.username,
"password": self.password,
"apiKey": self.api_key,
}
response = requests.get(url, params=params, headers=self.headers)
if self.debug:
print(f"[@DEBUG] Login response: {response.status_code}")
print(f"[@DEBUG] Response content: {response.text}")
try:
data = response.json()
self.user_hash = data.get("hash")
if not self.user_hash:
raise ValueError("No hash received in login response")
return data
except json.JSONDecodeError as e:
print(f"[@ERROR] Failed to decode JSON response: {e}")
print(f"[@DEBUG] Raw response: {response.text}")
raise
def get_set(self, set_number: str) -> Dict[str, Any]:
"""Get LEGO set information by set number"""
if not self.user_hash:
self.login()
url = "https://brickset.com/api/v3.asmx/getSets"
params = {
"apiKey": self.api_key,
"userHash": self.user_hash,
"params": json.dumps({"setNumber": f"{set_number}-1"}),
}
response = requests.get(url, params=params, headers=self.headers)
if self.debug:
print(f"[@DEBUG] Get set response: {response.status_code}")
print(f"[@DEBUG] Response content: {response.text}")
if response.status_code == 200:
try:
data = response.json()
if not data.get("sets"):
raise ValueError(f"No set data found for {set_number}")
set_data = data["sets"][0]
our_set = {
"set_num": set_data["number"],
"set_name": set_data["name"],
"set_image": set_data["image"]["thumbnailURL"],
"set_barcode": set_data["barcode"]["UPC"],
"set_retailprice": round(set_data["LEGOCom"]["US"]["retailPrice"]),
}
# Calculate adjusted price (10% markup)
retail_price = our_set["set_retailprice"]
our_set["set_adjustredprice"] = retail_price + (
(10 / 100) * retail_price
)
# Set Playwell inventory name
if our_set["theme"] == "Duplo":
our_set["pw_name"] = (
f"{our_set['set_num']} - DUPLO {our_set['set_name']}"
)
else:
our_set["pw_name"] = f"{our_set['set_num']} - {our_set['set_name']}"
return our_set
except json.JSONDecodeError as e:
# print(f"[@ERROR] Failed to decode JSON response: {e}")
# print(f"[@DEBUG] Raw response: {response.text}")
raise
except KeyError as e:
# print(f"[@ERROR] Missing expected field in response: {e}")
# print(f"[@DEBUG] Response data: {data}")
raise
elif response.status_code == 500:
raise Exception(
"Internal Server Error: Please check API key and parameters."
)
else:
raise Exception(f"Unexpected response: {response.status_code}")
def example():
client = BricksetAPI(debug=True)
try:
set_info = client.get_set("10931")
print(set_info)
except Exception as e:
print(f"Error: {e}")
+16
View File
@@ -0,0 +1,16 @@
import os
import dotenv
from square.environment import SquareEnvironment
dotenv.load_dotenv()
LEGO_CAT_ID = os.getenv("SQUARE_LEGO_CAT_ID")
if os.getenv("SQUARE_ENV") == "production":
SQUARE_APP_ID = os.getenv("PROD_SQUARE_APP_ID")
SQUARE_TOKEN = os.getenv("PROD_SQUARE_TOKEN")
environment = SquareEnvironment.PRODUCTION
else:
SQUARE_APP_ID = os.getenv("SANDBOX_SQUARE_APP_ID")
SQUARE_TOKEN = os.getenv("SANDBOX_SQUARE_TOKEN")
environment = SquareEnvironment.SANDBOX
+66
View File
@@ -0,0 +1,66 @@
import json
import get_sq_env
SQUARE_TOKEN = get_sq_env.SQUARE_TOKEN
LEGO_CAT_ID = get_sq_env.LEGO_CAT_ID
ENVIRONMENT = get_sq_env.environment
from square import Square
from square.environment import SquareEnvironment
def get_square_client():
"""Initialize and return a Square client."""
if not SQUARE_TOKEN:
raise ValueError("SQUARE_TOKEN environment variable is not set.")
square = Square(token=SQUARE_TOKEN, environment=ENVIRONMENT) # type: ignore
return square
client = get_square_client()
def get_itm(item_id: str):
"""Fetch an item from Square by its ID."""
result = client.catalog.search_items(text_filter=item_id, limit=1)
result_json = json.loads(result.model_dump_json())
ids = {
"item_id": result_json["items"][0]["id"],
"vari_id": result_json["items"][0]["item_data"]["variations"][0][
"item_variation_data"
]["item_id"],
}
item = {
"set_name": result_json["items"][0]["item_data"]["name"],
"set_price": result_json["items"][0]["item_data"]["variations"][0][
"item_variation_data"
]["price_money"],
"set_barcode": result_json["items"][0]["item_data"]["variations"][0][
"item_variation_data"
]["sku"],
}
return [ids, item]
def cat2id(cat_id: str):
"""Return a list of item IDs within the specified category ID."""
result = client.catalog.search(
object_types=["ITEM"],
query={
"exact_query": {
"attribute_name": "category_id",
"attribute_value": cat_id,
}
},
)
result_json = json.loads(result.model_dump_json())
item_ids = [obj["id"] for obj in result_json.get("objects", [])]
return item_ids
def test() -> None:
# all_lego_sets = cat2id(SQUARE_LEGO_CAT_ID) # type: ignore
# result = get_itm("71433")
# print(result)
pass
+7
View File
@@ -0,0 +1,7 @@
CREATE TABLE "REVIEW" (
"sq_object" TEXT NOT NULL UNIQUE,
"time_added" INTEGER,
"set_number" INTEGER,
"time_modified" INTEGER,
PRIMARY KEY("sq_object")
)
+113
View File
@@ -0,0 +1,113 @@
import asyncio
import aiohttp.web
import uuid
import os
from base64 import b64decode
import dotenv
dotenv.load_dotenv()
SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID")
from asyncio_throttle.throttler import Throttler
import asquare as sq
import abricksetapi as alego
def valid_uuid(val):
try:
uuid.UUID(str(val))
return True
except ValueError:
return False
async def fullsync():
"""'
full_db = await sq.acat2id(SQUARE_LEGO_CAT_ID)
for l_set in full_db:
l_setinfo = await alego.throttled_get_set(l_set)
await sq.update_itm(l_set, l_setinfo)"""
async def stop():
pass
async def priceonly():
pass
async def handle_post(request):
valid_cmd = ["RUNFULLSYNC", "STOP", "RUNPRICEONLY"]
async def handle_cmd(cmd: str):
if cmd not in valid_cmd:
return False
else:
return True
idp_key = request.headers.get("Idempotency-Key")
user_agent = request.headers.get("User-Agent")
if not idp_key and not valid_uuid(idp_key) and user_agent != "Kopling/0.1":
# 412 Precondition Failed
# The server does not meet one of the preconditions that the requester put on the request header fields.
return aiohttp.web.Response(status=412, text="412 Headers rejected!")
else:
data = await request.post()
reqdata = data.get(b64decode("CMD"))
if await handle_cmd(reqdata):
match reqdata:
case "RUNFULLSYNC":
print("Running full sync...")
await fullsync()
return True
case "STOP":
await stop()
return True
case "RUNPRICEONLY":
await priceonly()
return True
else:
return aiohttp.web.Response(status=400, text="400 Bad Request.")
result = {"message": f"Edderkop/0.1 received command: {reqdata}"}
return aiohttp.web.json_response(status=200, data=result)
app = aiohttp.web.Application()
app.router.add_post("/", handle_post)
import subprocess
def generate_toilet_banner(text, font="smblock", filter_effect=None, colors=None):
command = ["toilet", "-f", font]
if filter_effect:
command.extend(["-F", filter_effect])
if colors:
# Toilet uses a specific format for color arguments, e.g., --gay or --metal
# For general colors, you might need to use specific filters or direct color codes.
# This example focuses on filters that inherently use colors.
pass # More complex color handling would be needed for arbitrary colors
command.append(text)
try:
result = subprocess.run(command, capture_output=True, text=True, check=True)
return result.stdout
except subprocess.CalledProcessError as e:
return f"Error executing toilet: {e}\n{e.stderr}"
async def main():
throttler = Throttler(rate_limit=500, period=60) # 500 requests per minute
if __name__ == "__main__":
print(generate_toilet_banner("Edderkop v0.1"))
print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n")
aiohttp.web.run_app(app, port=7432)
+6
View File
@@ -0,0 +1,6 @@
import src.libsquare as sq
import src.bricksetapi as lego
oursq = sq.get_itm("71417")
ourlego = lego.get_set(71417)
print(f"{oursq}\n\n{ourlego}\n\n")
+39
View File
@@ -0,0 +1,39 @@
import asyncio
import aiohttp
from uuid import uuid4
from base64 import b64encode
import os
async def idp_request(cmd: str):
headers = {
"Idempotency-Key": f"{uuid4()}",
"Content-Type": "application/json",
"User-Agent": "Kopling/0.12",
}
print(f"[@DEBUG] Idempotency-Key: {headers['Idempotency-Key']}")
enc_cmd = b64encode(f"{cmd}".encode("utf-8")).decode("utf-8")
print("[@DEBUG] Encoded command:", enc_cmd)
print("[@DEBUG] Sending request..")
try:
async with aiohttp.ClientSession() as session:
async with session.post(
"http://localhost:7432", # Added http:// protocol
headers=headers,
json={"CMD": enc_cmd}, # Changed to json instead of data
) as resp:
print(f"[@DEBUG] Response status: {resp.status}")
print(f"[@DEBUG] Response body: {await resp.text()}")
except Exception as e:
print(f"\nRequest failed: {e}")
os._exit(-1)
async def main():
# Await the task instead of just creating it
await idp_request("RUNUPDATE")
if __name__ == "__main__":
asyncio.run(main())
+121
View File
@@ -0,0 +1,121 @@
import asyncio
import json
from aiohttp import web
from base64 import b64decode
from typing import Dict, Any
import uuid
from time import time
import dotenv
import os
import re
import logging
from pathlib import Path
Path("./logs/").mkdir(parents=True, exist_ok=True)
import banner
import get_sq_env
import asquare as sq
from abricksetapi import AsyncBricksetAPI
lego = AsyncBricksetAPI(debug=True)
dotenv.load_dotenv()
SQUARE_LEGO_CAT_ID = os.environ.get("SQUARE_LEGO_CAT_ID")
from asyncio_throttle.throttler import Throttler
logger = logging.getLogger("aiohttp.access")
logging.basicConfig(
filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG
)
UA = re.compile(r"^kopling\/?", re.IGNORECASE)
async def run_update():
set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID)
for l_set in set_list:
print(f"[@DEBUG]{l_set}")
l_setinfo = await lego.throttled_get_set(l_set["number"])
print(f"[@DEBUG]{l_setinfo}")
# await sq.update_itm(l_set, l_setinfo)
def valid_uuid(val):
try:
uuid.UUID(str(val))
return True
except ValueError:
return False
async def request_handler(request: web.Request) -> web.Response:
"""Handle incoming POST requests with command processing"""
try:
# Verify request method
if request.method != "POST":
logger.error(
f"{time()}\tInvalid request method from %s at ", request.remote
)
return web.Response(status=405, text="Method not allowed. Use POST.")
# Get JSON data from request
try:
data = await request.json()
except json.JSONDecodeError:
logger.error("Invalid JSON from %s", request.remote)
return web.Response(
status=400, text="{time()}\tInvalid JSON in request body"
)
# Validate Idempotency-Key header
if valid_uuid(request.headers.get("Idempotency-Key")) is False:
logger.error("{time()}\tInvalid Idempotency-Key from %s", request.remote)
return web.Response(status=412, text="412 Headers rejected!")
# Validate User-Agent header
if not re.match(UA, str(request.headers.get("User-Agent"))):
logger.error("{time()}\tInvalid User-Agent from %s", request.remote)
return web.Response(status=412, text="412 Headers rejected!")
# Validate command exists
if "CMD" not in data:
logger.error("{time()}\tMissing CMD field from %s", request.remote)
return web.Response(status=400, text="Missing CMD field in request")
# Decode and process command
try:
decoded_cmd = b64decode(data["CMD"]).decode("utf-8")
print(
f"""At: {time()}\nReceived command: {decoded_cmd}\nUser-Agent: {request.headers.get('User-Agent')}\nIdempotency-Key: {request.headers.get('Idempotency-Key')}"""
)
logging.info(
f"{time()}\tCommand: {decoded_cmd}\tFrom: {request.remote}\tUA: {request.headers.get('User-Agent')}\tIdempotency-Key: {request.headers.get('Idempotency-Key')}"
)
# Process the command
if decoded_cmd == "RUNUPDATE":
# Add your command handling logic here
await run_update()
response_data = {"status": "success", "message": "Update started"}
logging.info(f"{time()}\tUpdate command processed successfully")
return web.json_response(response_data)
else:
return web.Response(status=400, text=f"Unknown command: {decoded_cmd}")
except Exception as e:
print(f"Error processing command: {str(e)}")
return web.Response(status=500, text=f"Server error: {str(e)}")
except Exception as e:
print(f"Request handler error: {str(e)}")
return web.Response(status=500, text="Internal server error")
app = web.Application()
app.router.add_post("/", request_handler)
if __name__ == "__main__":
print(banner.generate("Edderkop v0.1"))
print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n")
web.run_app(app, host="localhost", port=7432)
+15
View File
@@ -0,0 +1,15 @@
import aiohttp
import asyncio
from asyncio_throttle.throttler import Throttler
throttler = Throttler(rate_limit=500, period=60) # 500 requests per minute
async def worker(throttler, session):
while True:
async with throttler:
async with session.get("http://example.com") as resp:
do_some_job_with(await resp.text())
await asyncio.sleep(0.05)