Update app.py
Browse files
app.py
CHANGED
|
@@ -3,94 +3,22 @@ import time
|
|
| 3 |
import os
|
| 4 |
import random
|
| 5 |
import requests
|
|
|
|
| 6 |
from flask import Flask
|
| 7 |
-
import
|
|
|
|
| 8 |
|
| 9 |
# ==================== CONFIGURATION ====================
|
| 10 |
-
# Read environment variables set in Hugging Face Secrets
|
| 11 |
SCRATCH_USER = os.environ.get("SCRATCH_USER")
|
| 12 |
SCRATCH_PASS = os.environ.get("SCRATCH_PASS")
|
| 13 |
PROJECT_ID = int(os.environ.get("PROJECT_ID", 0))
|
| 14 |
AI_URL = "https://16dvnk-spirit-kings-ai.hf.space/generate"
|
| 15 |
|
| 16 |
-
#
|
| 17 |
-
|
| 18 |
-
"
|
| 19 |
-
"
|
| 20 |
-
"
|
| 21 |
-
"http://8.243.126.136:8801",
|
| 22 |
-
"http://45.236.66.163:8520",
|
| 23 |
-
"http://103.155.167.62:8080",
|
| 24 |
-
"http://119.92.71.40:8080",
|
| 25 |
-
"http://103.144.146.5:8080",
|
| 26 |
-
"http://36.91.220.132:8080",
|
| 27 |
-
"http://41.254.48.192:1976",
|
| 28 |
-
"http://45.169.169.14:8085",
|
| 29 |
-
"http://103.3.246.71:3128",
|
| 30 |
-
"http://185.225.40.236:8080",
|
| 31 |
-
"http://164.90.151.28:3128",
|
| 32 |
-
"http://177.234.211.31:999",
|
| 33 |
-
"http://38.156.73.61:8080",
|
| 34 |
-
"http://103.68.214.135:8181",
|
| 35 |
-
"http://169.255.77.150:9999",
|
| 36 |
-
"http://35.225.22.61:80",
|
| 37 |
-
"http://103.125.174.233:7777",
|
| 38 |
-
"http://103.171.83.115:7777",
|
| 39 |
-
"http://84.247.149.172:3128",
|
| 40 |
-
"http://51.79.135.131:8080",
|
| 41 |
-
"http://186.148.184.18:999",
|
| 42 |
-
"http://116.80.64.157:7777",
|
| 43 |
-
"http://116.80.64.158:7777",
|
| 44 |
-
"http://116.80.48.217:7777",
|
| 45 |
-
"http://116.0.54.25:8080",
|
| 46 |
-
"http://116.80.95.238:7777",
|
| 47 |
-
"http://142.171.157.207:3128",
|
| 48 |
-
"http://154.119.80.27:3128",
|
| 49 |
-
"http://103.82.93.98:3128",
|
| 50 |
-
"http://104.248.81.109:3128",
|
| 51 |
-
"http://194.163.183.242:3128",
|
| 52 |
-
"http://49.144.17.15:8082",
|
| 53 |
-
"http://103.170.22.44:8080",
|
| 54 |
-
"http://160.22.234.4:1111",
|
| 55 |
-
"http://116.80.49.156:3172",
|
| 56 |
-
"http://116.80.49.163:3172",
|
| 57 |
-
"http://116.80.96.95:3172",
|
| 58 |
-
"http://116.80.96.100:3172",
|
| 59 |
-
"http://116.80.96.107:3172",
|
| 60 |
-
"http://116.80.65.77:3172",
|
| 61 |
-
"http://45.136.198.40:3128",
|
| 62 |
-
"http://130.61.139.145:3128",
|
| 63 |
-
"http://116.80.63.64:7777",
|
| 64 |
-
"http://150.95.26.146:7080",
|
| 65 |
-
"http://116.80.96.103:3172",
|
| 66 |
-
"http://20.2.83.243:3128",
|
| 67 |
-
"http://165.232.146.249:3128",
|
| 68 |
-
"http://199.68.217.2:3128",
|
| 69 |
-
"http://116.80.65.75:3172",
|
| 70 |
-
"http://116.80.65.78:3172",
|
| 71 |
-
"http://116.80.65.81:3172",
|
| 72 |
-
"http://116.80.65.85:3172",
|
| 73 |
-
"http://45.119.85.216:3128",
|
| 74 |
-
"http://192.232.48.2:8181",
|
| 75 |
-
"http://116.80.65.82:3172",
|
| 76 |
-
"http://95.213.217.168:52004",
|
| 77 |
-
"http://174.140.109.250:3128",
|
| 78 |
-
"http://185.118.51.163:3128",
|
| 79 |
-
"http://46.175.148.17:2040",
|
| 80 |
-
"http://42.96.16.158:1311",
|
| 81 |
-
"http://103.30.31.209:32323",
|
| 82 |
-
"http://45.131.6.46:80",
|
| 83 |
-
"http://172.67.70.113:80",
|
| 84 |
-
"http://103.21.244.88:80",
|
| 85 |
-
"http://103.21.244.43:80",
|
| 86 |
-
"http://103.21.244.189:80",
|
| 87 |
-
"http://103.21.244.186:80",
|
| 88 |
-
"http://103.21.244.165:80",
|
| 89 |
-
"http://103.21.244.150:80",
|
| 90 |
-
"http://103.21.244.210:80",
|
| 91 |
-
"http://173.245.49.114:80",
|
| 92 |
-
"http://103.21.244.12:80",
|
| 93 |
-
"http://178.130.47.129:1082"
|
| 94 |
]
|
| 95 |
|
| 96 |
# ==================== ENCODING/DECODING ====================
|
|
@@ -123,81 +51,81 @@ def decode_text(encoded):
|
|
| 123 |
i += 1
|
| 124 |
return "".join(result)
|
| 125 |
|
| 126 |
-
# ====================
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
self.
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
self.
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
response = requests.post(AI_URL, json={"prompt": prompt}, timeout=30)
|
| 168 |
-
if response.status_code == 200:
|
| 169 |
-
return response.json().get("response", "Sorry, no response.")
|
| 170 |
-
except:
|
| 171 |
-
pass
|
| 172 |
-
return "Error: Could not reach AI service."
|
| 173 |
|
| 174 |
# ==================== BRIDGE WORKER ====================
|
| 175 |
def bridge_worker():
|
| 176 |
-
print("[BRIDGE] Starting bridge
|
| 177 |
-
|
|
|
|
| 178 |
while True:
|
| 179 |
try:
|
| 180 |
print("[BRIDGE] Logging into Scratch...")
|
| 181 |
-
|
| 182 |
-
print("[BRIDGE] Login successful. Connecting to
|
| 183 |
-
|
| 184 |
-
print("[BRIDGE]
|
| 185 |
-
cloud = project.connect_cloud_variables()
|
| 186 |
-
print("[BRIDGE] Cloud connection established. Waiting for prompts...")
|
| 187 |
|
| 188 |
last_prompt = ""
|
| 189 |
while True:
|
| 190 |
-
|
| 191 |
-
current =
|
|
|
|
|
|
|
| 192 |
if current != last_prompt and current != "0":
|
| 193 |
print(f"[BRIDGE] New prompt: {current}")
|
| 194 |
decoded = decode_text(current)
|
| 195 |
print(f"[BRIDGE] Decoded: {decoded}")
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
| 197 |
print(f"[BRIDGE] AI response: {ai_response[:100]}...")
|
|
|
|
| 198 |
encoded_response = encode_text(ai_response)
|
| 199 |
-
|
| 200 |
print("[BRIDGE] Response sent to Scratch")
|
|
|
|
| 201 |
last_prompt = current
|
| 202 |
time.sleep(1)
|
| 203 |
except Exception as e:
|
|
@@ -212,7 +140,7 @@ app = Flask(__name__)
|
|
| 212 |
|
| 213 |
@app.route('/')
|
| 214 |
def health():
|
| 215 |
-
return "Scratch AI Bridge is running
|
| 216 |
|
| 217 |
if __name__ == '__main__':
|
| 218 |
print("[MAIN] Starting bridge thread...")
|
|
|
|
| 3 |
import os
|
| 4 |
import random
|
| 5 |
import requests
|
| 6 |
+
import ssl
|
| 7 |
from flask import Flask
|
| 8 |
+
import scratchattach as sa
|
| 9 |
+
import websocket
|
| 10 |
|
| 11 |
# ==================== CONFIGURATION ====================
|
|
|
|
| 12 |
SCRATCH_USER = os.environ.get("SCRATCH_USER")
|
| 13 |
SCRATCH_PASS = os.environ.get("SCRATCH_PASS")
|
| 14 |
PROJECT_ID = int(os.environ.get("PROJECT_ID", 0))
|
| 15 |
AI_URL = "https://16dvnk-spirit-kings-ai.hf.space/generate"
|
| 16 |
|
| 17 |
+
# Scratch cloud WebSocket IP addresses (from your dig output)
|
| 18 |
+
SCRATCH_CLOUD_IPS = [
|
| 19 |
+
"16.144.201.211",
|
| 20 |
+
"54.69.147.69",
|
| 21 |
+
"44.230.13.252"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
]
|
| 23 |
|
| 24 |
# ==================== ENCODING/DECODING ====================
|
|
|
|
| 51 |
i += 1
|
| 52 |
return "".join(result)
|
| 53 |
|
| 54 |
+
# ==================== FORCE IP FOR WEBSOCKET ====================
|
| 55 |
+
def patch_scratchattach_websocket():
|
| 56 |
+
"""Monkey-patch scratchattach to use IP address with custom Host header."""
|
| 57 |
+
# Pick a random working IP from the list
|
| 58 |
+
working_ip = random.choice(SCRATCH_CLOUD_IPS)
|
| 59 |
+
new_cloud_host = f"wss://{working_ip}/"
|
| 60 |
+
|
| 61 |
+
# Save original CloudConnection class and its connect method
|
| 62 |
+
from scratchattach import CloudConnection
|
| 63 |
+
original_connect = CloudConnection.connect
|
| 64 |
+
|
| 65 |
+
def patched_connect(self, *args, **kwargs):
|
| 66 |
+
# Override the cloud host for this instance
|
| 67 |
+
self.CLOUD_HOST = new_cloud_host
|
| 68 |
+
# Create WebSocket with custom Host header and disabled SSL verification
|
| 69 |
+
self.ws = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
|
| 70 |
+
self.ws.connect(
|
| 71 |
+
self.CLOUD_HOST,
|
| 72 |
+
header=[f"Host: clouddata.scratch.mit.edu"],
|
| 73 |
+
http_proxy_host=None,
|
| 74 |
+
http_proxy_port=None
|
| 75 |
+
)
|
| 76 |
+
self.ws.sock.setblocking(0)
|
| 77 |
+
# Send handshake
|
| 78 |
+
handshake = {"method": "handshake", "project_id": str(self.project_id)}
|
| 79 |
+
if self.user:
|
| 80 |
+
handshake["user"] = self.user
|
| 81 |
+
if self.project_token:
|
| 82 |
+
handshake["project_token"] = self.project_token
|
| 83 |
+
self.ws.send(sa.json.dumps(handshake))
|
| 84 |
+
response = self.ws.recv()
|
| 85 |
+
data = sa.json.loads(response)
|
| 86 |
+
self.session_id = data["session_id"]
|
| 87 |
+
self.project_id = data["project_id"]
|
| 88 |
+
self.user = data.get("user")
|
| 89 |
+
self.permissions = data.get("permissions", {})
|
| 90 |
+
self.ws.settimeout(None)
|
| 91 |
+
|
| 92 |
+
# Apply the patch
|
| 93 |
+
CloudConnection.connect = patched_connect
|
| 94 |
+
print(f"[PATCH] CloudConnection patched to use {new_cloud_host}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
|
| 96 |
# ==================== BRIDGE WORKER ====================
|
| 97 |
def bridge_worker():
|
| 98 |
+
print("[BRIDGE] Starting bridge...")
|
| 99 |
+
patch_scratchattach_websocket()
|
| 100 |
+
|
| 101 |
while True:
|
| 102 |
try:
|
| 103 |
print("[BRIDGE] Logging into Scratch...")
|
| 104 |
+
session = sa.login(SCRATCH_USER, SCRATCH_PASS)
|
| 105 |
+
print("[BRIDGE] Login successful. Connecting to cloud variables...")
|
| 106 |
+
conn = session.connect_cloud(PROJECT_ID)
|
| 107 |
+
print(f"[BRIDGE] Connected to project {PROJECT_ID}")
|
|
|
|
|
|
|
| 108 |
|
| 109 |
last_prompt = ""
|
| 110 |
while True:
|
| 111 |
+
# Get current prompt value
|
| 112 |
+
current = conn.get_var("prompt")
|
| 113 |
+
if current is None:
|
| 114 |
+
current = "0"
|
| 115 |
if current != last_prompt and current != "0":
|
| 116 |
print(f"[BRIDGE] New prompt: {current}")
|
| 117 |
decoded = decode_text(current)
|
| 118 |
print(f"[BRIDGE] Decoded: {decoded}")
|
| 119 |
+
|
| 120 |
+
# AI response (direct call, no proxy)
|
| 121 |
+
response = requests.post(AI_URL, json={"prompt": decoded}, timeout=30)
|
| 122 |
+
ai_response = response.json().get("response", "Sorry, no response.") if response.status_code == 200 else f"Error {response.status_code}"
|
| 123 |
print(f"[BRIDGE] AI response: {ai_response[:100]}...")
|
| 124 |
+
|
| 125 |
encoded_response = encode_text(ai_response)
|
| 126 |
+
conn.set_var("response", encoded_response)
|
| 127 |
print("[BRIDGE] Response sent to Scratch")
|
| 128 |
+
|
| 129 |
last_prompt = current
|
| 130 |
time.sleep(1)
|
| 131 |
except Exception as e:
|
|
|
|
| 140 |
|
| 141 |
@app.route('/')
|
| 142 |
def health():
|
| 143 |
+
return "Scratch AI Bridge is running."
|
| 144 |
|
| 145 |
if __name__ == '__main__':
|
| 146 |
print("[MAIN] Starting bridge thread...")
|