Spaces:
Running
Running
Upload main.py
Browse files
main.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import socket
|
| 2 |
socket.setdefaulttimeout(4000)
|
| 3 |
|
| 4 |
-
from fastapi import FastAPI, HTTPException
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from pydantic import BaseModel
|
| 7 |
from typing import Optional, Any, Dict, List
|
|
@@ -73,7 +73,11 @@ SHEET_UPDATE_DELAY_SECONDS = 10 # 10 second delay between sheet updates
|
|
| 73 |
SCAMMER_WEBHOOK_URL = os.getenv("SCAMMER_WEBHOOK_URL")
|
| 74 |
VALUE_WEBHOOK_URL = os.getenv("VALUE_WEBHOOK_URL")
|
| 75 |
DUPE_CHECK_WEBHOOK_URL = os.getenv("DUPE_CHECK_WEBHOOK_URL")
|
|
|
|
| 76 |
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
# --- Global Cache ---
|
| 79 |
cache = {
|
|
@@ -86,7 +90,9 @@ cache = {
|
|
| 86 |
"dupes": [], # List of duped usernames
|
| 87 |
"last_updated": None, # Timestamp of the last successful/partial update
|
| 88 |
"is_ready": False, # Is the cache populated at least once?
|
| 89 |
-
"service_available": True # Is the Google Sheets service reachable?
|
|
|
|
|
|
|
| 90 |
}
|
| 91 |
# --- Google Sheets Initialization ---
|
| 92 |
sheets_service = None # Initialize as None
|
|
@@ -1007,7 +1013,10 @@ async def startup_event():
|
|
| 1007 |
logger.warning("VALUE_WEBHOOK_URL environment variable not set. Value change notifications disabled.")
|
| 1008 |
if not DUPE_CHECK_WEBHOOK_URL:
|
| 1009 |
logger.warning("WEBHOOK_URL (for dupe checks) environment variable not set. Dupe check notifications disabled.")
|
|
|
|
|
|
|
| 1010 |
asyncio.create_task(update_cache_periodically())
|
|
|
|
| 1011 |
|
| 1012 |
|
| 1013 |
# --- API Endpoints ---
|
|
@@ -1176,4 +1185,182 @@ def health_check():
|
|
| 1176 |
return status_detail
|
| 1177 |
|
| 1178 |
# If we reach here, status is 'ok'
|
| 1179 |
-
return status_detail
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import socket
|
| 2 |
socket.setdefaulttimeout(4000)
|
| 3 |
|
| 4 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 5 |
from fastapi.middleware.cors import CORSMiddleware
|
| 6 |
from pydantic import BaseModel
|
| 7 |
from typing import Optional, Any, Dict, List
|
|
|
|
| 73 |
SCAMMER_WEBHOOK_URL = os.getenv("SCAMMER_WEBHOOK_URL")
|
| 74 |
VALUE_WEBHOOK_URL = os.getenv("VALUE_WEBHOOK_URL")
|
| 75 |
DUPE_CHECK_WEBHOOK_URL = os.getenv("DUPE_CHECK_WEBHOOK_URL")
|
| 76 |
+
VISITOR_WEBHOOK_URL = os.getenv("VISITOR_WEBHOOK_URL") # New webhook URL for visitor tracking
|
| 77 |
|
| 78 |
+
# Visitor tracking configuration
|
| 79 |
+
VISITOR_BATCH_INTERVAL_SECONDS = 60 # Send visitor webhooks every 1 minute
|
| 80 |
+
MAX_VISITORS_PER_WEBHOOK = 3 # Maximum number of visitors to include in a single webhook
|
| 81 |
|
| 82 |
# --- Global Cache ---
|
| 83 |
cache = {
|
|
|
|
| 90 |
"dupes": [], # List of duped usernames
|
| 91 |
"last_updated": None, # Timestamp of the last successful/partial update
|
| 92 |
"is_ready": False, # Is the cache populated at least once?
|
| 93 |
+
"service_available": True, # Is the Google Sheets service reachable?
|
| 94 |
+
"visitors": [], # Recent visitors for batched webhook notifications
|
| 95 |
+
"last_visitor_webhook": None # Timestamp of last visitor webhook
|
| 96 |
}
|
| 97 |
# --- Google Sheets Initialization ---
|
| 98 |
sheets_service = None # Initialize as None
|
|
|
|
| 1013 |
logger.warning("VALUE_WEBHOOK_URL environment variable not set. Value change notifications disabled.")
|
| 1014 |
if not DUPE_CHECK_WEBHOOK_URL:
|
| 1015 |
logger.warning("WEBHOOK_URL (for dupe checks) environment variable not set. Dupe check notifications disabled.")
|
| 1016 |
+
if not VISITOR_WEBHOOK_URL:
|
| 1017 |
+
logger.warning("VISITOR_WEBHOOK_URL environment variable not set. Visitor tracking notifications disabled.")
|
| 1018 |
asyncio.create_task(update_cache_periodically())
|
| 1019 |
+
asyncio.create_task(visitor_webhook_task()) # Start visitor webhook background task
|
| 1020 |
|
| 1021 |
|
| 1022 |
# --- API Endpoints ---
|
|
|
|
| 1185 |
return status_detail
|
| 1186 |
|
| 1187 |
# If we reach here, status is 'ok'
|
| 1188 |
+
return status_detail
|
| 1189 |
+
|
| 1190 |
+
# --- Visitor Tracking Model and Endpoint ---
|
| 1191 |
+
class VisitorData(BaseModel):
|
| 1192 |
+
device_type: str # "Laptop", "Desktop", "Mobile", etc.
|
| 1193 |
+
os: str # "Windows 10", "MacOS", etc.
|
| 1194 |
+
browser: str # "Chrome", "Firefox", etc.
|
| 1195 |
+
country: str # Country of origin
|
| 1196 |
+
path: str # URL path visited
|
| 1197 |
+
ip_address: Optional[str] = None # Optional IP address (for internal use only)
|
| 1198 |
+
|
| 1199 |
+
@app.post("/api/track-visitor")
|
| 1200 |
+
async def track_visitor(visitor: VisitorData, request: Request):
|
| 1201 |
+
"""Records visitor information and adds it to the visitor queue"""
|
| 1202 |
+
|
| 1203 |
+
# Get client IP (for internal tracking, not included in webhook)
|
| 1204 |
+
client_host = request.client.host if request.client else "Unknown"
|
| 1205 |
+
|
| 1206 |
+
# Format current time
|
| 1207 |
+
current_time = datetime.now()
|
| 1208 |
+
formatted_time = current_time.strftime("%A, %B %d, %Y %I:%M %p")
|
| 1209 |
+
short_time = current_time.strftime("%I:%M %p")
|
| 1210 |
+
|
| 1211 |
+
# Get country flag emoji
|
| 1212 |
+
flag_emoji = "๐" # Default globe emoji
|
| 1213 |
+
if visitor.country.lower() == "france":
|
| 1214 |
+
flag_emoji = "๐ซ๐ท"
|
| 1215 |
+
elif visitor.country.lower() == "pakistan":
|
| 1216 |
+
flag_emoji = "๐ต๐ฐ"
|
| 1217 |
+
elif visitor.country.lower() == "united states" or visitor.country.lower() == "usa":
|
| 1218 |
+
flag_emoji = "๐บ๐ธ"
|
| 1219 |
+
elif visitor.country.lower() == "united kingdom" or visitor.country.lower() == "uk":
|
| 1220 |
+
flag_emoji = "๐ฌ๐ง"
|
| 1221 |
+
elif visitor.country.lower() == "canada":
|
| 1222 |
+
flag_emoji = "๐จ๐ฆ"
|
| 1223 |
+
elif visitor.country.lower() == "australia":
|
| 1224 |
+
flag_emoji = "๐ฆ๐บ"
|
| 1225 |
+
elif visitor.country.lower() == "germany":
|
| 1226 |
+
flag_emoji = "๐ฉ๐ช"
|
| 1227 |
+
elif visitor.country.lower() == "india":
|
| 1228 |
+
flag_emoji = "๐ฎ๐ณ"
|
| 1229 |
+
elif visitor.country.lower() == "japan":
|
| 1230 |
+
flag_emoji = "๐ฏ๐ต"
|
| 1231 |
+
elif visitor.country.lower() == "china":
|
| 1232 |
+
flag_emoji = "๐จ๐ณ"
|
| 1233 |
+
# Add more country flags as needed
|
| 1234 |
+
|
| 1235 |
+
# Create visitor entry
|
| 1236 |
+
visitor_entry = {
|
| 1237 |
+
"device_type": visitor.device_type,
|
| 1238 |
+
"os": visitor.os,
|
| 1239 |
+
"browser": visitor.browser,
|
| 1240 |
+
"country": visitor.country,
|
| 1241 |
+
"flag_emoji": flag_emoji,
|
| 1242 |
+
"path": visitor.path,
|
| 1243 |
+
"timestamp": current_time,
|
| 1244 |
+
"formatted_time": short_time,
|
| 1245 |
+
"ip_address": client_host # Store IP internally but don't send in webhook
|
| 1246 |
+
}
|
| 1247 |
+
|
| 1248 |
+
# Add to visitor queue
|
| 1249 |
+
cache["visitors"].append(visitor_entry)
|
| 1250 |
+
logger.info(f"Recorded visitor from {visitor.country} using {visitor.browser} visiting {visitor.path}")
|
| 1251 |
+
|
| 1252 |
+
# Check if we should send a webhook immediately
|
| 1253 |
+
should_send_now = len(cache["visitors"]) >= MAX_VISITORS_PER_WEBHOOK
|
| 1254 |
+
|
| 1255 |
+
if cache["last_visitor_webhook"]:
|
| 1256 |
+
time_since_last = (current_time - cache["last_visitor_webhook"]).total_seconds()
|
| 1257 |
+
should_send_now = should_send_now or time_since_last >= VISITOR_BATCH_INTERVAL_SECONDS
|
| 1258 |
+
else:
|
| 1259 |
+
# First visitor, send after accumulating more or after interval
|
| 1260 |
+
should_send_now = False
|
| 1261 |
+
|
| 1262 |
+
# Send webhook if needed
|
| 1263 |
+
if should_send_now and VISITOR_WEBHOOK_URL:
|
| 1264 |
+
await send_visitor_webhook()
|
| 1265 |
+
|
| 1266 |
+
return {"status": "recorded", "webhook_queued": True}
|
| 1267 |
+
|
| 1268 |
+
async def send_visitor_webhook():
|
| 1269 |
+
"""Sends a webhook with accumulated visitor information"""
|
| 1270 |
+
if not cache["visitors"] or not VISITOR_WEBHOOK_URL:
|
| 1271 |
+
return False
|
| 1272 |
+
|
| 1273 |
+
try:
|
| 1274 |
+
current_time = datetime.now()
|
| 1275 |
+
|
| 1276 |
+
# Group visitors by device type for better organization
|
| 1277 |
+
visitors_by_device = {}
|
| 1278 |
+
for visitor in cache["visitors"]:
|
| 1279 |
+
key = f"{visitor['device_type']} - {visitor['os']} - {visitor['browser']}"
|
| 1280 |
+
if key not in visitors_by_device:
|
| 1281 |
+
visitors_by_device[key] = []
|
| 1282 |
+
visitors_by_device[key].append(visitor)
|
| 1283 |
+
|
| 1284 |
+
# Create webhook fields for each device group
|
| 1285 |
+
fields = []
|
| 1286 |
+
for device_key, visitors in visitors_by_device.items():
|
| 1287 |
+
visits_text = ""
|
| 1288 |
+
for v in visitors:
|
| 1289 |
+
# Format the path more specifically as shown in the example
|
| 1290 |
+
path_display = v['path']
|
| 1291 |
+
if "textures" in path_display.lower():
|
| 1292 |
+
path_display = "www.JailbreakValueCentral.com/textures"
|
| 1293 |
+
elif "tires" in path_display.lower() or "rims" in path_display.lower():
|
| 1294 |
+
path_display = "www.JailbreakValueCentral.net/tires"
|
| 1295 |
+
elif "calculator" in path_display.lower():
|
| 1296 |
+
path_display = "www.JailbreakValueCentral.net/value-calculator"
|
| 1297 |
+
elif "drift" in path_display.lower() or "particles" in path_display.lower():
|
| 1298 |
+
path_display = "www.JailbreakValueCentral.net/drift-particles"
|
| 1299 |
+
elif "furniture" in path_display.lower():
|
| 1300 |
+
path_display = "www.JailbreakValueCentral.net/furniture"
|
| 1301 |
+
elif "spoilers" in path_display.lower():
|
| 1302 |
+
path_display = "www.JailbreakValueCentral.net/spoilers"
|
| 1303 |
+
|
| 1304 |
+
# Format date like: Sunday, April 13, 2025 2:06 AM
|
| 1305 |
+
timestamp_str = v['timestamp'].strftime("%A, %B %d, %Y %I:%M %p")
|
| 1306 |
+
|
| 1307 |
+
# Format similar to the example in the screenshot
|
| 1308 |
+
visits_text += f"A User from {v['country']} {v['flag_emoji']} browsed:\n"
|
| 1309 |
+
visits_text += f"{timestamp_str}:\n"
|
| 1310 |
+
visits_text += f"{path_display}\n\n"
|
| 1311 |
+
|
| 1312 |
+
fields.append({
|
| 1313 |
+
"name": device_key,
|
| 1314 |
+
"value": visits_text.strip(),
|
| 1315 |
+
"inline": False
|
| 1316 |
+
})
|
| 1317 |
+
|
| 1318 |
+
# Create the embed with improved wording
|
| 1319 |
+
embed = {
|
| 1320 |
+
"title": "๐ Jailbreak Value Central Visitors",
|
| 1321 |
+
"description": "Check out the latest visitor activity from around the world! See what sections are trending right now.",
|
| 1322 |
+
"color": 3447003, # Blue
|
| 1323 |
+
"fields": fields,
|
| 1324 |
+
"timestamp": current_time.isoformat(),
|
| 1325 |
+
"footer": {
|
| 1326 |
+
"text": f"Sourced From JailbreakValueCentral.com โข Today at {current_time.strftime('%I:%M %p')}"
|
| 1327 |
+
},
|
| 1328 |
+
"thumbnail": {
|
| 1329 |
+
"url": "https://jailbreakvalue.net/logo.png" # Update with your actual logo URL
|
| 1330 |
+
}
|
| 1331 |
+
}
|
| 1332 |
+
|
| 1333 |
+
# Send the webhook notification
|
| 1334 |
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
|
| 1335 |
+
await send_webhook_notification(session, VISITOR_WEBHOOK_URL, embed)
|
| 1336 |
+
logger.info(f"Visitor tracking webhook sent with {len(cache['visitors'])} entries")
|
| 1337 |
+
|
| 1338 |
+
# Clear the visitor queue and update timestamp
|
| 1339 |
+
cache["visitors"] = []
|
| 1340 |
+
cache["last_visitor_webhook"] = current_time
|
| 1341 |
+
|
| 1342 |
+
return True
|
| 1343 |
+
|
| 1344 |
+
except Exception as e:
|
| 1345 |
+
logger.error(f"Error sending visitor tracking webhook: {e}")
|
| 1346 |
+
return False
|
| 1347 |
+
|
| 1348 |
+
# Background task to periodically send visitor webhooks
|
| 1349 |
+
async def visitor_webhook_task():
|
| 1350 |
+
"""Periodically sends visitor webhooks if there are any pending"""
|
| 1351 |
+
while True:
|
| 1352 |
+
try:
|
| 1353 |
+
current_time = datetime.now()
|
| 1354 |
+
|
| 1355 |
+
# Check if we have visitors and if enough time has passed
|
| 1356 |
+
if cache["visitors"] and VISITOR_WEBHOOK_URL:
|
| 1357 |
+
if not cache["last_visitor_webhook"] or \
|
| 1358 |
+
(current_time - cache["last_visitor_webhook"]).total_seconds() >= VISITOR_BATCH_INTERVAL_SECONDS:
|
| 1359 |
+
await send_visitor_webhook()
|
| 1360 |
+
|
| 1361 |
+
# Wait for next check
|
| 1362 |
+
await asyncio.sleep(60) # Check every minute
|
| 1363 |
+
|
| 1364 |
+
except Exception as e:
|
| 1365 |
+
logger.error(f"Error in visitor webhook background task: {e}")
|
| 1366 |
+
await asyncio.sleep(60) # Wait and retry
|