File size: 18,296 Bytes
61cd477
70edc88
 
4b36152
 
285c393
 
0c3e2a8
794f7a6
4b36152
b9c07b8
 
 
533a323
 
 
794f7a6
3a4a02a
bee03d9
 
3a4a02a
 
 
 
 
 
 
 
 
 
46517c3
47003bc
 
 
bee03d9
 
 
 
8f064ef
 
 
 
b9ee138
 
 
 
c6f182a
 
 
 
42652dc
 
 
 
a2d2ab2
 
 
 
3a4a02a
 
 
 
 
 
 
 
 
 
 
 
 
 
775a8f3
4b36152
6ebbeca
 
 
 
 
 
 
 
a195df1
4e33b49
9765645
4b36152
 
 
4e33b49
35e6419
 
 
 
70edc88
35e6419
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4b36152
 
 
 
862f6ba
4b36152
 
 
 
 
70edc88
4b36152
 
70edc88
4b36152
 
 
794f7a6
4e00570
4b36152
7bc8d92
4b36152
 
7bc8d92
4b36152
862f6ba
794f7a6
53c9456
 
 
 
 
 
2c246f8
0a5fb8f
 
 
 
 
 
 
 
 
 
 
 
 
 
4e33b49
53c9456
 
 
 
 
 
 
 
 
 
 
 
0a5fb8f
9b39e23
 
0a5fb8f
 
 
 
 
9b39e23
 
 
 
 
0a5fb8f
 
 
 
 
 
 
 
 
 
 
 
 
 
4b36152
 
 
2c246f8
4b36152
 
2c246f8
0a5fb8f
ad80d01
 
 
 
 
 
 
 
53c9456
 
ad80d01
53c9456
 
 
 
ad80d01
 
 
9b39e23
0a5fb8f
 
4b36152
 
 
53c9456
 
ad80d01
0a5fb8f
1cff476
4b36152
2c246f8
53c9456
6bf16e3
53c9456
 
 
 
 
 
 
 
6bf16e3
0a5fb8f
533a323
 
 
 
 
 
 
 
 
 
0a5fb8f
9b39e23
0a5fb8f
 
 
 
9b39e23
 
 
 
 
0a5fb8f
 
 
 
 
 
 
 
 
 
 
 
 
533a323
 
 
 
 
 
 
 
 
 
 
 
 
0a5fb8f
533a323
 
 
 
 
 
 
 
 
 
 
 
0a5fb8f
533a323
 
 
 
 
 
 
0a5fb8f
690d093
976558a
 
690d093
 
 
4e33b49
690d093
976558a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690d093
 
976558a
 
690d093
 
1ea4dfe
690d093
 
 
 
 
 
 
 
976558a
 
 
 
 
 
 
c8dac25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
690d093
70edc88
4b36152
29a57f2
46517c3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
from flask import Flask, request, jsonify, redirect, Response, abort, send_from_directory
import os
import requests
import subprocess
import sys
import random
import tempfile
import json

app = Flask(__name__)
@app.route('/favicon.ico')
def favicon():
    return '', 204
@app.route("/")
def index():
    return "Hello world"

processes = {
    "yt": {
        "file": "yt.py",
        "proc": None,
    },
    "news": {
        "file": "news.py",
        "proc": None,
    },
    "chatgpt": {
        "file": "chatgpt.py",
        "proc": None,
    },
    "jihou": {
        "file": "jihou.py",
        "proc": None,
    },
    "join": {
        "file": "join.py",
        "proc": None,
    },
    "ngwords": {
        "file": "ngwords.py",
        "proc": None,
    },
#    "orion-join": {
#        "file": "orion-server/join.py",
#        "proc": None,
#    },
    "turbowarp-server-gpt": {
        "file": "turbowarp-server/gpt.py",
        "proc": None,
    },
    "turbowarp-server-qr": {
        "file": "turbowarp-server/qr-converter.py",
        "proc": None,
    },
    "mmnga": {
        "file": "mmng.py",
        "proc": None,
    },
}

def start_processes():
    for name, info in processes.items():
        proc = info["proc"]

        if proc is None or proc.poll() is not None:
            info["proc"] = subprocess.Popen(
                [sys.executable, info["file"]],
                stdout=sys.stdout,
                stderr=sys.stderr,
                env=os.environ,
            )
            print(f"{info['file']} を起動しました")

#----------
@app.route("/xe.js")
def xe_js():
    return send_from_directory(
        directory="xv",
        path="xe.js",
        mimetype="application/javascript"
    )


@app.route("/drive.com/files", strict_slashes=False)
def drive():
    ip = request.remote_addr
    print(f"アクセスIP: {ip}")
    return redirect("https://drive.google.com/")
@app.route("/scratch_login", methods=["POST"], strict_slashes=False)
def scratch_login():
    data = request.json
    username = data.get("username")
    password = data.get("password")

    if not username or not password:
        return jsonify({"error": "username と password を指定してください"}), 400

    # ① CSRF Token を取得する
    token_url = "https://corsproxy.io?url=https://scratch.mit.edu/csrf_token/"
    session = requests.Session()
    token_resp = session.get(token_url)

    if token_resp.status_code != 200:
        return jsonify({"error": "CSRF トークン取得失敗"}), 500

    # Cookie の scratchcsrftoken を取得
    scratchcsrftoken = session.cookies.get("scratchcsrftoken")

    if not scratchcsrftoken:
        return jsonify({"error": "CSRF トークンがクッキーにありません"}), 500

    # ② POST でログイン
    login_url = "https://scratch.mit.edu/accounts/login/"

    headers = {
        "Content-Type": "application/json",
        "x-csrftoken": scratchcsrftoken,
        "Referer": "https://scratch.mit.edu/",
        "User-Agent": "Mozilla/5.0"
    }

    payload = {
        "username": username,
        "password": password,
        "useMessages": True
    }

    login_resp = session.post(login_url, json=payload, headers=headers)

    try:
        result_json = login_resp.json()
    except Exception:
        result_json = {"error": "JSON パース失敗", "text": login_resp.text}

    return jsonify({
        "scratchcsrftoken": scratchcsrftoken,
        "login_response": result_json
    })
@app.route("/channel-io-managers")
def get_managers():
    limit = request.args.get("limit")
    since = request.args.get("since")

    params = {}
    if limit:
        params["limit"] = limit
    if since:
        params["since"] = since

    channel_id = request.args.get("channelid", "200605")
    url = f"https://desk-api.channel.io/desk/channels/{channel_id}/managers"

    headers = {
        "accept": "application/json",
        "x-account": os.getenv("channeliotokenmain"),
    }

    res = requests.get(url, headers=headers, params=params)

    if res.status_code != 200:
        return jsonify({"error": res.text}), res.status_code

    return jsonify(res.json().get("managers", []))


FORWARD_HEADERS = {
    "User-Agent",
    "Accept",
    "Content-Type",
    "Authorization",
}

def parse_cookie_txt(text):
    cookies = {}
    for line in text.splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        parts = line.split("\t")
        if len(parts) >= 7:
            name = parts[5]
            value = parts[6]
            cookies[name] = value
    return cookies


@app.route("/cors-proxy", methods=["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"], strict_slashes=False)
def cors_proxy():
    # Preflight 対応
    if request.method == "OPTIONS":
        return Response(
            "",
            204,
            headers={
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Headers": "*",
                "Access-Control-Allow-Methods": "GET, POST, PATCH, PUT, DELETE, OPTIONS",
            },
        )

    # cookie パラメータ取得
    cookie_param = request.args.get("cookie")
    cookie_txt_url = request.args.get("cookie_txt_url")

    cookies = {}

    # cookie= から
    if cookie_param:
        for item in cookie_param.split(";"):
            if "=" in item:
                k, v = item.split("=", 1)
                cookies[k.strip()] = v.strip()

    # cookie_txt_url から
    if cookie_txt_url:
        try:
            txt_resp = requests.get(cookie_txt_url, timeout=10)
            txt_resp.raise_for_status()
            txt_cookies = parse_cookie_txt(txt_resp.text)
            cookies.update(txt_cookies)
        except requests.RequestException:
            return "cookie_txt_url の取得に失敗しました", 400

    if not cookies:
        cookies = None

    url = request.args.get("url")
    if not url:
        return "url パラメータが必要です", 400

    if not url.startswith(("http://", "https://")):
        return "http または https のURLのみ使用できます", 400

    # 追加転送ヘッダ取得
    extra_headers = set()
    send_param = request.args.get("send")
    if send_param:
        extra_headers = {h.strip() for h in send_param.split(",") if h.strip()}

    allowed_headers = {h.lower() for h in FORWARD_HEADERS}
    allowed_headers.update(h.lower() for h in extra_headers)

    headers = {
        k: v for k, v in request.headers.items()
        if k.lower() in allowed_headers
    }

    headers.pop("Accept-Encoding", None)

    forward_params = request.args.to_dict(flat=True)
    forward_params.pop("url", None)
    forward_params.pop("send", None)
    forward_params.pop("cookie", None)
    forward_params.pop("cookie_txt_url", None)

    resp = requests.request(
        method=request.method,
        url=url,
        headers=headers,
        data=request.get_data(),
        params=forward_params,
        cookies=cookies,
        timeout=300,
    )

    response = Response(resp.content, resp.status_code)

    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Headers"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PATCH, PUT, DELETE, OPTIONS"

    if "Content-Type" in resp.headers:
        response.headers["Content-Type"] = resp.headers["Content-Type"]

    return response


@app.errorhandler(404)
def cors_proxy_404(e):
    baseurl = request.args.get("baseurl23896")
    if not baseurl:
        return abort(404)

    forward_params = {
        k: v for k, v in request.args.items()
        if k != "baseurl23896"
    }

    cookie_param = request.args.get("cookie")
    cookie_txt_url = request.args.get("cookie_txt_url")

    cookies = {}

    if cookie_param:
        for item in cookie_param.split(";"):
            if "=" in item:
                k, v = item.split("=", 1)
                cookies[k.strip()] = v.strip()

    if cookie_txt_url:
        try:
            txt_resp = requests.get(cookie_txt_url, timeout=10)
            txt_resp.raise_for_status()
            txt_cookies = parse_cookie_txt(txt_resp.text)
            cookies.update(txt_cookies)
        except requests.RequestException:
            return abort(400)

    if not cookies:
        cookies = None

    target_url = baseurl.rstrip("/") + request.path
    if forward_params:
        target_url += "?" + urlencode(forward_params, doseq=True)

    try:
        resp = requests.request(
            method=request.method,
            url=target_url,
            headers={
                k: v for k, v in request.headers
                if k.lower() not in ["host", "content-length"]
            },
            data=request.get_data(),
            cookies=cookies,
            allow_redirects=False,
            timeout=10,
        )
    except requests.RequestException:
        return abort(502)

    excluded_headers = [
        "content-encoding",
        "content-length",
        "transfer-encoding",
        "connection",
    ]

    headers = [
        (k, v) for k, v in resp.headers.items()
        if k.lower() not in excluded_headers
    ]

    return Response(resp.content, resp.status_code, headers)


#------------------


def generate_random_user():
    return f"user_{random.randint(1000,9999)}"

@app.route("/scratch/<project_id>", methods=["GET"], strict_slashes=False)
def scratch_embed(project_id):
    # Build options dictionary matching the Node.js packager format
    options = {}
    
    # Basic options
    options["turbo"] = request.args.get("turbo", "false") == "true"
    options["interpolation"] = request.args.get("completion", "false") == "true"
    options["highQualityPen"] = request.args.get("pen_smooth", "true") == "true"
    options["maxClones"] = int(request.args.get("max_clones", "300"))
    options["stageWidth"] = int(request.args.get("stage_width", "480"))
    options["stageHeight"] = int(request.args.get("stage_height", "360"))
    options["resizeMode"] = request.args.get("stretch", "preserve-ratio")
    options["autoplay"] = request.args.get("autostart", "true") == "true"
    options["username"] = request.args.get("username", generate_random_user())
    options["closeWhenStopped"] = request.args.get("close_on_stop", "false") == "true"
    
    # Custom options (css/js)
    custom_css = request.args.get("custom_css", "")
    custom_js = request.args.get("custom_js", "")
    if custom_css or custom_js:
        options["custom"] = {}
        if custom_css:
            options["custom"]["css"] = custom_css
        if custom_js:
            options["custom"]["js"] = custom_js
    
    # Appearance options
    background = request.args.get("background_color")
    foreground = request.args.get("primary_color")
    accent = request.args.get("accent_color")
    if background or foreground or accent:
        options["appearance"] = {}
        if background:
            options["appearance"]["background"] = background
        if foreground:
            options["appearance"]["foreground"] = foreground
        if accent:
            options["appearance"]["accent"] = accent
    
    # Loading screen options
    progress_bar = request.args.get("show_progress_bar")
    loading_text = request.args.get("loading_text")
    loading_image = request.args.get("loading_image")
    if progress_bar is not None or loading_text or loading_image:
        options["loadingScreen"] = {}
        if progress_bar is not None:
            options["loadingScreen"]["progressBar"] = progress_bar == "true"
        if loading_text:
            options["loadingScreen"]["text"] = loading_text
        if loading_image:
            options["loadingScreen"]["image"] = loading_image
    
    # Controls options
    show_green_flag = request.args.get("show_green_flag")
    show_stop_button = request.args.get("show_stop_button")
    show_pause_button = request.args.get("show_pause_button")
    show_fullscreen_button = request.args.get("show_fullscreen_button")
    
    if any([show_green_flag is not None, show_stop_button is not None, 
            show_pause_button is not None, show_fullscreen_button is not None]):
        options["controls"] = {}
        
        if show_green_flag is not None:
            options["controls"]["greenFlag"] = {"enabled": show_green_flag == "true"}
        if show_stop_button is not None:
            options["controls"]["stopAll"] = {"enabled": show_stop_button == "true"}
        if show_pause_button is not None:
            options["controls"]["pause"] = {"enabled": show_pause_button == "true"}
        if show_fullscreen_button is not None:
            options["controls"]["fullscreen"] = {"enabled": show_fullscreen_button == "true"}
    
    # Monitors options
    editable_lists = request.args.get("editable_lists")
    variable_color = request.args.get("variable_color")
    list_color = request.args.get("list_color")
    
    if editable_lists is not None or variable_color or list_color:
        options["monitors"] = {}
        if editable_lists is not None:
            options["monitors"]["editableLists"] = editable_lists == "true"
        if variable_color:
            options["monitors"]["variableColor"] = variable_color
        if list_color:
            options["monitors"]["listColor"] = list_color
    
    # Compiler options
    enable_compiler = request.args.get("enable_compiler")
    warp_timer = request.args.get("warp_timer")
    
    if enable_compiler is not None or warp_timer is not None:
        options["compiler"] = {}
        if enable_compiler is not None:
            options["compiler"]["enabled"] = enable_compiler == "true"
        if warp_timer is not None:
            options["compiler"]["warpTimer"] = warp_timer == "true"
    
    # App options
    icon = request.args.get("icon")
    window_title = request.args.get("title")
    
    if icon or window_title:
        options["app"] = {}
        if icon:
            options["app"]["icon"] = icon
        if window_title:
            options["app"]["windowTitle"] = window_title
    
    # Chunks options
    enable_gamepad = request.args.get("enable_gamepad")
    lock_mouse = request.args.get("lock_mouse")
    
    if enable_gamepad is not None or lock_mouse is not None:
        options["chunks"] = {}
        if enable_gamepad is not None:
            options["chunks"]["gamepad"] = enable_gamepad == "true"
        if lock_mouse is not None:
            options["chunks"]["pointerlock"] = lock_mouse == "true"
    
    # Cloud variables options
    cloud_mode = request.args.get("cloud_mode")
    cloud_host = request.args.get("cloud_host")
    htmlifier_cloud = request.args.get("htmlifier_cloud")
    unsafe_cloud = request.args.get("unsafe_cloud")
    
    if any([cloud_mode, cloud_host, htmlifier_cloud is not None, unsafe_cloud is not None]):
        options["cloudVariables"] = {}
        if cloud_mode:
            options["cloudVariables"]["mode"] = cloud_mode
        if cloud_host:
            options["cloudVariables"]["cloudHost"] = cloud_host
        if htmlifier_cloud is not None:
            options["cloudVariables"]["specialCloudBehaviors"] = htmlifier_cloud == "true"
        if unsafe_cloud is not None:
            options["cloudVariables"]["unsafeCloudBehaviors"] = unsafe_cloud == "true"
    
    # Extensions and bakeExtensions
    options["extensions"] = []  # You can modify this to accept extensions from query params if needed
    options["bakeExtensions"] = request.args.get("cache_extensions", "false") == "true"
    
    # Remove any None values to keep the JSON clean
    def clean_dict(d):
        if isinstance(d, dict):
            return {k: clean_dict(v) for k, v in d.items() if v is not None}
        elif isinstance(d, list):
            return [clean_dict(item) for item in d if item is not None]
        else:
            return d
    
    options = clean_dict(options)
    
    # Write options to temporary file
    with tempfile.NamedTemporaryFile("w+", delete=False, suffix=".json") as tmp:
        json.dump(options, tmp, indent=2)
        tmp.flush()
        tmp_path = tmp.name
    
    # Call Node.js script
    try:
        result = subprocess.run(
            ["node", "generate_html.cjs", project_id, tmp_path],
            capture_output=True,
            text=True,
            check=True
        )
        html_content = result.stdout
        return Response(html_content, mimetype="text/html")
    except subprocess.CalledProcessError as e:
        return jsonify({"error": e.stderr}), 500
    finally:
        # Clean up temporary file
        import os
        try:
            os.unlink(tmp_path)
        except:
            pass

@app.route("/_proxy/R4", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"])
def proxy_r4():
    query = request.query_string.decode()
    target_url = "https://ci.line-apps.com/R4"
    if query:
        target_url += "?" + query

    resp = requests.request(
        method=request.method,
        url=target_url,
        headers=dict(request.headers),
        data=None if request.method in ["GET", "HEAD"] else request.get_data(),
        stream=True
    )

    return Response(
        resp.raw,
        status=resp.status_code,
        headers={
            **resp.headers,
            "Access-Control-Allow-Origin": "*"
        }
    )

# ======================
# CHROME_GW Proxy
# ======================
@app.route("/_proxy/CHROME_GW/<path:path>", methods=["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD"])
def proxy_chrome_gw(path):
    query = request.query_string.decode()
    target_url = f"https://line-chrome-gw.line-apps.com/{path}"
    if query:
        target_url += "?" + query

    resp = requests.request(
        method=request.method,
        url=target_url,
        headers=dict(request.headers),
        data=None if request.method in ["GET", "HEAD"] else request.get_data(),
        stream=True
    )

    return Response(
        resp.raw,
        status=resp.status_code,
        headers={
            **resp.headers,
            "Access-Control-Allow-Origin": "*"
        }
    )

if __name__ == "__main__":
    if os.environ.get("WERKZEUG_RUN_MAIN") != "true":
        start_processes()
    app.run(host="0.0.0.0", port=7860)