summaryrefslogtreecommitdiff
path: root/src/server.py
blob: 3f7442254eea808377c39a6037b18063ea2e8bc4 (plain)
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
#!/usr/bin/env python

import argparse
import asyncio
import json
import sys

from datetime import datetime, timezone
from pathlib import Path
from websockets.asyncio.server import serve
from websockets.exceptions import ConnectionClosedOK

class Context():
    def __init__(self, output):
        self.output = output
        output.mkdir(parents = True, exist_ok = True)

    def next(self):
        ts = datetime.now(timezone.utc).timestamp()
        return self.output / f"session-{ts}.json"


def quote(instr):
    return json.dumps(instr)

def parse_header(message):
    parts = message.split('|')
    if len(parts) < 3:
        return None
    return {
        'width': float(parts[0]),
        'height': float(parts[1]),
        'brands': '|'.join(parts[2:]),
    }

def parse_event(message):
    parts = message.split('|')
    if parts[0] == 'u' and len(parts) > 1:
        return {
            'type': 'url',
            'url': '|'.join(parts[1:]),
        }
    if len(parts) < 3:
        return None
    return {
        'type': 'click',
        'x': float(parts[0]),
        'y': float(parts[1]),
        'tag': parts[2],
    }

async def handler(context, websocket):
    filename = context.next()
    with open(filename, "w", encoding="utf-8") as session_file:
        try:
            message = await websocket.recv()
        except ConnectionClosedOK:
            filename.unlink()
            return

        header = parse_header(message)
        if not header:
            print(f"Bad header: {message}", file=sys.stderr)
            filename.unlink()
            return

        print(f'''{{
  "format":"clicks",
  "version":"1.0",
  "width": {header['width']},
  "height": {header['height']},
  "brands": {quote(header['brands'])},
  "events": [''',
              file=session_file)

        comma = ''

        while True:
            try:
                message = await websocket.recv()
                event = parse_event(message)
                if event:
                    if event['type'] == 'click':
                        print(f'''{comma}{{
"x": {event['x']}, "y": {event['y']}, "tag": {quote(event['tag'])}
}}''',
                              file=session_file)
                    else:
                        print(f'''{comma}{{"url": {quote(event['url'])}}}''',
                              file=session_file)

                    if not comma:
                        comma = ','
            except ConnectionClosedOK:
                break

        print(']}', file=session_file)


async def server(output):
    context = Context(output)
    async with serve(lambda x: handler(context, x), "127.0.0.1", 8001):
        await asyncio.get_running_loop().create_future()

def main():
    parser = argparse.ArgumentParser(
        description='Example server')

    parser.add_argument('output', type=Path, help='output directory')

    args = parser.parse_args()

    asyncio.run(server(args.output))

if __name__ == "__main__":
    main()