commit d1c12a08b0f3b0bcdab4711c6de25b4c388e21da
Author: Your Name <you@example.com>
Date:   Mon Dec 2 02:35:49 2024 +0500

    init

diff --git a/__pycache__/screen.cpython-313.pyc b/__pycache__/screen.cpython-313.pyc
new file mode 100644
index 0000000..192e388
Binary files /dev/null and b/__pycache__/screen.cpython-313.pyc differ
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..e28e6aa
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,7 @@
+services:
+  python:
+    volumes:
+      - './:/usr/src/app'
+    working_dir: /usr/src/app
+    build: .
+    command: python server.py
diff --git a/dockerfile b/dockerfile
new file mode 100644
index 0000000..b9bedfb
--- /dev/null
+++ b/dockerfile
@@ -0,0 +1,10 @@
+FROM python:3
+
+WORKDIR /usr/src/app
+
+COPY requirements.txt ./
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY . .
+
+CMD [ "python", "./server.py" ]
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..652f6fc
--- /dev/null
+++ b/index.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Document</title>
+  <style>
+    body{
+        padding:0;
+        margin:0;
+        background-repeat: no-repeat;
+        background-size:contain;
+        background-position: center;
+        height: 100vh;
+        background-color: #4b444b;
+    }
+    @media (height < 700px) {
+        body {
+            height: 88vh;
+        }
+    }
+        #top_bar {
+            background-color: #6e84a3;
+            color: white;
+            font: bold 24px Helvetica;
+            padding: 6px 5px 4px 5px;
+            border-bottom: 1px outset;
+        }
+
+        #status {
+            text-align: center;
+        }
+  </style>
+  <script>
+            function createCookie(name, value, days) {
+            if (days) {
+                var date = new Date();
+                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
+                var expires = "; expires=" + date.toGMTString();
+            }
+            else var expires = "";               
+        
+            document.cookie = name + "=" + value + expires + "; path=/";
+        }
+        
+        function readCookie(name) {
+            var nameEQ = name + "=";
+            var ca = document.cookie.split(';');
+            for (var i = 0; i < ca.length; i++) {
+                var c = ca[i];
+                while (c.charAt(0) == ' ') c = c.substring(1, c.length);
+                if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
+            }
+            return null;
+        }
+        
+        function eraseCookie(name) {
+            createCookie(name, "", -1);
+        }
+        
+        
+        function readQueryVariable(name, defaultValue) {
+            // A URL with a query parameter can look like this:
+            // https://www.example.com?myqueryparam=myvalue
+            //
+            // Note that we use location.href instead of location.search
+            // because Firefox < 53 has a bug w.r.t location.search
+            const re = new RegExp('.*[?&]' + name + '=([^&#]*)'),
+                  match = document.location.href.match(re);
+        
+            if (match) {
+                // We have to decode the URL since want the cleartext value
+                return decodeURIComponent(match[1]);
+            }
+        
+            return defaultValue;
+        }
+        
+        function uuidv4() {
+        return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
+            (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
+        );
+        }
+        
+        let uuid = readCookie("uuid")
+        if (uuid==null){
+            uuid = uuidv4();
+            createCookie("uuid",uuid)   
+        }
+    function status(text) {
+        document.getElementById('status').textContent = text;
+    }
+    window.onload = () => {
+        if ('WebSocket' in window) {
+            let was_connect = false;
+            // create websocket connection
+            let ws = null;
+            con()
+
+            function con(){
+                ws = new WebSocket('wss://ws.n0r.su/');
+                ws.onopen = () => {
+                    console.log('websocket success---');
+                    get()
+                }
+                ws.onclose = (e) => {
+                    no_stream();
+                    setTimeout(()=>{con()},5000)
+                };
+                ws.onerror = (e) => {
+                    setTimeout(()=>{con()},5000)
+                    no_stream();
+                    // console.error('websocket fail');
+                }
+            }
+            function get() {
+                ws.send(`get_stream%uuid=${uuid}`);
+                // setTimeout(() => {
+                //     get();
+                // }, 100);
+                ws.onmessage = (message) => {
+                    if (message.data != "null"){
+                        was_connect = true;                    
+                        let data = message.data;
+                        // console.log('get websocket message---', JSON.parse(data)[1]);
+                        document.body.style.backgroundImage = `url(data:image/jpeg;base64,${JSON.parse(data)[1]})`;
+                        document.getElementById("top_bar").setAttribute("hidden","");
+                        status("conected")
+                        setTimeout(()=>{get()},200)
+                    }
+                    else{
+                        setTimeout(()=>{get();},5000)
+                        no_stream();
+                    }
+                }
+            }
+            ws.onerror = (e) => {
+                setTimeout(()=>{con()},5000)
+                no_stream();
+                        // console.error('websocket fail');
+            }
+            ws.onclose = (e) => {
+                setTimeout(()=>{con()},5000)
+                no_stream()
+                // console.log("The connection has been closed successfully.")
+            };
+
+            function no_stream(){
+                if (document.body.style.backgroundImage != `url(/papi/savehouse/?const=club_dance)`){
+                    document.body.style.backgroundImage = `url(/papi/savehouse/?const=club_dance)`;
+                }
+                document.getElementById("top_bar").removeAttribute("hidden");
+                if(was_connect){
+                    status("Stream has ended")
+                }
+                else{
+                    status("Stream is closed")
+                }
+            }
+        } else {
+            console.error('dont support websocket');
+        };
+    };
+</script>
+
+</head>
+<body>
+    <div id="top_bar">
+        <div id="status">Loading</div>
+    </div>
+</body>
+</html>
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..448ed83
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+flask
+websockets
+pillow
diff --git a/screen.py b/screen.py
new file mode 100644
index 0000000..34c43fc
--- /dev/null
+++ b/screen.py
@@ -0,0 +1,60 @@
+#Copyright (C) 2021  Qijun Gu
+#
+#This file is part of Screenshare.
+#
+#Screenshare is free software: you can redistribute it and/or modify
+#it under the terms of the GNU General Public License as published by
+#the Free Software Foundation, either version 3 of the License, or
+#(at your option) any later version.
+#
+#Screenshare is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+#along with Screenshare. If not, see <https://www.gnu.org/licenses/>.
+
+import threading, time, base64, sys
+
+ver = sys.version_info.major
+if ver==2:
+    import StringIO as io
+elif ver==3:
+    import io
+
+from PIL import ImageGrab as ig
+
+class Screen():
+    def __init__(self):
+        self.FPS = 30
+        self.screenbuf = ""
+        self.password = ""
+        if ver==2:
+            self.screenfile = io.StringIO()
+        elif ver==3:
+            self.screenfile = io.BytesIO()
+        threading.Thread(target=self.getframes).start()
+
+    def __del__(self):
+        self.screenfile.close()
+
+    def getframes(self):
+        while True:
+            im = ig.grab(bbox=(0, 0, 1920, 1080), include_layered_windows=False, all_screens=False, xdisplay=":0")
+            self.screenfile.seek(0)
+            self.screenfile.truncate(0)
+            im_converted = im.convert("RGB")
+            im_converted.save(self.screenfile, format="jpeg", quality=60, progressive=True)
+            self.screenbuf = base64.b64encode(self.screenfile.getvalue())
+            time.sleep(1.0/self.FPS)
+    
+    def gen(self):
+        s = ''
+        if ver==2:
+            s = self.screenbuf
+        elif ver==3:
+            s = self.screenbuf.decode()
+        return s
+    
+screenlive = Screen()
diff --git a/server.py b/server.py
new file mode 100644
index 0000000..0785dec
--- /dev/null
+++ b/server.py
@@ -0,0 +1,95 @@
+from screen import screenlive
+import json, argparse
+from flask import Flask, json
+import threading
+from time import sleep
+from websockets.sync.server import serve
+import websockets
+import array
+import time
+
+api = Flask(__name__)
+watchers_last_seen = {}
+stream = False
+debug = False
+
+def current_time():
+    return str(int(time.time()))
+
+@api.route("/off", methods=["GET"])
+def streaOff():
+    global stream
+    stream=False
+    return "True"
+
+@api.route("/on", methods=["GET"])
+def streamOn():
+    global stream
+    stream=True
+    return "True"
+
+@api.route("/views", methods=["GET"])
+def views():
+    global watchers_last_seen
+    return str(len(watchers_last_seen))
+
+
+def ws_server(websocket):
+    global stream, watchers_last_seen
+    if debug: print("WebSocket: Server Started.")
+    try:
+        while True:
+            req=websocket.recv()
+            if "%" in req:
+                args= req.split("%")[1]
+                args = dict(x.split("=") for x in args.split(";"))
+                req = req.split("%")[0]
+            if debug: print(req,"-----",args)
+            match (req):
+                case ("get_stream"):
+                    if stream:
+                        watchers_last_seen[args["uuid"]] = current_time()
+                        websocket.send(f""+str(json.dumps([True, screenlive.gen()])))
+                    else:
+                        websocket.send(f"null")
+                case (_):
+                    websocket.send(f"null")
+    except websockets.exceptions.ConnectionClosedOK:
+        if debug: print("hungup")
+    except websockets.ConnectionClosedError:
+        if debug: print("Internal Server Error.")
+
+def run_ws_server():
+        with serve(ws_server, "0.0.0.0", 7890) as server:
+            server.serve_forever()
+
+def timeout_task():
+    while True:
+        for key,value in watchers_last_seen.items():
+            if debug: print(key+"||||"+value)
+            if (int(current_time())-int(value)) > 5:
+                if debug: print(key+"is timed out")
+                watchers_last_seen.pop(key)
+                break
+        if debug: print("time -",current_time())
+        if debug: print("watchers -",watchers_last_seen.keys())
+        sleep(10)
+
+def run_flask_server():
+    api.run()
+
+def main():
+    t1 = threading.Thread(target=run_ws_server)
+    t2 = threading.Thread(target=run_flask_server)
+    t3 = threading.Thread(target=timeout_task)
+
+    t1.start()
+    t2.start()
+    t3.start()
+
+    t1.join()
+    t2.join()
+    t3.join()
+ 
+if __name__ == "__main__":
+    main()