4 Commits
Author SHA1 Message Date
C 10537855f6 add initial web hook handling 2025-10-26 15:33:02 -07:00
C 3a5f477914 ignore things i forgot 2025-10-26 15:32:43 -07:00
C de9852e374 add arguments for host and port!!! yay!! 2025-10-23 14:42:41 -07:00
C 5440da0bdf BIG rename 2025-10-16 17:03:34 -07:00
19 changed files with 375 additions and 310 deletions

No files matched your search

+5 -2
View File
@@ -25,11 +25,14 @@ square-src.code-workspace
doc/pw-inventory/ doc/pw-inventory/
edderkop.db-journal studspider.db-journal
.venv .venv
*.db *.db
*.py[cod] *.py[cod]
.web .web
.states .states
assets/external/ assets/external/
__pycache__/ __pycache__/
win/
.github/
activate/
+4 -4
View File
@@ -1,4 +1,4 @@
# Edderkop # studspider
![Static Badge](https://img.shields.io/badge/python-3.13.7-blue?logo=python) ![Static Badge](https://img.shields.io/badge/code_style-black-black) ![Static Badge](https://img.shields.io/badge/license-MPL-orange) ![Static Badge](https://img.shields.io/badge/python-3.13.7-blue?logo=python) ![Static Badge](https://img.shields.io/badge/code_style-black-black) ![Static Badge](https://img.shields.io/badge/license-MPL-orange)
@@ -11,14 +11,14 @@ is a tool used to talk to brickset.com and a square inventory to pull lego set i
Clone the project Clone the project
```bash ```bash
git clone http://git.traps.moe/ph0sgene/edderkop.git git clone http://git.traps.moe/ph0sgene/studspider.git
git checkout uv git checkout uv
``` ```
Go to the project directory Go to the project directory
```bash ```bash
cd edderkop cd studspider
``` ```
Install dependencies Install dependencies
@@ -30,7 +30,7 @@ Install dependencies
Start the server Start the server
```bash ```bash
uvx run edderkop.py uvx run studspider.py
``` ```
+1 -1
View File
@@ -1,5 +1,5 @@
[project] [project]
name = "edderkop" name = "studpider"
version = "0.1.0" version = "0.1.0"
description = "" description = ""
authors = [{ name = "CF", email = "ph0sgene@pm.me" }] authors = [{ name = "CF", email = "ph0sgene@pm.me" }]
+10 -10
View File
@@ -1,19 +1,19 @@
from setuptools import setup from setuptools import setup
setup( setup(
name='Edderkop', name="studspider",
version='0.1', version="0.1",
description='Bricket + Square integration application', description="Bricket + Square integration application",
author='CF', author="CF",
author_email='chris-forst@pm.me', author_email="chris-forst@pm.me",
packages=['src'], packages=["src"],
package_dir={'': 'src'}, package_dir={"": "src"},
install_requires=[ install_requires=[
# List your project dependencies here # List your project dependencies here
], ],
entry_points={ entry_points={
'console_scripts': [ "console_scripts": [
'main=main:main', # Adjust this line based on your main function "main=main:main", # Adjust this line based on your main function
], ],
}, },
) )
+162 -161
View File
@@ -1,161 +1,162 @@
""" """
abricksetapi.py - Asynchronous Brickset API Client abricksetapi.py - Asynchronous Brickset API Client
This module provides asynchronous interactions with the Brickset API. This module provides asynchronous interactions with the Brickset API.
""" """
import asyncio import asyncio
import os import os
import aiohttp import aiohttp
import json import json
import dotenv import dotenv
from typing import Dict, Any from typing import Dict, Any
from . import throttler import throttler
Throttler = throttler.GlobalThrottler
Throttler = throttler.GlobalThrottler
dotenv.load_dotenv()
dotenv.load_dotenv()
class AsyncBricksetAPI:
"""Asynchronous client for the Brickset API""" class AsyncBricksetAPI:
"""Asynchronous client for the Brickset API"""
def __init__(self, debug: bool = False, logger=None):
self.username = os.getenv("BRICKSET_USERNAME") def __init__(self, debug: bool = False, logger=None):
self.password = os.getenv("BRICKSET_PASS") self.username = os.getenv("BRICKSET_USERNAME")
self.api_key = os.getenv("BRICKSET_API_KEY") self.password = os.getenv("BRICKSET_PASS")
self.debug = debug self.api_key = os.getenv("BRICKSET_API_KEY")
self.headers = {"UserAgent": "Edderkop/0.1"} self.debug = debug
self.throttler = Throttler(rate_limit=500, period=60) self.headers = {"UserAgent": "studspider/0.1"}
self.user_hash = None self.throttler = Throttler(rate_limit=500, period=60)
self.logger = logger self.user_hash = None
self.logger = logger
async def login(self) -> Dict[str, Any]:
"""Asynchronously authenticate with Brickset API""" async def login(self) -> Dict[str, Any]:
url = "https://brickset.com/api/v3.asmx/login" """Asynchronously authenticate with Brickset API"""
params = { url = "https://brickset.com/api/v3.asmx/login"
"username": self.username, params = {
"password": self.password, "username": self.username,
"apiKey": self.api_key, "password": self.password,
} "apiKey": self.api_key,
}
async with aiohttp.ClientSession() as session:
async with session.get( async with aiohttp.ClientSession() as session:
url, params=params, headers=self.headers # type: ignore async with session.get(
) as response: url, params=params, headers=self.headers # type: ignore
if self.debug: ) as response:
print(f"[@DEBUG] Login response status: {response.status}") if self.debug:
if self.logger: print(f"[@DEBUG] Login response status: {response.status}")
self.logger.debug(f"Login response status: {response.status}") if self.logger:
self.logger.debug(f"Login response status: {response.status}")
data = await response.read()
result = json.loads(data.decode("utf-8")) data = await response.read()
self.user_hash = result.get("hash") result = json.loads(data.decode("utf-8"))
self.user_hash = result.get("hash")
if self.logger:
self.logger.info("Successfully authenticated with Brickset API") if self.logger:
return result self.logger.info("Successfully authenticated with Brickset API")
return result
async def get_set(self, set_number: int) -> Dict[str, Any]:
""" async def get_set(self, set_number: int) -> Dict[str, Any]:
Fetch LEGO set information asynchronously """
Fetch LEGO set information asynchronously
Args:
set_number (str): LEGO set number Args:
set_number (str): LEGO set number
Returns:
Dict containing set information Returns:
""" Dict containing set information
if not self.user_hash: """
await self.login() if not self.user_hash:
await self.login()
url = "https://brickset.com/api/v3.asmx/getSets"
params = { url = "https://brickset.com/api/v3.asmx/getSets"
"apiKey": self.api_key, params = {
"userHash": self.user_hash, "apiKey": self.api_key,
"params": json.dumps({"setNumber": f"{set_number}-1"}), "userHash": self.user_hash,
} "params": json.dumps({"setNumber": f"{set_number}-1"}),
print(f"[@DEBUG] {params}...") }
print(f"[@DEBUG] {params}...")
async with aiohttp.ClientSession() as session:
async with session.get( async with aiohttp.ClientSession() as session:
url, params=params, headers=self.headers async with session.get(
) as response: url, params=params, headers=self.headers
if self.debug: ) as response:
print(f"[@DEBUG] Get set response status: {response.status}") if self.debug:
if self.logger: print(f"[@DEBUG] Get set response status: {response.status}")
self.logger.debug(f"Get set response status: {response.status}") if self.logger:
self.logger.debug(f"Get set response status: {response.status}")
if response.status == 200:
data = await response.read() if response.status == 200:
result = json.loads(data.decode("utf-8")) data = await response.read()
result = json.loads(data.decode("utf-8"))
if not result.get("sets"):
error_msg = f"No data found for set {set_number}" if not result.get("sets"):
if self.logger: error_msg = f"No data found for set {set_number}"
self.logger.error(error_msg) if self.logger:
raise ValueError(error_msg) self.logger.error(error_msg)
raise ValueError(error_msg)
if self.logger:
self.logger.info( if self.logger:
f"Successfully retrieved data for set {set_number}" self.logger.info(
) f"Successfully retrieved data for set {set_number}"
)
set_data = result["sets"][0]
our_set = { set_data = result["sets"][0]
"set_num": set_data["number"], our_set = {
"set_name": set_data["name"], "set_num": set_data["number"],
"set_theme": set_data["theme"], "set_name": set_data["name"],
"set_image": set_data["image"]["thumbnailURL"], "set_theme": set_data["theme"],
"set_barcode": set_data["barcode"]["UPC"], "set_image": set_data["image"]["thumbnailURL"],
"set_retailprice": round( "set_barcode": set_data["barcode"]["UPC"],
set_data["LEGOCom"]["US"]["retailPrice"] "set_retailprice": round(
), set_data["LEGOCom"]["US"]["retailPrice"]
} ),
}
# Calculate adjusted price (10% markup)
retail_price = our_set["set_retailprice"] # Calculate adjusted price (10% markup)
our_set["set_adjustredprice"] = retail_price + ( retail_price = our_set["set_retailprice"]
(10 / 100) * retail_price our_set["set_adjustredprice"] = retail_price + (
) (10 / 100) * retail_price
)
# Set PW inventory name with theme
our_set["pw_name"] = ( # Set PW inventory name with theme
f"{our_set['set_num']} - {our_set['set_theme'].upper()} {our_set['set_name']}" 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: # Flag for manual price review if needed
our_set["requires_manual_price"] = True if our_set["set_retailprice"] >= 110:
our_set["requires_manual_price"] = True
return our_set
return our_set
elif response.status == 500:
error_msg = ( elif response.status == 500:
"Internal Server Error: Please check API key and parameters." error_msg = (
) "Internal Server Error: Please check API key and parameters."
if self.logger: )
self.logger.error(error_msg) if self.logger:
raise Exception(error_msg) self.logger.error(error_msg)
else: raise Exception(error_msg)
error_msg = f"Unexpected response: {response.status}" else:
if self.logger: error_msg = f"Unexpected response: {response.status}"
self.logger.error(error_msg) if self.logger:
raise Exception(error_msg) self.logger.error(error_msg)
raise Exception(error_msg)
async def throttled_get_set(self, set_number: str) -> Dict[str, Any]:
"""Get set information with rate limiting""" async def throttled_get_set(self, set_number: str) -> Dict[str, Any]:
async with self.throttler: """Get set information with rate limiting"""
return await self.get_set(set_number) async with self.throttler:
return await self.get_set(set_number)
async def main():
"""Example usage""" async def main():
client = AsyncBricksetAPI(debug=True) """Example usage"""
try: client = AsyncBricksetAPI(debug=True)
set_info = await client.throttled_get_set("10349") try:
print(json.dumps(set_info, indent=2)) set_info = await client.throttled_get_set("10349")
except Exception as e: print(json.dumps(set_info, indent=2))
print(f"Error: {e}") except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
asyncio.run(main()) if __name__ == "__main__":
asyncio.run(main())
+10 -6
View File
@@ -6,9 +6,9 @@ import uuid
import re import re
from time import time from time import time
from . import db import db
from . import get_sq_env import get_sq_env
def get_set_number(text: str) -> int: def get_set_number(text: str) -> int:
@@ -106,10 +106,14 @@ async def acat2id(cat_id: str):
{ {
"id": obj["id"], "id": obj["id"],
"name": obj.get("item_data", {}).get("name", "Unknown"), "name": obj.get("item_data", {}).get("name", "Unknown"),
"number": None if not obj.get("item_data", {}).get("name", "") else ( "number": (
get_set_number(obj.get("item_data", {}).get("name", "")) None
if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", "")) if not obj.get("item_data", {}).get("name", "")
else None else (
get_set_number(obj.get("item_data", {}).get("name", ""))
if re.findall(r"\d{5}", obj.get("item_data", {}).get("name", ""))
else None
)
), ),
"updated_at": obj.get("updated_at"), "updated_at": obj.get("updated_at"),
"version": obj.get("version"), "version": obj.get("version"),
+2 -2
View File
@@ -14,9 +14,9 @@ debug = False # Set to True to enable debug output
def login(username, password): def login(username, password):
url = "https://brickset.com/api/v3.asmx/login" url = "https://brickset.com/api/v3.asmx/login"
headers = {"UserAgent": "Edderkop/0.1"} headers = {"UserAgent": "studspider/0.1"}
params = { params = {
"UserAgent": "Edderkop/0.1", "UserAgent": "studspider/0.1",
"username": username, "username": username,
"password": password, "password": password,
"apiKey": BRICKSET_API_KEY, "apiKey": BRICKSET_API_KEY,
+15 -7
View File
@@ -1,12 +1,15 @@
import aiosqlite import aiosqlite
from time import time from time import time
time = int(time()) # we dont need no stinkin floats time = int(time()) # we dont need no stinkin floats
# db = await aiosqlite.connect("edderkop.db") # type: ignore # db = await aiosqlite.connect("studspider.db") # type: ignore
async def close(db): async def close(db):
await db.close() await db.close()
async def fetch(table, sqobj): async def fetch(table, sqobj):
""" """
Asynchronously fetch a record from the REVIEW table by sq_object. Asynchronously fetch a record from the REVIEW table by sq_object.
@@ -16,13 +19,16 @@ async def fetch(table, sqobj):
Returns: Returns:
dict or None: The fetched record as a dictionary, or None if not found. dict or None: The fetched record as a dictionary, or None if not found.
""" """
async with aiosqlite.connect("edderkop.db") as db: # type: ignore async with aiosqlite.connect("studspider.db") as db: # type: ignore
async with db.execute(f"SELECT * FROM {table} WHERE sq_object = {sqobj}") as cursor: async with db.execute(
f"SELECT * FROM {table} WHERE sq_object = {sqobj}"
) as cursor:
async for row in cursor: async for row in cursor:
return row return row
return None return None
async def upsert(table: str, data: dict, unique_key: str) -> bool: async def upsert(table: str, data: dict, unique_key: str) -> bool:
""" """
Asynchronously insert or update a record in the specified table. Asynchronously insert or update a record in the specified table.
@@ -37,9 +43,11 @@ async def upsert(table: str, data: dict, unique_key: str) -> bool:
""" """
print(f"Upserting into {table}: {data}") print(f"Upserting into {table}: {data}")
columns = ', '.join(data.keys()) columns = ", ".join(data.keys())
placeholders = ', '.join('?' * len(data)) placeholders = ", ".join("?" * len(data))
updates = ', '.join(f"{col}=excluded.{col}" for col in data.keys() if col != unique_key) updates = ", ".join(
f"{col}=excluded.{col}" for col in data.keys() if col != unique_key
)
sql = f""" sql = f"""
INSERT INTO {table} ({columns}) INSERT INTO {table} ({columns})
@@ -47,7 +55,7 @@ async def upsert(table: str, data: dict, unique_key: str) -> bool:
ON CONFLICT({unique_key}) DO UPDATE SET ON CONFLICT({unique_key}) DO UPDATE SET
{updates}; {updates};
""" """
async with aiosqlite.connect("edderkop.db") as db: # type: ignore async with aiosqlite.connect("studspider.db") as db: # type: ignore
async with db.execute(sql, tuple(data.values())) as cursor: async with db.execute(sql, tuple(data.values())) as cursor:
await db.commit() await db.commit()
return True return True
+1 -1
View File
@@ -15,7 +15,7 @@ class BricksetAPI:
self.password = os.getenv("BRICKSET_PASS") self.password = os.getenv("BRICKSET_PASS")
self.api_key = os.getenv("BRICKSET_API_KEY") self.api_key = os.getenv("BRICKSET_API_KEY")
self.debug = debug self.debug = debug
self.headers = {"UserAgent": "Edderkop/0.1"} self.headers = {"UserAgent": "studspider/0.1"}
self.user_hash = None self.user_hash = None
def login(self) -> Dict[str, Any]: def login(self) -> Dict[str, Any]:
+5 -5
View File
@@ -6,21 +6,21 @@ import os
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Get the absolute path to edderkop.db in the project root # Get the absolute path to studspider.db in the project root
DEFAULT_DB_PATH = os.path.join( DEFAULT_DB_PATH = os.path.join(
os.path.dirname(os.path.dirname(__file__)), "edderkop.db" os.path.dirname(os.path.dirname(__file__)), "studspider.db"
) )
if not os.path.exists(DEFAULT_DB_PATH): if not os.path.exists(DEFAULT_DB_PATH):
raise FileNotFoundError(f"Database file not found at {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.') print('Please run "src/scripts/studspider.SQL" to setup the database.')
os.exit(0) os.exit(0)
class AsyncSchedulerDB: class AsyncSchedulerDB:
""" """
Asynchronous SQLite database manager for APScheduler. Asynchronous SQLite database manager for APScheduler.
Handles job storage, retrieval, and database operations using the main edderkop.db database. Handles job storage, retrieval, and database operations using the main studspider.db database.
""" """
def __init__(self, db_path: str = DEFAULT_DB_PATH): def __init__(self, db_path: str = DEFAULT_DB_PATH):
@@ -29,7 +29,7 @@ class AsyncSchedulerDB:
Args: Args:
db_path (str, optional): Path to the SQLite database file. db_path (str, optional): Path to the SQLite database file.
Defaults to edderkop.db in project root. Defaults to studspider.db in project root.
""" """
self.db_path = db_path self.db_path = db_path
self._initialized = False self._initialized = False
+2 -2
View File
@@ -74,7 +74,7 @@ async def handle_post(request):
return True return True
else: else:
return aiohttp.web.Response(status=400, text="400 Bad Request.") return aiohttp.web.Response(status=400, text="400 Bad Request.")
result = {"message": f"Edderkop/0.1 received command: {reqdata}"} result = {"message": f"studspider/0.1 received command: {reqdata}"}
return aiohttp.web.json_response(status=200, data=result) return aiohttp.web.json_response(status=200, data=result)
@@ -108,6 +108,6 @@ async def main():
if __name__ == "__main__": if __name__ == "__main__":
print(generate_toilet_banner("Edderkop v0.1")) print(generate_toilet_banner("studspider v0.1"))
print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n")
aiohttp.web.run_app(app, port=7432) aiohttp.web.run_app(app, port=7432)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

-2
View File
@@ -1,2 +0,0 @@
reflex==0.8.14.post1
-9
View File
@@ -1,9 +0,0 @@
import reflex as rx
config = rx.Config(
app_name="web",
plugins=[
rx.plugins.SitemapPlugin(),
rx.plugins.TailwindV4Plugin(),
]
)
View File
Whitespace-only changes.
-36
View File
@@ -1,36 +0,0 @@
"""Welcome to Reflex! This file outlines the steps to create a basic app."""
import reflex as rx
from rxconfig import config
class State(rx.State):
"""The app state."""
def index() -> rx.Component:
# Welcome Page (Index)
return rx.container(
rx.color_mode.button(position="top-right"),
rx.vstack(
rx.heading("Welcome to Reflex!", size="9"),
rx.text(
"Get started by editing ",
rx.code(f"{config.app_name}/{config.app_name}.py"),
size="5",
),
rx.link(
rx.button("Check out our docs!"),
href="https://reflex.dev/docs/getting-started/introduction/",
is_external=True,
),
spacing="5",
justify="center",
min_height="85vh",
),
)
app = rx.App()
app.add_page(index)
+111 -15
View File
@@ -1,3 +1,4 @@
import argparse
import asyncio import asyncio
import json import json
from aiohttp import web from aiohttp import web
@@ -7,16 +8,19 @@ import uuid
import dotenv import dotenv
import os import os
import re import re
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from pathlib import Path from pathlib import Path
print("Creating log directory if it doesn't exist...") print("Creating log directory if it doesn't exist...")
os.makedirs("./logs/", exist_ok=True) os.makedirs("./logs/", exist_ok=True)
import banner import banner
import get_sq_env import get_sq_env
from time import time from time import time
time = int(time()) time = int(time())
import asquare as sq import asquare as sq
@@ -31,15 +35,15 @@ from asyncio_throttle.throttler import Throttler
# set up logger # set up logger
print("Setting up logger...") print("Setting up logger...")
logger = logging.getLogger("edderkop") logger = logging.getLogger("studspider")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
handler = RotatingFileHandler("./logs/edderkop.log", maxBytes=125000, backupCount=5) handler = RotatingFileHandler("./logs/studspider.log", maxBytes=125000, backupCount=5)
logger.addHandler(handler) logger.addHandler(handler)
lego = AsyncBricksetAPI(debug=True, logger=logger) lego = AsyncBricksetAPI(debug=True, logger=logger)
# sq = AsyncSquareClient(logger=logger) # sq = AsyncSquareClient(logger=logger)
# logging.basicConfig( # logging.basicConfig(
# filename="./logs/edderkop.log", encoding="utf-8", level=logging.DEBUG # filename="./logs/studspider.log", encoding="utf-8", level=logging.DEBUG
# ) # )
print("Setting up rate limiter...") print("Setting up rate limiter...")
print("Doing RE things...") print("Doing RE things...")
@@ -47,10 +51,10 @@ UA = re.compile(r"^kopling\/?", re.IGNORECASE)
async def run_update(): async def run_update():
set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore set_list = await sq.acat2id(SQUARE_LEGO_CAT_ID) # type: ignore
#print(f"[@DEBUG]\t {set_list}") # type: ignore # print(f"[@DEBUG]\t {set_list}") # type: ignore
for l_set in set_list: for l_set in set_list:
#print(f"[@DEBUG]{l_set}") # print(f"[@DEBUG]{l_set}")
try: try:
l_setinfo = await lego.throttled_get_set(l_set["number"]) l_setinfo = await lego.throttled_get_set(l_set["number"])
print(f"[@DEBUG] Found {l_setinfo["pw_name"]}") print(f"[@DEBUG] Found {l_setinfo["pw_name"]}")
@@ -58,8 +62,8 @@ async def run_update():
print(f"Error fetching set\t: {l_set}: {str(e)}") print(f"Error fetching set\t: {l_set}: {str(e)}")
continue continue
#print(f"[@DEBUG]{l_setinfo}") # print(f"[@DEBUG]{l_setinfo}")
#await sq.update_itm(l_set, l_setinfo) # await sq.update_itm(l_set, l_setinfo)
"""for l_set in set_list: """for l_set in set_list:
print(f"[@DEBUG]{l_set}") print(f"[@DEBUG]{l_set}")
try: try:
@@ -80,14 +84,53 @@ def valid_uuid(val):
return False return False
async def hook_handler(request: web.Request) -> web.Response:
"""Handle incoming JSON webhook POSTs on /hook endpoint"""
try:
# Verify request method
if request.method != "POST":
logger.error(
f"{time}\tInvalid request method to /hook from %s", 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 to /hook from %s", request.remote)
return web.Response(status=400, text="Invalid JSON in request body")
# Log the received webhook
print(f"[@DEBUG] Webhook received at {time} from {request.remote}")
logger.info(
f"{time}\tWebhook received from {request.remote}: {json.dumps(data)}"
)
# Process the webhook data here
# TODO: Add your webhook processing logic
response_data = {
"status": "success",
"message": "Webhook received",
"timestamp": time,
"data_received": data,
}
return web.json_response(response_data)
except Exception as e:
logger.error(f"Webhook handler error: {str(e)}")
print(f"Webhook handler error: {str(e)}")
return web.Response(status=500, text="Internal server error")
async def request_handler(request: web.Request) -> web.Response: async def request_handler(request: web.Request) -> web.Response:
"""Handle incoming POST requests with command processing""" """Handle incoming POST requests with command processing"""
try: try:
# Verify request method # Verify request method
if request.method != "POST": if request.method != "POST":
logger.error( logger.error(f"{time}\tInvalid request method from %s at ", request.remote)
f"{time}\tInvalid request method from %s at ", request.remote
)
return web.Response(status=405, text="Method not allowed. Use POST.") return web.Response(status=405, text="Method not allowed. Use POST.")
# Get JSON data from request # Get JSON data from request
@@ -138,12 +181,65 @@ async def request_handler(request: web.Request) -> web.Response:
print(f"Request handler error: {str(e)}") print(f"Request handler error: {str(e)}")
return web.Response(status=500, text="Internal server error") return web.Response(status=500, text="Internal server error")
print("Starting web server...") print("Starting web server...")
os.system('cls' if os.name == 'nt' else 'clear') os.system("cls" if os.name == "nt" else "clear")
app = web.Application() app = web.Application()
app.router.add_post("/", request_handler) app.router.add_post("/api", request_handler)
app.router.add_post("/hook", hook_handler)
parser = argparse.ArgumentParser(description="studspider")
parser.add_argument(
"--host",
type=str,
default="localhost",
help="IP address to listen on. default: localhost",
)
parser.add_argument(
"--port", type=int, default=7432, help="Port to listen on. default: 7432"
)
parser.add_argument(
"--hook-port",
type=int,
default=8080,
help="Optional separate port for webhook endpoint. default: 8080",
)
args = parser.parse_args()
if __name__ == "__main__": if __name__ == "__main__":
print(banner.generate("Edderkop v0.1")) print(banner.generate("studspider v0.1"))
print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n") print("Built for PLAY-WELL TEKNOLOGIES by contact@traps.moe\n\n")
web.run_app(app, host="localhost", port=7432)
# If hook-port is specified, run a separate server for webhooks
if args.hook_port:
print(f"Starting main server on {args.host}:{args.port}")
print(f"Starting webhook server on {args.host}:{args.hook_port}")
# Create separate app for webhook
hook_app = web.Application()
hook_app.router.add_post("/hook", hook_handler)
# Run both servers concurrently
async def run_servers():
runner1 = web.AppRunner(app)
await runner1.setup()
site1 = web.TCPSite(runner1, args.host, args.port)
runner2 = web.AppRunner(hook_app)
await runner2.setup()
site2 = web.TCPSite(runner2, args.host, args.hook_port)
await site1.start()
await site2.start()
print(f"Main server running on http://{args.host}:{args.port}")
print(f"Webhook server running on http://{args.host}:{args.hook_port}/hook")
# Keep running
await asyncio.Event().wait()
asyncio.run(run_servers())
else:
print(f"Server running on {args.host}:{args.port}")
print(f"Webhook endpoint: http://{args.host}:{args.port}/hook")
web.run_app(app, host=args.host, port=args.port)
+14 -14
View File
@@ -1,14 +1,14 @@
import src.test_server import src.test_server
class edderkop: class Studspider:
def __init__(self): def __init__(self):
self.name = "Edderkop" self.name = "Studspider"
self.version = "0.0.1" self.version = "0.0.1"
self.description = """ self.description = """
Edderkop is an application designed to interact with the Brickset API and directly with Square. studspider is an application designed to interact with the Brickset API and directly with Square.
It allows users to search for items, manage collections, and perform various operations related to bricksets. It allows users to search for items, manage collections, and perform various operations related to bricksets.
""" """
def run(self): def run(self):
print(f"{self.name} v{self.version} is running.") print(f"{self.name} v{self.version} is running.")
Generated
+33 -33
View File
@@ -239,39 +239,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
] ]
[[package]]
name = "edderkop"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "aiohttp" },
{ name = "aiosqlite" },
{ name = "apscheduler" },
{ name = "asyncio-throttle" },
{ name = "bs4" },
{ name = "dotenv" },
{ name = "pyinstaller" },
{ name = "pytest" },
{ name = "reflex" },
{ name = "requests" },
{ name = "squareup" },
]
[package.metadata]
requires-dist = [
{ name = "aiohttp", specifier = ">=3.12.13,<4.0.0" },
{ name = "aiosqlite", specifier = ">=0.21.0,<0.22.0" },
{ name = "apscheduler", specifier = ">=3.11.0,<4.0.0" },
{ name = "asyncio-throttle", specifier = ">=1.0.2,<2.0.0" },
{ name = "bs4", specifier = ">=0.0.2" },
{ name = "dotenv", specifier = ">=0.9.9,<0.10.0" },
{ name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" },
{ name = "pytest", specifier = ">=8.4.2,<9.0.0" },
{ name = "reflex", specifier = ">=0.8.14.post1" },
{ name = "requests", specifier = ">=2.32.4,<3.0.0" },
{ name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" },
]
[[package]] [[package]]
name = "frozenlist" name = "frozenlist"
version = "1.7.0" version = "1.7.0"
@@ -971,6 +938,39 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" },
] ]
[[package]]
name = "studpider"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "aiohttp" },
{ name = "aiosqlite" },
{ name = "apscheduler" },
{ name = "asyncio-throttle" },
{ name = "bs4" },
{ name = "dotenv" },
{ name = "pyinstaller" },
{ name = "pytest" },
{ name = "reflex" },
{ name = "requests" },
{ name = "squareup" },
]
[package.metadata]
requires-dist = [
{ name = "aiohttp", specifier = ">=3.12.13,<4.0.0" },
{ name = "aiosqlite", specifier = ">=0.21.0,<0.22.0" },
{ name = "apscheduler", specifier = ">=3.11.0,<4.0.0" },
{ name = "asyncio-throttle", specifier = ">=1.0.2,<2.0.0" },
{ name = "bs4", specifier = ">=0.0.2" },
{ name = "dotenv", specifier = ">=0.9.9,<0.10.0" },
{ name = "pyinstaller", specifier = ">=6.14.1,<7.0.0" },
{ name = "pytest", specifier = ">=8.4.2,<9.0.0" },
{ name = "reflex", specifier = ">=0.8.14.post1" },
{ name = "requests", specifier = ">=2.32.4,<3.0.0" },
{ name = "squareup", specifier = ">=43.0.0.20250618,<44.0.0.0" },
]
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.15.0" version = "4.15.0"