# -*- coding: utf-8 -*-
"""
OfferSync 截图助手 —— 笔试面试同步化助手的电脑端（常驻热键截图）
工作原理（与 offeroc 客户端一致）：
  1. 电脑上按 F2 拖动框选题目区域（或 F3 / PrtSc 全屏截图）
  2. 截图自动推送到同房间的「问答区」网页（手机/平板/电脑均可打开）
  3. 问答区自动 OCR 识别文字 → AI 生成答案 → 实时显示在旁边设备上
用法：
  python offersync-shot.py [6位房间码]
依赖：
  pip install mss pillow paho-mqtt pynput
  （或直接双击「启动截图助手.bat」自动安装）
"""
import sys,os,json,time,base64,io,threading,random

def _need(mod):
    print('[X] 缺少依赖: %s'%mod)
    print('    请先执行: pip install mss pillow paho-mqtt pynput')
    print('    或双击运行「启动截图助手.bat」自动安装')
    try: input('按回车退出...')
    except Exception: pass
    sys.exit(1)

try:
    import mss
except ImportError:
    _need('mss')
try:
    from PIL import Image
except ImportError:
    _need('pillow')
try:
    import paho.mqtt.client as mqtt
except ImportError:
    _need('paho-mqtt')
try:
    from pynput import keyboard
except ImportError:
    _need('pynput')

import tkinter as tk

BROKER='broker.emqx.io'
PORT=1883
HERE=os.path.dirname(os.path.abspath(__file__))
CFG=os.path.join(HERE,'.offersync-room')

def get_room():
    r=None
    for a in sys.argv[1:]:
        if a.isdigit() and len(a)==6:
            r=a
            break
    if not r and os.path.exists(CFG):
        try:
            t=open(CFG,encoding='utf-8').read().strip()
            if t.isdigit() and len(t)==6:
                r=t
        except Exception:
            pass
    if not r:
        try:
            r=input('请输入网页端 6 位房间码后回车: ').strip()
        except EOFError:
            sys.exit(1)
    if not(r and r.isdigit() and len(r)==6):
        print('[X] 房间码必须是 6 位数字')
        sys.exit(1)
    try:
        open(CFG,'w',encoding='utf-8').write(r)
    except Exception:
        pass
    return r

ROOM=get_room()
TOPIC='offersync/v1/'+ROOM

# 物理像素对齐（高分屏截图不偏移）
try:
    import ctypes
    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(2)
    except Exception:
        try:
            ctypes.windll.user32.SetProcessDPIAware()
        except Exception:
            pass
except Exception:
    pass

client=None

def on_connect(c,u,f,rc=0,props=None):
    if rc==0:
        print('[√] 同步通道已连接，热键已就绪')
    else:
        print('[X] 同步通道连接失败 code=%s'%rc)

def mqtt_init():
    global client
    cid='osh_'+''.join(random.choice('0123456789abcdef') for _ in range(8))
    try:
        client=mqtt.Client(mqtt.CallbackAPIVersion.VERSION1,client_id=cid)
    except AttributeError:
        client=mqtt.Client(client_id=cid)
    client.on_connect=on_connect
    try:
        client.connect_async(BROKER,PORT,keepalive=60)
        client.loop_start()
    except Exception as e:
        print('[X] MQTT 初始化失败: %s'%e)

busy=threading.Lock()

def send_pil(img):
    w,h=img.size
    if w>1600:
        img=img.resize((1600,max(1,int(h*1600.0/w))),Image.LANCZOS)
    buf=io.BytesIO()
    img.convert('RGB').save(buf,'JPEG',quality=62)
    data=buf.getvalue()
    if not client or not client.is_connected():
        print('[X] 同步通道未连接，本次截图未发送（几秒后自动重连，可重按热键）')
        return
    b=base64.b64encode(data).decode()
    payload=json.dumps({'t':'img','img':'data:image/jpeg;base64,'+b,'src':'PC截图助手','ts':int(time.time()*1000000)+random.randint(0,999)})
    client.publish(TOPIC,payload,qos=1)
    print('[√] 已推送到房间 %s（%d KB）'%(ROOM,len(data)//1024))
    toast('√ 已推送到问答区')

def toast(msg):
    try:
        t=tk.Tk()
        t.overrideredirect(True)
        t.attributes('-topmost',True)
        t.configure(bg='#04121c')
        tk.Label(t,text='  %s  '%msg,bg='#04121c',fg='#00e0b8',font=('Microsoft YaHei UI',12,'bold'),padx=16,pady=10).pack()
        t.update_idletasks()
        sw=t.winfo_screenwidth(); sh=t.winfo_screenheight()
        t.geometry('+%d+%d'%(sw-300,sh-130))
        t.after(1400,t.destroy)
        t.mainloop()
    except Exception:
        pass

def region_shot():
    if not busy.acquire(False):
        print('[!] 上一次截图还未完成')
        return
    try:
        with mss.mss() as sct:
            shot=sct.grab(sct.monitors[1])
        img=Image.frombytes('RGB',shot.size,shot.rgb)
        root=tk.Tk()
        root.attributes('-fullscreen',True)
        root.attributes('-alpha',0.30)
        root.configure(bg='black')
        cv=tk.Canvas(root,bg='black',highlightthickness=0)
        cv.pack(fill=tk.BOTH,expand=True)
        cv.create_text(root.winfo_screenwidth()//2,80,text='拖动鼠标框选题目区域（Esc 取消）',fill='#00e0b8',font=('Microsoft YaHei UI',16,'bold'))
        st={'x0':None,'rect':None}
        def down(e):
            st['x0']=(e.x,e.y)
        def move(e):
            if st['x0'] is None:
                return
            x0,y0=st['x0']
            if st['rect'] is not None:
                cv.coords(st['rect'],min(x0,e.x),min(y0,e.y),max(x0,e.x),max(y0,e.y))
            else:
                st['rect']=cv.create_rectangle(min(x0,e.x),min(y0,e.y),max(x0,e.x),max(y0,e.y),outline='#00e0b8',width=2)
        def up(e):
            x0,y0=st['x0']
            st['x0']=None
            root.destroy()
            if abs(e.x-x0)<8 or abs(e.y-y0)<8:
                print('[!] 选区太小，已忽略')
                return
            crop=img.crop((min(x0,e.x),min(y0,e.y),max(x0,e.x)+1,max(y0,e.y)+1))
            send_pil(crop)
        root.bind('<Escape>',lambda e: root.destroy())
        cv.bind('<ButtonPress-1>',down)
        cv.bind('<B1-Motion>',move)
        cv.bind('<ButtonRelease-1>',up)
        root.focus_force()
        root.mainloop()
    finally:
        busy.release()

def full_shot():
    if not busy.acquire(False):
        print('[!] 上一次截图还未完成')
        return
    try:
        with mss.mss() as sct:
            shot=sct.grab(sct.monitors[1])
        send_pil(Image.frombytes('RGB',shot.size,shot.rgb))
    finally:
        busy.release()

def on_press(key):
    if key==keyboard.Key.f2:
        threading.Thread(target=region_shot,daemon=True).start()
    elif key in (keyboard.Key.f3,keyboard.Key.print_screen):
        threading.Thread(target=full_shot,daemon=True).start()

def main():
    mqtt_init()
    print('='*50)
    print('  OfferSync 截图助手   房间码: %s'%ROOM)
    print('  F2 = 框选截图      F3 / PrtSc = 全屏截图')
    print('  截图自动推送到同房间网页的「问答区」并完成')
    print('  OCR 识别 + AI 解析（手机打开网站输入同一房间码）')
    print('  关闭本窗口即退出')
    print('='*50)
    with keyboard.Listener(on_press=on_press) as l:
        l.join()

if __name__=='__main__':
    try:
        main()
    except KeyboardInterrupt:
        pass
