// ==UserScript== // @name WUT网上党校 全能助手 // @namespace https://gitee.com/fieldlu/wut_assistant // @version 1.6.0 // @description 全自动刷课:视频断点续播/智能跳课/进度看门狗/真人模拟,打开培训班页面后自动运行 // @author FieldLu // @license GPL-3.0-or-later // @match *://wsdx.whut.edu.cn/* // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // @icon data:image/jpeg;base64,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abRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMaBtib9559e8wrPZXegsM+5FUoZe2XAQdsdPmTxptExptExptExptExptExptExptExptExptExptExptExptExpsEE3k+9uxrmWsQh32RqrJ+67kxptExptExptExptExptExptExptExptExptExptExptExptExptExkLWOdEXJxGdqEdsTY1+XJL7aMfMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTXInwLLBixYMqEppoIoXak4gESY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY04JtOXgMtlTwyFcmcyjLomNNomNNomNNomNNomNNomNNomNNomNNomNNomNNomNNomNNod0z2gybKdT6PwqFCpYDIIkcqcOOO0GTxptExptExptExptExptExptExptExptExptExptExpqzdqj+3bGdgqlzjdRS0S8+VyGfTcc1KYr7iY4xp20Y+ZPGm0TGm0TGm0TGm0TGm0TGm0TGm0TGm0TGm0So0DCMP+MUX5YY8gpG5n9y77bv1mRbcQdtn5DRl9tD/RJJDLvjPK9Bf4V5jIabRMabRMabRMabRMabRMabRMabRMabRMabRMabRMaVQ9VrmvB8yeM/odtQsjqn5OYcwk7tSo47Ff+l1utsAx8yeNNomNNomNNomNNomNNomNNomNNomNNomNNomHvIHdwH8Y+ZPGeUtcOq9/Bo011twrzgRAyd2kwl9djwEGTxptExptExptExptExptExptExptExptExptEw95A7uA/jHzJ4zLajg9c5Za9OBRXYu7tog14VlcZOcPLXp8yeNNomNNomNNomNNomNNomNNomNNomNKoeq1zXg+ZPGms3Hm/SDR84eWgqqjm9gCG72W7nDfvy16fMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaHG0wVY7ntGPmTrjZEHhj/7DgydhgX9QnbLCz8t3+cPLXp8yeNNomNNomNNomNNomNNomNNomNNolSlLqwEf8abRMZltRzxu6w4MnjTGzpQhS0FGrEigW/2HBk8abRMabRMabRMabRMabRMabRMabRMabN5WHP2joxptExmXZFlh2v20Y+ZPGi3ZoN8ybRh+STnDy16fMnjTaJjTaJjTaJjTaJjTaJjTaJjTVsSoRSkktExptEQ5PMAdpP+qeNNomNMPSlsToMlpvZ85MptExptExptExptExptExptExptExptExptDjaYKsdz2jHzJ40u1n/NF4eUQ/T5k8aV4i/jPrgyEXu0Hzh5a9PmTxptExptExptExptExptExptExlrYJ9ULwuDJ402iYokHcW73E2JtExptEAkfPmvtlY3RjVuDJ402iY02iY02iY02iY02iY02iY02iY02NmCgMAXPaJjTaJjTZ9Nw89vChq4Y+ZPGmmI6+y15oVsND402iY02iY02iY02iY02iY02iY02iY02iYzyvQXWvg6fWWvT5k8aa8iiRFKZQIZk8abQOE0ux/YZVUD1pFINox8yeNNomNNomNNomNNomNNomNNomNNolpyS13H9o1nOHexKnJWfF9tGNwWv+hIVIjgvtoxu4f2pvMnctO5TxptExptExptExptExptExptExptExptExnlegulG/jYO9f+aOFqSZmx6zKLY2jHzG1FQxl6SevDgydsIx3BkvoCH6fMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTXyuiucBfSf8wqQrNk9DGLCvpo91UeNNohosGGxeTLHHaDGLVODy03gHOHlr0+ZPGm0TGm0TGm0TGm0TGm0TGm0TGm0P/M36Y5LaEjV0bC1YaGqj3BS7vr7BtGPbcKmQiOJhA2jGtcI5FxPGfkL4vtox8yeNNomNNomNNomNNomNNomNNomNNomM6Es/Chqe6vLA7WDytzXEdQUaS05w8tNXu4t3uNMKmzz8ersc4eBBntPmTxptExptExptExptExptExptExptExptExptExpswtdoojMElmTxps+qTLiXEdls7ZhmNWp/YYsTwkrtox8yeNNomNNomNNomNNomNNomNNomNNomNNomNNomLbc82WjIjwfp8yYycRNsLhpau9NWN6GTxW2vwwqeNNomNNomNNomNNomNNomNNomNNomNNomNNomNNomNMo02BXckU1s2iY0w5QqIpXIAJQDGKpFqgybLM2R7hExptExptExptExptExptExptExptExptExptExptExptAf4LMHughIdtGPY1MTmGJC0C0AaUYcGO97JOaX6fMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJejT/MRlKjcsODJ3G68xMr/LNosbnDy1Ps43DZa9PmTxptExptExptExptExptExptExptErjSWFSuGPmTxptExpI7nAxWmsImTxptZSMfLCMNichEmNNomNNomNNomNNomNNomNNomNNomNNoeDWhYdrS1uFhe9xrRMabRMabQH+CzEonma22jHzJ402Ij9NTaJjTaJjTaJjTaJjTaJjTaJjTaJjTaJjTWa8uihazN/6QR36U5MabRMabRL1M7RwYVfMAF9tGPmTDFuoOvsuHlr0+ZPGm0TGm0TGm0TGm0TGm0TGm0TGZbUgXJtd8xM3QW+uUG5UBtGPmTxptAf4LMHufmo8abRMaZlt2zQUMgiTGm0TGm0TGm0TGm0TGm0TGm0TGm0TGmwc9D2Wxx37DYWOXt6HCdsFlr0+ZPGmzC12iiMwSWZPGm0S1EKgZUiTGm0TGm0TGm0TGm0TGm0TGm0TGm0TGm0TFgEyG8UiqbO/QzVtbDWT5OsCDJ402iYttzztK/dPbRj5k4c1jlbzfl6fMnjTaJjTaJjTaJjTaJjTaJjTaJjTaJjS4iXwk8BXLXp9NGvpP3mUTddAUQKDJ4yyd//ob4w5saRfbRj5UciokCZ+JDnbRj5k8abRMabRMabRMabRMabRMabRMabRLB1LaJ6gNox8yse88kBcKyY+Vrlnxj6XRmrqBxPTbj/AWB54fGTbx/v4HGm0TGmz0g9gJlKOXZPGm0TGm0TGm0TGm0TGm0TGm0TGm0TGmznfgV/+5+k2clLO5stExptExptExptExptExpsBpJR+Z+UCztox8yeNNomNNomNNomNNomNNomNNomM2nXTw1LjTaJjTaJjTaJjTaJjTaJjTZ9UVFx3CcTDDgyeNNomNNomNNomNNomNNomNNomNNlADk2r3uTmUXKOWGtIAmgF9tGPmTxptExptExps3lVYdIRVg8tenzJ402iY02iY02iY02iY02iY02iGfTZ20SRbzY3uxcFn7v1iY02iY02iY01nHW3ej+irhZelzm8U5dG5vrh5a9PmTxptExptExptExptExptExptExWUFNl4zIvHOVU4i3juyE6riARJjTaJjTZzLA/v2CR2cKkY9frQAoaNLWwT6oXhcGTxptExptExptExptExptExptExptExmtYhCoqXpw0fqYObSt4DSGMd8Hh1C7QZPGm0PISCd//GzP37lrK1/4xGYJLIMSoRSkktExptExptExptExptExptExptExptExps6eMfQsQVuX7VKwRZXkcaa9jibHAtIuUMzB/iWRKNoxiUzZBFeBbG5//X8Fv+w39brTOwq+XVsFWO57Rj5k8abRMabRMabRMabRMabRMabRMabRMakatibp+jXpDO9FwZPGnBorZGesDiae3CIwuBjeF9u8jekip4IbErKRj5k8W4B3xbE5w8tenzJ402iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY02iY01YAAD+//7254AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/GoRm3O51fCnRquTCDpvFU1QJElvdla18wORhLiHZLmwtDelQ5Rk3piR/ttYXPHhpYzk3DZAUfevJ3uWlXbQcXE2pfusUQPmLkB7q569YRVpv5tqVyySXOCWisPR+ilEAYXRe0tOSnob1VZ5qmv1I++68hhEfOBIn2N1DgC8L4XSyv9QRstgt7E1PE2n7bffEfGaNKjD9cYWo3hdD8yE93pjyAmrlIecRASYWa50IKMUXQ4lK8P+S2L1bSMM1WK5kKZOIphqxZU7YHlw+TdbZyBGT9WQ2PvPfPuwrbXaVYpB9C8Yjzile9Z+p0Mh4UJYhXcxWfNUyvCLFt/cgyJOu+4kd0vbDekELXcwOXgWqUlk9uMVMQnEOet2DEjEL2T9MbbOcaDjNHAJwfwZQGlA7csXjXivK3Eo0hsKXsPaFH4gTloYE0L1VREoUfc15M0FIlUqL64D4OapuK3dZZuYivLv0ASInn/zDGfU++Oka+3dI7jM4rpGEAk6uhXje39EGeqnkQI+ri9rh3HPqtJ76RmY8G+br7OhoDnrIYgeza3zNS7eG3hOv4jKpY5kGF5YxgJ9h/elBARR11ml1Aixch2LFMEq3FMmetUUIAT6x+BLji3AAAAA2tuG8ZDd9fwMByVpzzVoPe+LV34ZFm6w3LzKqN1Xs0EoFCREK/Wuta/XSx2v7usMGePp2kSLS96v1aM9ADHmj8fW5KgQlY4rSPQwpP95yOXnVMGiw/svv1HZSl0aD/UoVo6JgJduPgaqh/imN1VI8jWP9SrJk8+mFujqwQ6FmMgmsRp6dVg+3hLLdFm0SYVuoETvPnIX1padhLrY/+KaICWiojU5qBEsLWa27KGndVOw0ZcOPsj3CYq+l06tCpN1CA4HgiYin1+5aIzODZn9+smQJ3nhtyz5QEfeNzRpKV2hdu1qm8KgYHaVdRRqGLAS1r3BqfVuTY2oo9Op54SpEpnA7tcdBass/ZYq7m84p9oaKALh6TY/qfZrnBPp0zpGmkinylRuMiqLYUOY8wm7kjsmN+oXS+YmM1VJYrJ49KBbC3clBDlPtRFXj9svpCbgo//dVI2vCCwrV93rs+zhKUalnOQRNbkS0Pot0OkE2unFpzfTyImD6vkrwmQUAAABEjiuq4ZQTBUb40HVl43fqId+gfQRfZrvjSVhMC8JiLQufAXJocUIYtoOH6KEnZvPfRGMk2yvp4R5TNvM54DxsnvEG/JEJfV4YUYjIeFtTgDMAq6Zr7SnpKAEOO0h+u5sbJ4pyuPzK/MRtmy4xB5RlxxWGnYQdLfNkcpB9eUVbaiGKZwaW3TVsMc+JbQNJF0C5Jv5ctpjsQaMvYBpO6z1E7XPNmszW3+qhfrgY3yfpsNaXJ5OZd6T0Q9Ypr0+ltIZPggO/eSxTgiylHZq+OYRdEJbjrpEx/kz73AN9cOTlW90+c34FYIUD+pCgEwhjs+UKbMPPh+X2p3+U/gOHvYqA2mATqfy96BymooG6gwjorH8cQzpJv3Qp6HF+h3+psKXAAABTzDWJWGkUwL1csv1GHNYitKlka/D6Iz5yJDvXjGKGYF2qnIfc7NCMV1mBul0jYHRKvRvh5K/kxeTjCJ1qWZ+Rh41v/KUO80E7KELjjyDe/R/o25MRyi/DrwbJnavScs4jaVrIxA8EF/0dlj5WL2TdWGIbg4QJROlsI9I4ybi32tdA1qiOSHXAMUSqfkur2AYPKsxYvXCl4q55d1XkDSjlOoFCHN6yD2h5L3FN+6CnGEyOi0Db/e5byT1YD3axv6HXWV/eke7ceRoB/Ldm5TXpPo57bHCHtYOE8+zyTlULgWe8cfxyF+TjY9UzkiAoZJ1C0y1EBRYx9x7S5st6OIsGW0Fq5XI8/kGFjtamV/LCS5zK+PORGICoIG+9jYAAAOiI/CiEYTmyNIfclfmPtLJ3TvtNxQetLQpIC9ew3xQt4ru1qu5Q5Nk6YVzXE/OPqx+kCfEoJ0YVPHHBOnqJ10Hla3UMZeqb0ihoUAxiwtutWyRbRkpnY1zEifZFFm4p3iqC6lE0w9qvOIiPHaf35VADzmswWGLYHKfZRVwZVD0Xgei+aqfZh6rhxM8xv+zVl1Efh9R671rJNoYZcnnu42hYKhSAunZyi8C482o3AXKojTg9aurmmEjC+n0/weG8uWWN2B0EBTcA/SpzreJtDxxqXrSGDBDgIyWTjIFb7w0UnM/gVEKun5TFC0rd62vrDCjzSOqwtCeY2DjS2rO0O/idDMAAABdsqkxu3fsE7b1y//fwx59LdkH5p3/z0YwTFB+mT4KiItqXzPRaeQQKrf92EpIWEHntrdfo8OSIkHFxtv9EMBrKfT4hwq/cTdTM2bNHZ0N5aN2CjXrENniiElGkRqmLlEslhbEZTy/aXuQZ3XQSqmv7tOn3TySG/3d8S4oK4xX+l+YHcBYVo6l3rt8kOLRWukJXJXsr7y9O17kxX7+eMCQYK3Aa3nyFLW+/dM0S3BVPLw/YLCs98awMXJaj8EuoazMdoSI7f8Ky7uZTtsHmDgce4zTEJcioXzjuiL4AzTCXF7XvVmCmdomzRDV7AX+wQm56uJXbyAKwc0yH8HwhCRuuwMzj82tL0SjqnIPEdIjMBNp7gFNESgtA13FcbjIQaL8u/k8CAAA1dzK+CqzkUWg4BK4Md+JSySe76DbDB4wimMs+IX8x45kzbp0WDRqhjQPkV3EBcnjqWjC77v9AAdzlFYoAzDPXPha1qIDtTOy/MtJagRUsjV4mEgs6ZeI9VZYZX2IpayhyLxm6WcsFhMsICvXUC4XrPG/BkJ2o11Peg0aJgvNO9h3B8zB8uoArLANAJTXfrzP9HNT8NyhkHVNNICT9aGLr7FrZPOCwGG+m0V+qGRj4WxAL/0uY+htn7StKMdv5bbZEBB2vr9+D+Z6PyZpRMsGmh43K51dgXZmtkIe4hAQpVYk8pFHx1sa+U0Mv7RG5oBZi2/b0Er7OJI8lWqxoYlrRQq7oMJ2G6gilMJiFwsQVIOj2Yd584inmhfdn0XVBUD/2ck+LhLEX0fApkszbwFY54T5a0wFAE/GB073ddLH5pYdGYaQp1kgn3u939Bf4Rrbpv/p0TEdFLmknTsJ7re3NBxIphX0dVqPv3uUUHSKJOesb6UEt3rIGZvvEMaRBcXvkBVyXn3Xuu4rGaqvtMOy8a5b6Bjkg+TlWPnNCr4p/TjFvffkiVsmTZIqa69fo8LUDbWlfRRrzYUa0eR0ebf/9uHKk+UHbVSGMGD+8r7uIfZHSXFwOl57aAJ8uDAAfz7zDJBc/mnOmuEcBD+5eGlzN7WAv7PVX6+ISLZWAABMxWDU7uaqpqSM/I1/Wy9pUhHmn85HSCPXUzzwnRFAjPrd9B+eSpk14Vf5GAd2GMFui0/ejdra8sBQkJaqcSN4m64Y2FPjip2oLltMwrdR8z+yFyZ4Z4MeGbwWW6SHkUSY6AkI45enN/LiJ8NVKg852uhM2dHqcwfk911sp+6Q+RqZcGVYxnDtZjn+ZcV6XDLsCYYBsXLxH2W1ImO1hEnXoIURwiXNmWXIYve9jQVAXl92x4TYNueYO+gZBz6g9buV2aC0detfESBlo4W8fsYVyDf1HuYhVlPII7vgfLT/qYYtVqQE8FhG64Zkn9gVqGHoX16+I89zT9xDHQBhHWbvB7ePH6isGlssnnJPP51EtFVtVjUmF/D1MEe6Q+OPNxSvSrQ3maEDWtYJyt4Sg2cRB8hnS37I5Upk0S19mxOQMwoaTOyM/O+LHGM2FffDK+m9txB4s3Bk/qqg6FtQ7FcBc0PSMH51KVPnYGPfL/usLQAyzcFpuJm3ts7dIDzn59GQ8IULCMNDPne8SF87sVYL/CGkFd/f+bPN7bfY/4O9zMWByvvuWuFB+C+ElV8+3eQZvHiZ0y2hIxfH95tNOEvsRc9RkXEC5M1Ze4+b4gziuOLqNg/SwaRYlhM1nufHw5leuM/aymUyKnzl588Z+qMIho2/LLXzDtzn6bRW8dCuPcyksmTso+Qj+wHSuXSOn/IHMWOnJbDiUnMFLaLNsQNXzvP6gKHTCO2MHaq/y422uo8hHTTXNaN9EjEYKvxxBkcc4tI7oxCHMj7Y3iauRsAqPspI90Bl5WgAUrE7eT2qIfm2cKJdxxiq867qzLUmQdS2fYVeELZ2Rzsg/R//whp856+uRYBg5uNRixnyt/4j2NO9q+XItihmkshZca2fzpEUTYCVMyCtp0FF2FL2OL4l+yd8NtH+HFrXWeDQmLwKxaEXD+FY4YZThYwGAejIcIEoztwzBBlT63QeWllJmI/ttAAAZTcY1kdYPVby0FZGv62WxbXzJot7A6QR66meeE6dqMhIGf2eUjHe2lBAZPDq66DvJ2oWrhET6Bc0cGfCnhMTN0LLkY7/6wRzXB5kl6a/KAu0+vILs4VSD47MQwBKpEqhN0wF896WfSqRTO4lCJfJ64T3q+6KJ93G8q9F3QzKgAAA8rOzgSGcLUcq5DDusDJHwGXIUWr6P4feS6QC2yQ+WozC7aeJXW5xNoAILEIcocaI5lMl+lPo4lwM+z93QCzG+MP+yVMXSyvOXdq2yKjb9TJnqR8iK6ti7wXmdX4uFrWhEXJQt1ix/hpWoFSQvX2wWr+xqfh5nUlgBkQY+z03NWVMiIFmuhbgAbyEDv7Ftq3dfYXVtShIOmgzBAYbo4CtUcsliUIZzeGO6zzyr3hRQWtvOce2c28DuOqCgHeDwvx84GRY7EschVIUmFOs+plFuMibNRDBuQhSQRP/4+uoQc0ZD0NFgXB9nYOG6ZsN2+xTKRnalGTYQhqI2xAikmziDBG3F0TgS+Xmu/3ziFKQ3t/TW4sEBsGqBd7FoXtUy1hIy8lz87lXc60mvsBiDFZYRk/CVj9cHd9OtS2ZNjhFTjcBIFDAczA2T0bFgAAtWmk5BZeU7SBjtKKyA+FsW185ghf8OX7Kds+p2npGrUT/Uj4gBV5crPzy8C80DID60laDPWwb9IG/vXKyeW6kYMd/9YI5rg9vg5xHvw8O021jFV4gWP6aEveaVL6R2+qyZEtKfsp2z6naeX84oS/V/c4HdjtwQfvdKsQ3GAaYDYyDaC8fcJsPbpIZmQmtgL4gHtxgod7ZocpT3f5DMoVvPbJdHx3740FE7/I1TBLlGG/C9+cBuEUj0tTehSn95ZpLZnky90hvEg++YmDIPF8JaVOekILCp6QfoYqom8rbGi8kCMaLF8bejBLWRqzYgilJxVTLLttpsKGpon0SbPajBtVZQOS8pclZVyU1G0Ekjy2PZfDWOKu7fyq8Kr7RMZNP07PvMzDzSvR9kv7CBwC2zOb+I1NhxRCizI3hB/megp+O34l3rgsl5kDsEiZNZgI4z+TYDdcUQeqqhmEk/clcaT2M6KhAiyHU2z8X7UW+sKp3hMIfCdtMp9zv31ZPuT3Jidm8XYZQtce6wjiG55gYOWEnf6atOXnXpHoHRbAAD9NI+6Re1H0e1rZ8kVkB8L2lSEpPc76HL9lO3L07kieUyaAKOXeVN1RCyqIn4xVzEpBoKmgsmE/oZv7VjLkoeKHMeAAh4jUd0CRAelurCLT0Fn8jt9U4prKVYv4kF2rEcP5T5MQhkC9x9OTnL68/mYHpf1/H3cZAl9v9vZ4Bn7206T+Cxo+RkJfZZa+XlmFTMr4rc8CVWqDb/1Go35Act+Ho5LIoHlCrjnBQhCYhsgR2cJ1AoI7J5ZOS32x33EHpqQq4KJ4fr58IjfqLeqvEtKWddXjpooMono6duWQ5Sjgh9g4hBGkZTuJm2PzpNzISWd1Bri+VB1t9iu7ruInj0ntxR+PPenDtqvBMigqhK+/9EZJJIb8k0aUnJHbujMbBlTKnwjbnYeQvxU0QvEFrV5No1bTonmjNOlMb+g2DkEDPkdUHz0SLkDelJJV3ARrYRednEOKvsV11GRFSstNm68qfvBOV7bi1OVVo23LkggRtorH8MHDUbMaNDI9GjhrDZQF/2HzpAAAu2m2z4XtVTVxN86isgIFL5kfMmup2Dl+ynbOwcHzJ835RbleU/48o7XSXb/TTKAczFhV6HcTmxp+cQL9H9kq+O/+sFgJgrLASOATVCySp0JwpNNa7QhUb3vWrrzvuFM0jfJiEMgYgvpyc5fXn1WtPaGUoCeuE96vKRKsA397adKd7Zs3rJZk+TGSAYTKddqGY54FDGZjFWgS/u+3dnD0clkUDygUP3Zv0O7sABrgkCyI+hJhW677Y77iKYJCvfS9kjEFMoZJDUW9VeJaUoLEaLAq4EdZD9qPmFcRLCVDg5FekjxpApUY4agWM/Q5FGqvm2NQTCj61NMuzqhJT/bcSzHpXwI0NqqdK1FuQOmeuQMRGPVIa6XLEjUKtBsd/xElyQbbE/wAGJW7B5pnQVjn5kTAswX7qW7u5J0D1cEKpyfxQ46Cm6WMth8zc/Jm1/rasnKMictZIPVZ/P1kVwd323pX0rj+DXhRUNYc2NIYqDU7l6rAV9jVZNAosJLjvfd25UGdwAA5lChnlicvW0WASka/rZbFtfMmi3sDpBHrqZ54Tp0XmeqXwIFC0F3mrjtm6/osHzMWnX9O4/EcDu/7SRG8jtIlfWXHf/WCOa4PMmTsR8Akh5FEmOgJCONRy+uCPbEQeY1/tb59kxCGQL2E/W0KtQVnVcR2QMX15/Mv1TsFTmATXKrzKi0FboDoYZOxPXDZm+NrMwKcnPAlVqqu1VpusO1hcMPRyWRQPKFXHRAp/16Qiu+oaTH2QS4GA0sM77Y77l78W3nyXskYeV+YCKfdMdRbs1Cay0VktAV2OAUz0Secj8oJ29zZpDXOOORIi9gyZw2FXwbVk/+mbawDrYpAPey+rV8SFDfyw2sMt+cm5PHLtKQLmJAifJ2xb4vwBCvxBRpzvMakAkomJFvx7ZZaAA4BsfLrAkXaEpNCq3audCnAnqZdoLZOnQv/RHSe9Y4x5MwLWewRd/i3ZgQqQZX5atWpauvCnY8NOzz/6X9V4CtNpy7jzxo1lFc90W5h7zzEAAOpQoZ6D3lFEuTD0jX+IoR4VGUCLewOX7Kds+p2npGxDe3vRNHd/Hyay18soWAMTZOWXbudW3CTYj816uxE7/p/VizHf/WCOa4PMkvTX5Jq8y074QIKwxXprKiljzpwo20oPgiTSz6VSKZ3EoRL5PXCfLpTXO1TXWyo4IMilOAhetSqfnnL+HsiMOtd/9fDEXe4VstaOeBKrVVdqrJ/JrfZJw9HJZFA8oVcg6QdUrupbJzxQeHcU4Hkl39sd9y9+LbwF5AcZexqtfNqi3qrxLSllM4zNXIAhCyQgUJDSE+J/LorF+QCJdD11RXkXpa0K23r8OgkHOUrIo3Xoy4GdgxEXgZN4UI9F5qPL0dAMJhUUmyOsLFSp9OfT84YOw2zjlSfRUTk7yRShsaDVSX3ZtdSQY+7nkNuZSeNfIk7j40SdNM4UYYrOXAbRx/h9daFVtnh3XG8FcmMLul8PQVFAxxARP36j1Q8wQmSVeaYkVb4hZhGCDcIAD3NITOqfUs3bZbpKKyA+FsW21JOqNFhy/ZTtn1O5IoR3c9qq5yAs0pPmNpMATqegzEvnsnznoT2ODfu+NtcaCvi8eAAh4jUdiZle6sF9P40Paz27BlQkugX5c0qYwQXbMqLuZNIqoahdeLtWZC0iRWDLmuvysSckPDsnHMeAeey6wgPXN5hKQ5TB2EnVRp+sgz3JlzwJVaqrFEIQL+VDzjD0clkUDyhVxzf160/sqc1OPRz4Uk/igskftjvuXvxbeAvIDjMEtVr5tUW9VeJaUs66vHTRQBMeYTeAVhiLQho7N9WGC2vZmoFCutCksOfa1IjzvQnGB7XjtjClkyqoJ2shSAETz+mBhtitgNDVUg5DITNYz11jOjNvfrYkpkYtNq9/F3pjSyFnZ+kYlBWphQ13bwwt304CEDsBppjS05UYlRKc9gy3bTv6qQnezPWpC3Q6EGqIGkKOQWw+tl5nXg8w4QosdQkV7eXsNA/gcOVKp8+BufEAAAFU4XYZ9cdSXsuQL0VkBApgsRD3CxzNBy/ZTty9O5ImmNfer+imRDY1ttr5T3GNpuoyTbCJgFzX7Nm+m7y9lWJ+60ybq+8oHJD/6dpSvp7SB1n36dI0j/lflgZBz79OkjlXJiEMgYgvpyc5fXn1Wvy2mxoQRLEVnG5GkaAveqqYfihGoA5aK1K1iEc4fTjTiPLEakanzsogoqEDUoeKKv6mBKYrHMlXRJHMfTyrGd6/r7PTxp4yYHdLC+AQJjwDzc2MxFtpTPh3wZMLch+wcbA3rXfStWex8CrkrZuBu8l+nKctObM7yiTmsGqEfq2KRdo298MIQXxKqXc3XNCgYNa1FopBD92/rQUqlaK2D2D7sFcEbRj2Yg+FQ/onN95sUHS1mrg0lDlZzi3+IxZ4eMYMZniY1y7nbFL3pAuF1rbx0nLpMZcqjGxMgNzWNuunB9oMxxzXa2ldTJ5wAAIafy5NcSUJIwrBUjX9bL2lSEeafzkcv2U7Z2Dg+Zg7Ll9+RrK+JKP/+lnvaQC6tlr3NqauePW0rUpHywcJyxMd/9YK7UdhraJfaaylWL/m6cVKtEUKHJAjQLunIQoTcESZMQhkC9hP1tCrUFZ1XEdkDF9efzL9Uyq0iAHSJcrolwu7jJhm+0X54EqsJxMR9mwHatejD0cXtQyliKLGd1OZcpA49r3TxsmrHd2A8s6+z08aeMmB3Swz6CmDArIwNlCEggwCGDHVNsd/2iMZSa/lb+2lmjY2OdcSza1rXVK1nmnPhdFG0sYgWgv3D+gV6d0WuFhSwsvAXhtcrQqlk7oh2jeg+V4gS2SG5PtmAyP8u2mX9xKgqlMUFbRuQYIXyO/K4YeEA1Ds621I9teMw8MgJaN30Oz19Z2mxyVwLWLjg9y5qG6QWdwkeah9iYPOG+rag9gvcsMvlkztAAE1FYNTu3nX3ow0ORr+tlsW18yaLewOkEeupnnhOnajISBn9nk6wdbNib1gdV/WlycuuRrWyVW3M7/OLQQKOglDwW7Hf/WCOa4PMkvTX5QF2norC2J2KnlCijwgeav3QWAaQ9IuTSKqGoSyBC4CTUjlW+6R7kP941NPmQsT3YAtl/26jgsLg+aJZvWUxM/cvjPxkTLQZh78S0PRxe1DKWJZAnkdcb+u2g4Jw6/R1jKqBQ+W91MZHaKOza5rkgqk+U7mc8jA2UISCDArTzeCNur1TH5RpBfOFV1zYGV87UbuFiiK9u5qDh5DTE2631sp8xgWhU/KzAZ2QJkSalbXsJrn06TYMpoltgaTSo+uOYAKq4kW5JawRapqluj9NR7SBk8/5l3nMtpMEAbc/YP6WRfEn4w2FjhL+0ulBu+sK5YddAACG3tNSZaFvN9zORmD5uXuLchAivNCMYcqCfqKwdaYUrAABVcChJHYVpoDc03CuU7Dgl/m2TsAcsDnXr3qGRjfL25ynexYuDKMP4rICBRwDQNw5V71DtPZpkrkN6Cuyo+l1akTu0pYJPxbSS+SOGb9Bqeev3kBy/ZTtn1O083txcWgSQYpSOyNrqVjTRErezVC1Rfkh6qA3J04oab6hAhwGfm039kYGlCZW1ZfHZH8PRxe1DKWJbSUH7OW3PFQ4CdlfPbLfjDX7fKgNeZjtm5W5JF3z5PpH26IkjA3cBBFacIGIL55WPkxt+Qax/p8F2TD4g6rwX2fM7ILyv0O/oEHD3XM9nLb947i6v209OngpnGuvsD5r+5H7bcsBE3TlT6GWsUtdi+PUWuFr38D4Up2f71pcqiiF1Y79z8pvtZyU155Gm8f5vb++VZ9fQsthr5GgH9Ic2x+uUEGGA1vFethRtkAABGFNqyCVopGaLePsFXN5wEewp/wAY0WkDCTB/7cuQMvep4tj7lyik72U4nGLO4I0yEbuEBeyI7Q7H4BmThFZYCp09Znk8rMjEF/WLfeWapB1vDkpuqJ1+WzY5d5u6d7UAD+ol2bQPN2rwUvdbLELNnqkx5PWSHBk6ZR8SLKpAjhoAZssVfL7D0cXtQyliLDCOLtf3DMmjoqX120HBOHYrlrVe0/C1YdNMJoJiLk3kX20QqIwCepZ7LS/smqAg5GuXaRd2bPtmmKsTAGHnLEXJ1AJvqKuC//wKOwYQmLnKhQ0KyZjY765JUHnrgD64K4e4wo7Fh7fQhy6D4r/imKZhFG9uWs8ZKFkUprJIMvRcnFIOr9OFAInU0nHaLCvv7YFz3I4T4aWR9L3D0DvigABGYJNEGJXIzRoBA8oi23GijBkpKa1NNAYX4d7VpgPNw3dOftQtf4LHBJ/UWD8+3z0L8cK4+1hvap5WN8zcNlbGA3G8l5qP326FcfRxcb8/JuR/uxNTgbHGCD4sfDqp1SuSsR+zqcDYPG8s/+ObF/0X4hwmA+VOP4RXDPnuYT6I+zs4x8sJ5QtIypG8KoPfdpAMTVyJfQN2xZvL/XimgfhheFXjlFuexQcE4a4bE+qCDSGZNBGo9p27chfzngSqwnExH2bAdq16MPRxe1DKWIosZzgZRg1YiqUqLAhCCk2vX2enjTxkwO6WF8AyMeAebmxmIttLTgLdKiKVf4Fa0HhWdC6JBNJJY3K1kJqXjCFlC4wv7g7hW0sD88XNdANndBlkWkqC1bEXo3G320KWSiTEEBUnj/mMYcqmOtFHfGiU6n/W0oND/vK0bBARMR6QTTC5aWuUa216jKbWaW9Zrp+Jw6ENtT4TYN7wAAR7KIZrDOeKOt/1FvMxh5raNOE+PXnVwo9NVH0w8y2oxkbkX3QEvt0mUSAbShFLzWS/MU1huS2uP90UWFWMUHA1vPMv/GSVumAaw3JbXH+9NofsVeXNNZzgGtsn4AGEaHJ38Oxru2saf0yHXPecnydKu6uzxqpbQri+Ck5xhdbv7o54EqtVV2qsqZbKG2Vh6OSyKB5Qq46IPHB33/UAFimogpQfRwytFg3vtjvuXvxbefJeyQB5X5YCtqLdmoTWWisloCuydLsRSSZbwZFXBuEed3lTZ9F3QFSY0wU2l6mQgRrJuETSkzblYEJMVVrJp4fACZKkL2uagssFapPdshkXTK98IA9VEMGplzz8B1ogwRiablvA4OBfzsuH1ihV45Rbnr/fZ3PIUp2i0JtuMfR2FOsEfci5m+m54EqsJxMR9mwHgajPh6OL2oZSxFFodYpvNleaaGPHvXlS4GbOz4Utar2n4XFrdWZFllB9BTBgVjwDzc2MxFyOGFuAj1OGH8IgOTz1V/D6p7mMvjCWo8gYKHFHG+5dcwgDN6B0i7JgeZ5FCJJXyPVvzZ+KR7ztGprC3d+O5ZwZEbuuDYXAFIkVtO5LX4gaBPPUcAAGy4bJ2Tn4YZxkH/VoJgz6mKNxwRINw6vQiYtXDKuCJdBvmKEV5RHn+Ud+LStgofWH7NSniyetH/MQqm7B06xlse/mwRTFR546KAvZEcToecHSmu7axp/a8uSJMI3Kh6Ah37MCmFa12sR0XJTcclyRv+NHUd2IkzhRqotU9wUw7unPAlVqqu1Vk/k1vrB4ejksigeUKuOb+vWhIvfCGwngYEW0VNmUt9sd9y9+LbwF5AcZezSKfcS2OVRb1V4lpSymcZmrkA0X/88yubsEOGljUHtCGx2pq5LnwzvnWpMGZeHeDPPGYT1X11NFqZC65Bkz6o997C7eHX4tHmTUnk1fjX6DenwNCcNcOwgjeoGnexkJSNR1BmL+K/ltDcSLc8wHnucbMCbz0cCpLz9pE1S+u2g4Jw7DspxzBlmCdsUOo8SnneRJLk4eji9qGUsS2iiakk9ZdyGFZooOA3qtQdzOF+uJJJjVR0xlCZRR7wJWPAPNzYzEXJvNnDKwNeN+CfpjRjaQaphCVTqQRQNnN4SnShJ8U7uQ2rV+IEYYaQNiJFMAAL6Dj9OuDWiDKAQ6DninPHAR7Cn/ABjRaDP2PCgeUMoRxwjfAQrdRh2Lg/nn4cLziwfCX8lrnE1KeLJ7F8/o1lNmmKimV9bPoYY3kvNTfJHDN+g0mWRdJ11XqzRbRm6Lm4RPdO9qAB/US7NoHmpUh65PWpVPqrQ8gfgaKNt1V4mXCuyH6fyHPAlVqgli28+S9kgQw9HJZFA8oVcc39d4z3GwFSOOpA91PWU8wLH+2O+4g9FmircmEvd5Wgeaai3qrxLSlnXV46aKAs3v6n9pmWZX6H+OK1uppw6/teuXl+RNJ8GxYrbzmr92pIQJFueYDz2pfU/B0oVlW8O0igFU4gVnAA/U8PrOBo+nAwfrFvSCHmxbANZDzFI57d0BUl6p56uhSZo3fXi5T9E7Bx1ZZ3DxVWD+wDpnigh59mfyd0cno7/K+VE1PnkrtxkPRzHPb/eFn52outliFmz1SY73r2pn8rO7i926IBN/FB3rk+E4NRKPpNyk8x4B5ubGYi2/KOB6HoIQqxozsLoF3v9Yy9kuXz+UK0QeJDYUZKgifFZxH7SUBq2P49sD2mhsxGRkDe42KJHP/i7njeXD/4eOZP7tw+yKKXNBG7didWe7AfVMclHGSu+mx4u+SWr0N4MRqE5n4JEAAF/6zCGy/dsFot4/UW4xzKdJ/fHw6qfvP76T7rwaUyRCSvYbLkx2Q+55Tz/KAEm8rurdAdfSa2W3OV7T0HUaz3BTDuxgFo3JbXLhYb3skfBn3nWjvBmq0p99p34zsTmO3LJMRyuYUOuq9WaLaAIydBd9wCin8aYFwmzhEYkcvewFP5oZ1ObZM3bxYjACXjODDuv2x33L34tvPkvZIw8sBTKJypEt6E1lpalcsj9/Ak2I3bg9nvUQLNdx9f2bqTia8mD6NV+Ra1G3OmZN3U1fYMVSm6Za9xu+D4bpFrUbc6Zk3dTV9RUesmzk7XxaSij9eAmoC6Cp7PFwAP1PD6fIIHX/vowwfjg39hB//ugi6EVITvHNIQyRm1UNbAXNpRXbQcEq1vPbFDtxmC7ug5v9MPOx9/PR/uXFWS3kNKQxEkrMuNCd/Ja+z08aeMmB3SwvgGRjwDzc2MxFtpoEeB39bHe796aQ9IoZRI28ceZBe205pkbO6hZISLlrWyFdDJ4l5y6oPkO6gkn8FqGyfZ+38iysGApXqQEMhGplNDAQcnaU1bDrCuKZ4FBPVY6+Elw9M/cnsbKCtAFrvZWbQVSN8q0imFqQdM71khAtEYjQlfqQqAAAADEGFbeovJU1WEAPfFCTrGR1odpPwLlvAcvUWTcImlJm3KwISYqrWTTxpqTgsxGGC+cERVOIFHz9ydr4tJOafjW3kKoSTwKzgAfqd0ZpTrkoXrsisT0KTNG7wBCR5VGOBUC4SicfHDM1LcA5mo7ujJjPAlVhOJiPs2A7Vr0Yeji9qGUsRRaHBK7ZOEf7QAmtKkKr9eE7GTs/dfZ6eNPGTA7pYZ9BTBgVkYGyhCQQYBDL3nNOMPIVzu1YeEE6ZFdABGLBw8lAzbZR/Gek9/rMckAdVoXAMRBTf5daEHQMCuMztPB+gJE2HaVvNTLqdLxbeb+YcBb4NhGllbN04zQqyPs3jRjJ7QtZMJGLhjFmK/zDr4lVInHOzkbUw7C31hQxBhAfwLbsd9zzgz3BY1UYvrKIJCTnPjd9OhU4M3V7+06AAAKa6O2U5v1vmRjRJlhlv45ff89ArB/rRpcGD2ZAbdoM26mr7gL6QhLLUyF1yDJn1S+9TIQI1k3CJo/A072UvhHUadz30GtvITFdvZ4uAB+p3RmlaKfiguti4GJzGupirzhVDtgJTZOkBnLeXmmhjx718J08rgRSrIjZrdWZFllB9ADw9HF7UMpYlkDwHxJUsi/2aP3JOw966y00pBgQNtKV1twWuBfNhONSoDZ5GBsoQkEGBWpRTXC961GZ1C1ehAikxRJEWEyJLZVh6XCiUP2yAPq2bRJGej+MoG250LEint2MnqjFJbhu5jUv4bsay4y4DKtfhmznM+8rrX2JzYw6o0EY4RUfFCCNHb/5CHxls3oZAq+2Dy0kSuqbeqmtkJhvxbQ8+z4PKOtC2C6H92yLXHNZwXGSJd/vrSbqTPCbC6aTJbAkqp+LpbLzU6mkY243QARAVhwHdAAAYRrkQassy2y9OtNeuf0wpzrHnAtlRWvDZXKwPGoiei8Xu8mL9Py4BSpbIaT4NixW3nNX7kmCUdwr1e0NfHGhOGuHYQRvUDTvZS+AoeZBmjzU87g8tNrumWvcbvg+GYUka8zyqTiTWFQDqGm+oQIcBn5tN/ZGBpQmVtWXx2R/D0cXtQyliW0lB+zltzxUOAnZXz2y34w1+3yoDXmY7ZuVuSRd8+T6R9uiJIwN3AQRWnCBuIIi1Gyrqrcd09jMbdtwhYl7grQqH7xW1aHBsDhFo3na4LnfrSDne314IH1/pSPAr3mDQb6s38a7SjDqca1W7xsHsPV3iTaY6CgeXEMvwXSFD1R3X8X3shOPOArCCN3W0o+1z8wMn5yS3+HcaN7RXYBM6SRwhnT+rMbDzMoWTaljxYyPVfBPWUOE8ZkUF7ndtu80QnEdK0OrUbGYF6B6SQAAAIxHkHjzgLby2mcblxWJk71K4NwhHHQlfIHDZXKybhE0pM25WBCTFVaztNRirR1a/jwNN3krXpLW+v0yfFfQ9mLUY4FSYS9po2i4AH6nh9Pkb8CSR5MS0BW5hddg9X+69yn6J2DjqywQTHBqQ92oHvGq/sIHnU3YghGNEfmv/EJ53yfP9MPRxe1DKWIsMI4u1/cMyaOipfXbQcE4diuWtV7T8LVh00wmgmIuTeRfbRCojAJ6mgD3vtvQkC1JYJZAPxegvOavQg/wUkjJLisLPBSQYzZZEyLZt0yycPERmhxnHG2ABaRCIg4Uo0J/x5fL4813jZV10N+/ryK72Y6HUVjohHIz9wF+RmQJ9T1X9OKHqibs3KfThE0sen1Y2/hTnapzXWQz+idCPzP3jkcvUeX6t2ebXr5LRtNH8hofe6WXD+8IQORzhEzXXP/yusC0O2INrpAfjxbY00rP0mJk/JC71ofn9DhvRdlVbDT3je8AAAGPFRM5Z7D37HDZol+gZ0DCtvUbHUSawQOGyuVk3CJpSZu4vcZ5gPPc42bGpOCzEYYL5wRFU4gVnAA/U8PrN4XtkornUOS6IHjYDbjXELW36MAwZhZzCWWpkLrkGTKcxtkornUORozUCxkYlGw01sY0xfPAlVhOJiPs2A7Vr0Yeji9qGUsRRc/VSVBC6aC96ojSarOL44akR8cMzUtxS4KrilQ2HhC4IAMHMhbWzefUDEWNx1dklZSFvEM+uL1BH7PP/xor5s1yiHV4UWqPKYAj9L+DHHvA0mn6qRE5sZ1yPlDMNO8Z4GkAFw0CzzTswnB2vCg782OuUdCyRwLLNGFkiIX7Ycvw/SXOYFzoan9jgQFsztBLxtaqFHtIPTyOT3eGAKXBDdHfxix/A4Dy5O2Pt2RUMN8cdBHyw1w/irgda8njWJsAAjuqzYLWfAF40N1hP/hW9LgEJkpKD8574cueCyNSfgxNL4MLuZ93VP2osWOhYXuRdda9wIeNy8JfywV0cly6UIWf5VYdS1KW6XL8S8weIRLC5X+AFdJt/vh8/e1QDZfof44rW6mnDr+13K0NkCcFR/1e/e7ur0xvyZyyunWnduj4ssFuMHlFuex/t7MgNu0GbdTV9uIXHbyREsABa2rx5K6c4n6sS3Ay8jE03LeBwcC/nZcPrFCrxyi3PX++zueQpTtFlBTzC64DrWxCKSmoh8TnolBf+Bwj7LNMXdjSb1326cCm6/YOoGnXmyYwbpVjW0a1r/lYBVjz4X8JFNh/OpbFsJbn3W2nbTpZgpHA8bAHwXTo23+38RMFCiIAAY/FlP5JpSG+ffclS+xgZgJhmoQm3Q574cueCyNSfgxRtysLuX+CBO+VHaAmd45rF0mhARePWYYCFQ+cDoH78FMwoWmheVx8wnH7gETiywByA05G3q+EEicaRp5SNdcxHC5YDYO4ID8qIBG0pY1zKd78yoxDzh+aOWCP/8zWVTyjiWNjxnbx5JB2c5yeMfJotRVWe7pRukWLSA6DRNQao9+p18jXGJZgdquUwYXYb9+JYVXCpTvsD5co9pKDRUjc7cwblpnn+uar9Rp0w6R9UWxjRbibWNcPl6WfsKbb+shCm7Wwgdf++i+e5bPT+NXcdKG19uj4ssFuMHlFuex/tGEDLw7wWNmWZJyHmxbANYz3nQwibSV1euAOpkBU9XZYyaCBjCtvUWRTa7plr3G76GTzlc1+fsP154vCGSqfWVnQu9bjgDUTR0IIZcx10JBUveF9232BqeRo2Rq6POiESougGenwG4OlNn4JIzyjfu+BiXHD6M4BSEYnRs4Xw6KncreWlVI9CDbsmR3jdOf9opItHLHkci1qAoIAADE4sp/GKlLkx668+w4ompcAhNi11bznvhywnC4vIPDE0vgwu5f4IE75Udo7R8zL+fVlD+gQni1liuOA5g/ahB0D/yvRA1d4xRMKShiJ+ut05gc2gPfIECd8qOn6inPgJ10HFRp+Q7ImSS+Qho34M+tXTLR6D1919vnlZAVtLSEUupZT2/r7yw4FDYLbz3KVQaFaMIfC/bAybf4fcs2BKBfB8l6q5+bDkqt8P4oxFxM7jB0pWOq8RmL3mtOUVEVEldEekTIfw5pGtNqCpK6npE1uKk3G/2XHLP2DHNpFTWxCHMmUyhdiKSTLeDIq4Nwj6KcXR0jFAbdoM26mr7Bioi+qAZg//y9EHyZSBw2Vysm4RNH4Gneyl8BQ85rGJ3PIVAK4cKx6ybOTtfFl+ynN+t8yQx8BF/InZqtrT9JGqJtJXV9Ku7k2NH12r3iXUl8idRrgKJf8eX+nwM0SLODJ8+shu4RB65fRpT3Xk38sxVjAWw285gPFYKCMEFMXDKIekVkJwo72c0spbp0QR1OF7HxEDhlVNY3q87Z2PwC1IrDOmnOYhKYRCdCQABB9rZAk13b4Q7F1vJIg3TQ5gIbNfL7nvjb+HpYdg0QHQvKnsLuX+CBO+UbkElXraIeYwigecTNjF2bQAE0DoH8HZObPD3/ZScyIdOrBKK4SPtDoD2k+PyrdwBXBpbABxyWYOizMjI9eGUcmN+CigazWmaLDwVyEmQRNuIxFOaZc3ggqyvgmjJKhnIlUxW/frH/4Khfz1+Zu4CZtsWpK86IhLu3d0aVyBlQDSlDpdn4VG4zceBMSGkKnVaS6GrnU0XWKY4DOHa41Op8UFJg8amr+YfH7NrV42bFP3r3HFYUjmieWD1ihuHxsvnaz+UcHO/5g1/aZ2UMq14qJnLPYnpPvuWz0/jV3HShtfbpBR/9pKRg0Ozh3Zr0xvyZyyunWnaS1vr9MnxX0Pa8j+hE2mugqezxcAD9Tw+nyCB1/76MMH44N/YQf/7oIuhFSE7xzSEMjdybwSZw8qA/WtlsIpsA+WPOTRCU4bKGXMYctNIWBrypLQwcDGJB/gpJGZe/dJj5M2X+ag2qqUMj0M3y75uT85sHHj6hFg9E7BmeavLDQ3kCqlRQABwbfkCnyK7AAAcI9MvqefaBWI4tpDUojC5fhHMMaQQBjW4dZ2DXdSoHe7OkTYvtI9M6garcOxkQYI+JbZUDvPNAn1k+ZbzSfXYi52EAIV5Zq6Cm4gXwvQXesenlBgIXvf1k6DlCL77yP/dreyc/wZ/mnMjfaiSLN7m9fjvxcY6HNIPGGk5w7tCiK98pUfEsh62G3XcHkIfiRAG6xpJKa2dyuw8xRKXAsSB5c3A30XgsMmB5GNEfVTnpKQ+afPbVo74452jK1UQp+1cr1o4E1SAMHAOVBvTM2VxGKwLylXn09fHO5Di3JEuq37Tbk+H823sgcI0QacRquGX21f7cMf82+wAElINOVNc2toLPaZlmV+h/jitdstjR4qFwxlOpFvICm8g/IKh5uA301OR2mEdOVMybupq+oqO+IujfFDzmogmQwamYuS1mQFT7k7XZMxNwbhHrEpiYXXYPV/uvcp+idg46AMBF4nE9pcBqd8yoLHr+F8ViglqMPo5rmMu9OVXh1YP4cxqsVJ4yyvx5w3VBBkCat7RiD6KSfEY1kQNs+aEBdXNVauWQbI7op2MRlfUrFH23bR5Apnac6jjPrMsl3jEXJVpBELI+Fxs3CNZ6yEAAAeQC9NHhMDvnNzZHxRqaV5DgHD5uV8T/lezNIU2G3NAeNDlokXHG8vjjV8MyOpGwc/GZpLJCsF+Tk6UO1eq/6aAXuH60fQu6pbpBi5Zqv462B+HOgysNZjBM4fpt0Vo+fAH3qllfYLS01j5Qhjc+p1nVSRrDWAPP0RJ7gFAk/cWjqYUmwNOu4W4yx+xWkWXDY05aZMwRKwzjam9Ma+epnttiCTXi16q1f/O+u+ML2UqA/LbvzsvmTsjpDEomAbPnsJTUqkKD+LtYvpoBj5lueRDDC78xLRi/XxDDtPiRx+L2zwgrUfoZ2wraxdT5B0ydmEAKmSW4e4barmfYcSk/RVy7GnqRV79dm2yf6vRdZTQJK6oMsNQe5gNtddK3fb7eSeGlC35EcDD5i2NAfTagNdBJrXaXop7er3I9ls9P41dx0obYEAXLqt2gPeEvUdN0WVDuaH/UgStZ/xJ3h9Yg08DwdhtKNytn6tSImTT3jqnE2krnQttaanRoS922+PnXryt1j2spQyYv9VCBHrO4FPYWw/TD2M3GCtR6HwOd+E2zZtcuL54bzFJJYTmzjDGP5YGLJgRWe+LSgs+D2ZHW3sV/8+XsC743g09uegoZH1ExLHMOBiDc2aMwR2snI2uS5c9lIf6a1XoKJojqFKqAAAYn2ZCJJ9WYIJv8i+RDL7ktMnGbV1VIqcqMCaJwgNCzVr65RbJmkVl40S0ZTiASg6rTFDj4t6Rb6U/iVe6EBkNLUl8k98wh5Zt74LA8MsNxqUrBm0Q8au7P/TrD0jBqnqIiUMJgVylrtlQtA+6F5dyN9+ef0tcSUcumNug1Gg4e6MemAyY0bpibGatOzyqEs1e3EWiQfwK++Rj560A2wBLS6HSpYpKcFMc+XQf2qRuQZHIMMZSEAtq9+VQNtLsdh3cOz1tp1Ma7K8PmZIrZCqBBjNpay/vZ79NJelbhLqBtoDdwYb7s4kvP7zAajIlogVa4bHYA4SOoD8s6v6mlmngsvLhLAkL4S4IqIviLBDDulHQZHebPSS6xTQBsInhFp8ScF26ianQ6jWx5Cg8+Z+VeZV2GBzF9XEkXeM7TlL24K2KtWc57zphqnbCLcL6GvdGWnZuj9X/3CwoF54D4U0+dGMuy4qIi/HpG3OZjXJZpzdJfoWhBiVDc7s5neEmeKfDyI5xlqx0YamwiXWc+zyQZgMsuUERVLMt/qEgHL3TBaaYep6wRk2nydVPuuDaRQwAVbU70Uvd6RoKyL3tkWi7zYwugz6O847MGsFaoM/44U0r+fnqio1sTvOJCmQsMIbGIVX0Ch7zzEfBhXcQcHSzbtjsW/zkZlpOziZKb37x/vUYQHw0QgDcD+xe10dZS32zsuJZikvatomuLOdqiLuS3DeDqXCLX9+B7yVJJVg7S8LsueBmvjnzAH3h5sO/no/3qnkKPZqO7o3BHvhJN/p+IhTX+9xCgJv4oO7cZgu7sVf1EGBWRgbuAgitOEDAAA9HVRg3AbpyLsTUAbtWZ3bWkEZlb27KFL2WbE7/O/5Ynrtru5+pjyU18jVdz5sNwlb7TM/yG//hD8eyrlfeE8pIvSFLWgN/OIs+HqAzl9/XprA0KZ7HoA/7ZROFR30Y3dseXYFVSEPnZlcUj9GUJ77NV0HJXlZgukOQFJGmTQO4N+kHlabvlx8CsnGPFDyh2/MP9tUb3wEoR8pU5mev02F6344YEzfTeertXf1Mskti0ZabknL0lwrHyWMKUwxMC1Le0zQFmVoq7tBRQ4IxBuG86XkLnQjwiiVR//6bddkvT/HZmZz+I0EhNeXXE7GMxwiGW+/bKgWxLZZkdnAJ5vXQuk63UxZxSTeJjrY63GFBUF+fRHlD9B1m07q8zZh0mDsfLKWpMeQh/DqNvdCnhhu2uHPLkcbmK3iVDAhV7bCGnkXlWgd+D9ZdCkBjW2bS8rCJkQOcDxclVBjON2WyobTWvFVxZG1Yg/26xnJa1VUt0OPbH/beMlqWapD5rwIpcQNc2nkCGVkmvwS1FXlpRuzPcduK1wY42mfupsHxqE8X+JtS80lj+U0KsepYNV/ynEKKi3IAD89fu4vg2mLrOBCagAzgxOZIPAqt/JeC61vQ2D1Hu8qYmWz7OxX6RJLAXVnDuO9R5nrgM5Y7FpzEDfmrXFXIR79+S1iLvMhI5Cruo2dKEKX2lKGsXNyRkDETEc2ou/wF44r2RDVjDksc8f0nm8BttoDK96uHBnqFODBG5RS0kw7mhzBusWJUSaU0AOw8Biij73wogk8EFncJUZkoc4cKD+ywfl6ZWWW6Zdj8QFw0vtLTRzsN2VDk4G43EJDd9F8TbTrz3iA/xWWa3k13qgwjT/NvNso8fJN+xMkokhyKssQdrm2w6bq8F8U3CEa6RYo9N3I0wMyXsE49xgAB+gM5x2fFggFhgdAe9E+U39kYGssN583ZxNHssPRxe1DKWIsMI4u1/cMyaOipfXbQcE4diuWtV7T8LVh00wmgmIuTeRfbRCojAJ6lAANO7Xw0VzjxTqyf4fDHjbcT4pyMO1wq0MiImBvmrbNGMExQfk1zmHsNYCTvbo3l01A2s3ACwFHIjl4L3OBrwXpXGqZsRVw2MpXvs1t04WReUZB9slhZ+tx5yZLRAz9bGmZoSSOKTZ5vNjZwncGLQdLK7bgLc35xaUtz1v/pK7DHfXTf3X++J3FJPz39/6pu269zmdpJqmb8QtU8TXsxj5GCI/uqJyhL+0yvdFP2IOdshdY8wagyW6U86Clbt3iDifhHzlHonAF8HwX/3ZZkuV5Cd65Qhy9lYRqcj+FvJjLuvHOsADozrI7gxnngCE07P3E40AXjvAAAAAEH2VuejI7UWrxkhfzngSqwnExH2bAdq16MPRxe1DKWIosZzgZRg1YiqUqLAhCCk2vX2enjTxkwO6WF8AyMeAebmxmIttKAA2HSGZqPFHze2Sh/Ed/05f80y3ppeiCc5R7bAxtxjcpRjxjWp0hFHaisL2TCb08jFfMDO1Ss4H/EH+CkkZWr+Gza6d0q/fh1wFQ59OG0L+vU8H2nAE5s0Wead34hLCsuLbQp0mIGsDKvE+EkF/sZMc1iVLOpe8I/zB5g8oAAAwhwq3O5WX9e6zoQRrAT/jfKoeRrTx+eBKrCcTEfZsB4GqEmWKNF1hkB+xTF4eji9qGUsRRyq17qjTUoCT21KWiFi44AsxoQDNteG29aosjop0qiA6kW2BE826hD37+IrBTfZ4dKwOugAYlIj+oJRJ+/fKgR5hZ8b3KJQdJWPiBHhUSSWshEJBCza9FsPHJkxSUHm39qt5cmabBbyYrANoopf8ItcYTN6jT/6tTR4klI0kAKRFdIGYSFil0I8UugEkztFtaiCCgpzuuyylo+162cv1ukDtSrQW2fUetpnx//8WmcY+QxzRIgZ5KwfE32WAxGIzDWOvMwX8Dl/1hTcTr9Wz3e+JBbEw7ShqgVNKAMvlL/hRtBAjRRibh42n78Jpb1Zmz2URx3yedqeZJmpJOjX8/hcXotC720vypruLHWFKpo/KKF7X+vU/YHMZVmaI4qI7IN31IZzvq0+x2xw8RsiVwQsKWQ/G1NW+H31rHjxzMMBvSoCos03LNjwH8j+zcftU7YVGA7Z5hc9nPulLkKo7lYJ1yjV89vK/m82ieE6OBFTT1zU8bdkuDlhTHFmcJuGo5w45Bl5ePglVNLqbA3URQhsDQAQAA1zP1b/Pv7Co1grTOL2hB3d23EBm9rlz6Nj9UTY//V8p0UYMfmk+dQZK/LXF+c+C9nNGHQ5TsT14C0beUEIvdk1UALsva/z3eYS9oCGVnmRs1iMllx5abx3r5JRZUJZ9wWkr9srPj4t1DoQ6FDtDOsLj9WZHd36SIu8fDOJ34XyFkeP7pORd6UW+zLP3TZxLnK2Dc9EbRbLizsMGUTh5XWakJ+ZkHR51F/XzW+G1n3pVwqDOEKKIjtq73yBNtoFMAS1hA6cjTlJUreUzpcr/hiLPaDXzMcxFoXNmSBY8PIvsBC6eW/TgcPmyfWUpmupOtN6o++0qvAXJhpvI609e4xNnJCBJP0VeTRyCxgkSOSJN4TSKDrvENOZYFTp/6qQMlLAs+9dIS2l3ZiE5EehQOo2wwlzSqvMQ7mpQ5DfVrm3MPG3sQqlmWU2gB7hL+Z1DPjDnrhqsMoyLvgr3w8XIG4FgD/8R0OknePp68V6nnohOX6f5W5JT0PJvuG3qTGBTDTZLeVY6gWX2m531bzBBc8vqPyf0nS9k4eoB8y/gBUEImkGnuvNWcUwq2I1loD7NyqJtJXV5VQXe3LjSszWLU8cf2AnV+obJ8jTorprd+5J340NgxueLnGzAm89HAqTCXsdl3LacOdQpfK/i4yGc8rzPk4j+dIQLJXBDaT0oPPrSDQNAXG+JooMnhsFohpNP+LZ5D2+NR8ODJdKPe+Vw0fThjc4vnZT2/y/f1s+PjBs+crzwjboQCxHA8JwdCOQfsznhDlJ6UHn0weF8yVoXvC3tAkw493MjxQ1AABGcTVj9/QcqraHd9v5y7b3lfopfjDUj357DkyXTzi6zpa/5nuXgy3ZeYt6cGb6Jc2RkaHmoE6x4GZBybqX3sr55WODQQCJGXgXA76ryiCm98V7V7B1YQTYL5YCVsghF8/3/e1n80lwthBZrPxxXWrrBdF/Fs1yyY6VjEPAmZh0VckzVYBBdEk5cMNoOM5J1tIhXQfSOkD3ByTOGobloCT8T8EiC54yOFS/fH7pXTJvqqukffGFX3CYWmrb4GEIzvORo4bwBF+NpH6NIzHWMIgBrraGK9yHoWbMsFQDMi66HwUBTvQFLbcleRaT682nF9Aewx3WnNY4EyDRao/4HC/KcJR66H51Pa13H3WpMQm4c8RJ6uFWh0KgOOUSGCYTquW9421ANqrWwauigwHNkOq/JqZ+6JSlD6YaQUb+ecRv276Sm0Lc/Fz4Z9EpkSz6xNWJHc9o2j489IK0oc5Trl6ot2MZvkjmfLNxieXzZLaLBijXope6aPkU+Z5/ZA4DWfXk419joaS6PTXJwW9Uwwwt67ZT95Ej2YRTASPwdrlT8/GDZij2ddGO7FchGJPmSuiHyewhBNeEsIjcsZCiyYzpFkhYenve3ls5NcGZWJG/7ZcKXJ+zmgren6q1AiE3YNhPvuWz0/jV3HShtfbpBR/9pKRg0Ozh3Zr0xvyZyyunWnaS1vr9MnxX0Pa8j+hE2mugqezxcAD9Tw+nyCB1/76MMH44N/YQf/7oIuhFSE7xzSEMtgq9PYftRoav1maGc76tPuRhY0G3ilcENpPI6nKTI60Av0B0Cbhg8aU+Xr+fmYc5krMQYb7K6I3eBnchmI6cMbnF87Ke3+X7+tnx8YNndGcrjJqKE3F1SD20DguxR44szhNwemz/9A3A4Tkn1YgY81eQ08dAuwTgAAOY5kmV8CBgTzNmZx+PdrRy3WXTeO5vsJ6F47x+CiXt+jbJXrTx6kaFQ6g8ftfbiiNCtQbDnafYgMRnTKj74dMP7QU0ATdnruF9xUOvqRJboMwh1Tx7nMRMLQhiMPHnUUWsO7oLg4yZvDWKDSelptn1ZJzhS0NmE3D6pV4BDricdLLpj5to/UiYvck9Eczlw4pdn2/EBYflcWdZGI0iXI3V+h+OJGOrnrdeMsZe7lLTQ1lkAR/dp2Ge1TcmsXIqFtAg56lDZ+XtgGoArxNW74rZ9DF5XRS7nais7WnsQz+8omB2buv3l31kzQ6HAxUp30bpOIK6iVv7a+IN8hrmISLY/6KIcGK5sU4Lc1H+BwFBlsJ4K2X3YMvZUUI0bksGX5yLKlJ8ymZFEGu4pjuo5VaTEGXSRJT2D3by4LwSIjqxivistrFFOhpumcheA7EH59K89uTeMKo3f++4kWtXVNncbG+Aa6/a/m2yuUzDigEsmB6GsF4JozsUN22YDcVpsEg6gFpRFKHIJiPDhyYxkktWkgr2Brikiq7GG557KDbnv8yVsYWPglEKYhcc9/aKPssbF5+vObOuqfjU/Nx/uH01tPlyLcx2xL48H6u1j3kK0DwhBNIrOzLvihjwNM2fb5ld06kvZ15uPpr+9f5Fhas52CcTNqmDDvvRYb2Q32O4xyPsYfPGRDoMa9RACLaUgyDD4n4qGeW9aM3XwyyaoZmWACtCqH6/xb5JvXJBEGBso01LMsWzxc5cXEBprziL/37WrAA5tRHp91QCSGJ/Szef8VbUICIi4Wht72F28O0igFVONPGBicxrqYq84i6enYMIlEWJlv/RLmDQNXTLBKCsuEdUpsUWuuY2i4AH6ndGaU65KF67IrE9CkzRu8AQkeVRjgVAuEqNdyTaMWSuo+KYqhnO+rT7G0ByynQSuCG0nkdTlI3ncvvc55WpuP2095SRvKQMkAeD2ZF8bXgv0ZP3MBHIV8G8590pchhr6tl7cyf2QbzDsNa/8g9bl3MLyoviozwctN0JxxZnCbhqOcOOQZeXj3dDnYPIoK8e7mYXJGAAAD1UDqJyIQ6zU/aAalc6QLy6kEZlb27KFKvQDkTv2GOhDfxyuGC6OCdKEo/76wDRfAFDE3vtpTU+Ar9lSkbWoEe+qq5kC5K9rvWPekg6gJZPBkZ/TXutVEaUjTZ9ZFK4RlEOtKLBWYpO1XC8S0aErHBJp5ZbrlP0YsXE7SrK8o2xOKZxRQLHiIQaUnpACuvMek708E1bfe7lOC1XYa/JzM3L6T0on64D7oazo1OHbjwLEcSktEiXOzAtm/kxq0yoG13suWNILjF40D/UpygbO/EBJx77gSW0hAWhw4tBjGSA2z+lhti5m7lijpXvMv6q6qCAKr75uJgud/taoxKWB5tFn1SgS4C7Xk73o8hiiGZGMTTMdGe1OrolIzW1VYTfa6cWvJTZcAuengbK+k2jflGnK5iNeYt2zcuJlv1/hfzLIBVBLooCTMA3uQbFRb++GpFYNwCwudWZdQSpvpPp7pN4f3x91tPwUM9HR1n9eo4m4F74mo7hO9lg6I3NWCRvqiINllpvACuiW/9Lkfb48hS2218z94ENG9J3k2FpNcV1uPveCye4LT7gTJkQy5WErB6ww/AcuecjYmbDy5VAb6JwafLWK85cwSrcUyZ47VDCK7wemFBgQqmEdyy39uSgjxmOegMCrrvglDd3UMVlfja+QGe/EegvcsUK+AouXcetUSr311MB1y9u3U8k8LAwOx2bCjZZ9xOlfcV1TISxPCeRgduo4xV/tgd1n/DlQq51nTRfZdBTYjtlgNjjphZhSCzEfBHZESeuh+3nl2NWJQu7MmEbYYOpmevsQBndkODfy3nRVJCHQm4TQx3BIrKwf50nUlCd8L78s3o8khTlBhQcDhikUxL/52eLyysDPhxl8WdW9EvtmxDnXtCv0k4U0AHGhoHm15YRrofljyro4qXYRSde3Ddtl7Of6+B+xzbwLxrOeTR3EaEcPyVLPcLwJ2luTlNclJYu3rP0KxhF1CMxZvsKZ5swpyM2rJ1cWXX4u10OGIR9dR7DS9cXOWX7mkrJKVsmqKRFfeP0gBnL2UJpFtk8JqAKUGMiKbiY/2Cj7YuETjy4uJ1OZxxFW07YvpTN8cGXKX6QYdWognEZ95GTp0T5bq4L61Caz1bmgfqLJPHDYrohAPsMAmlaRz58qeV8W2FFrz4bpiP2AFrE80ue2kDLNes0wCqz7GbDX9pnZQyuFX5FrUbc6Zk3dTV9wF9IQllqZC65Bkz6pfepkIEaybhE0fgad7KXwjqNO576DW3kJiu3s8XAA/U7ozStFPxQjrR2a5TRNe6wuBfTokW55gPPal9eQ5/UuQqjuVbdhrvPRwKkvP3FxqRN5u7gWC+cTv1jCxUcFMmunGPEmpKhmtGRho7f8CiThjITq9Yweg0mZz7pS5CqO5WCdco1fP012u/SQQncrdCAWI4GC5wgAqrBGOLM4TcHps//QNwOE5J9geEDL4mkMK9vXvfAAABRL46J5mIKlJVvXyyOxNAGOIkoY6DTfrVgrAyG0zMayL9yIHdNbBRbi9Dhoowm7MZhQICMxFA2qGghtkvVtzvoq6WpBpn4HsSEJ+0kWgFGSC10fnq5Pk8ycxo6D6zNm/4HP2PD66wWLgs4gaiwpW9/XJbr9Fj/309hAbXVIecqwp2LKKhwpudIGQRSp03BsxQlM8MjpIUHdzma802IuxhlltBYPv+fTMmTy/RHpmAIF0HbVKTnN2Dy780wj74BsZ6gAETd3goMOBlacmhmhlLdW2ganeqQpMs/L/yCOCo2TaznaUuiFqTRqMj3hWEEuQlGgFf38Kr/VSafcS/OXHJDXNczjij1IQQVCA8DeTvmqyxLH4ugfjZf/kTMhkZmHz6lMRJ2Hu1jQRnjqUpdI8jpchY+w+N1nMrCF6bqO9drYih/TNImmBFI84VSJddhd6RX2O33Nu5yCF4PmK1r3W665auMnvu9yihDs+47MyNmZoSjo0kHyd8BsO2Fz7Ny1vQgwM+B45vzeeNONA5ACXkDckYqpYlAk+dKYvwWVfXiBtIgkevn5IevGp/mSEyKpuI/fO8Tb//0VUJYDMtYMDJkXh7H9YhkdZa31mBLfGkKG2DEgxyMRSSdrspswHnGQdEQg2eRnUVh7J7qeOq0OS+8gkaPgj+m8bUKrjiwtb+1wzjzvCc9Ma0ZP9CT5PNlTq6kW6q0qyPe2bOiMBlsuAAEysXby/3lI/ONf5eTpAcFCU4Ng/tPW5DabrM4YynHpci5HzS6shhQi8CsHg9s2eLf7zqma7bQIPvTHaAxDMYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== // ==/UserScript== (function () { 'use strict'; const SCRIPT_VERSION = typeof GM_info !== 'undefined' ? GM_info.script.version : '1.0.0'; const CFG = { checkInterval: 2000, coursePageTimeout: 900 }; const hacked = new WeakSet(); let stats = { completed: 0, total: 0, startTime: 0, scriptDone: 0 }; let timers = []; let stopped = false; // 始终自动运行 let navigating = false; let courseList = []; let currentCourse = null; let xmId = ''; let videoDuration = 0; let finishedDuration = 0; let lastVidUpdate = 0; let currentVidTime = 0; let courseRecoveryTimer = null; let watchdogProgressTime = Date.now(); let watchdogLastCurrentTime = -1; const WATCHDOG_STUCK_LIMIT = 180000; // 3分钟无进展则强刷 let lastProgressPct = -1; // 上次服务端进度百分比 let lastProgressUpdateTime = 0; // 上次进度更新时间戳 let progressStuckCount = 0; // 连续卡死次数 let videoTimeAtLastProgress = 0; // 上次进度更新时的视频播放位置 // ==================== 持久化存储(断点续播 + 后台重载恢复) ==================== function saveState() { try { GM_setValue('whut_xmId', xmId); GM_setValue('whut_stopped', stopped); GM_setValue('whut_completed', stats.completed); GM_setValue('whut_total', stats.total); GM_setValue('whut_scriptDone', stats.scriptDone); } catch(e) {} } // 重载前完整保存(含 courseList、进度、当前课程) function saveStateForReload() { try { saveState(); if (courseList.length > 0) { sessionStorage.setItem('whut_courseList', JSON.stringify(courseList)); } if (currentCourse) { sessionStorage.setItem('whut_currentCourse', JSON.stringify(currentCourse)); } if (finishedDuration > 0) { sessionStorage.setItem('whut_finishedDuration', finishedDuration); } // 保存完整课程状态用于跨重载渲染 const fullInfo = { total: stats.total, completed: stats.completed, scriptDone: stats.scriptDone }; sessionStorage.setItem('whut_courseInfo', JSON.stringify(fullInfo)); sessionStorage.setItem('whut_reload_reason', 'auto_recovery'); } catch(e) {} } function getCourseProgress(course) { if (course.progress != null) return parseFloat(course.progress) || 0; const duration = parseFloat(course.duration) || 0; const finishedDuration = parseFloat(course.finishedDuration) || 0; return duration > 0 && finishedDuration >= duration ? 1 : 0; } function renderCourseListItems(courses) { if (!listEl || !Array.isArray(courses)) return; listEl.replaceChildren(); courses.forEach(course => { const mode = course.studyMode || ''; const progress = Math.max(0, Math.min(1, getCourseProgress(course))); const cls = progress >= 1 ? 'done' : (progress > 0 ? 'half' : (mode !== 'WLXX' && mode !== '' ? 'skip' : 'todo')); const icon = progress >= 1 ? '✔' : (progress > 0 ? '◐' : '○'); const item = document.createElement('span'); item.className = cls; item.textContent = icon + ' ' + String(course.courseName || course.name || '未命名课程') + ' ' + Math.round(progress * 100) + '%'; listEl.appendChild(item); listEl.appendChild(document.createTextNode('\n')); }); } function renderCourseList() { if (!listEl) return; let courses = []; const full = sessionStorage.getItem('whut_fullList'); if (full) { try { const parsed = JSON.parse(full); if (Array.isArray(parsed)) courses = parsed; } catch(e) {} } if (courses.length === 0 && courseList.length > 0) { courses = courseList; } renderCourseListItems(courses); } function loadState() { try { xmId = GM_getValue('whut_xmId', ''); const wasStopped = GM_getValue('whut_stopped', true); stats.completed = GM_getValue('whut_completed', 0) | 0; stats.total = GM_getValue('whut_total', 0) | 0; stats.scriptDone = GM_getValue('whut_scriptDone', 0) | 0; // 尝试恢复课程列表 const saved = sessionStorage.getItem('whut_courseList'); if (saved) { try { courseList = JSON.parse(saved); } catch(e) {} } const savedCourse = sessionStorage.getItem('whut_currentCourse'); if (savedCourse) { try { currentCourse = JSON.parse(savedCourse); } catch(e) {} } const savedFd = sessionStorage.getItem('whut_finishedDuration'); if (savedFd) finishedDuration = parseFloat(savedFd) || 0; // 检查是否从重载中恢复 const reloadReason = sessionStorage.getItem('whut_reload_reason'); if (reloadReason) { sessionStorage.removeItem('whut_reload_reason'); // 清理一次性恢复标记 sessionStorage.removeItem('whut_finishedDuration'); return 'reload_recovery'; } if (xmId && wasStopped === false) { stopped = false; return 'session_restore'; } } catch(e) {} return false; } const $ = (s, p) => (p || document).querySelector(s); const $$ = (s, p) => [...(p || document).querySelectorAll(s)]; function isDetailPage() { return location.href.includes('/myTrain/detail'); } function isCoursePage() { return location.href.includes('/myTrain/course'); } // ==================== ID ==================== function getXmId() { const qs = (location.hash || '').split('?')[1] || ''; if (!qs) return ''; const p = new URLSearchParams(qs); if (isCoursePage()) return p.get('xmId') || ''; if (isDetailPage()) return p.get('id') || ''; return p.get('xmId') || p.get('id') || ''; } function getResourceId() { const qs = (location.hash || '').split('?')[1] || ''; if (!qs || !isCoursePage()) return ''; return new URLSearchParams(qs).get('id') || ''; } // ==================== API ==================== const API = { getXm: () => fetch('/api/student/xm/getById', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: 'id=' + encodeURIComponent(xmId), }).then(r => r.json()), getCourse: (rid) => fetch('/api/student/recommendCourse/getById?id=' + encodeURIComponent(rid) + '&xmId=' + encodeURIComponent(xmId)).then(r => r.json()), }; // ==================== Toast 通知 ==================== function toast(msg, duration) { const el = document.createElement('div'); el.style.cssText = 'position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:999999;' + 'background:#1e1e2e;color:#a6e3a1;padding:10px 24px;border-radius:20px;' + 'font:13px "Microsoft YaHei",sans-serif;box-shadow:0 4px 20px rgba(0,0,0,.5);' + 'animation:fadeInOut ' + (duration || 3) + 's ease both;pointer-events:none;border:1px solid rgba(166,227,161,.3)'; el.textContent = msg; document.body.appendChild(el); setTimeout(() => el.remove(), (duration || 3) * 1000); } // ==================== UI 面板 ==================== let logEl, statusEl, progressEl, courseNameEl, listEl; let vidBarEl, vidTimeEl, vidTotalEl, vidEtaEl, bannerEl; function createPanel() { if (document.getElementById('__whut_panel')) return; // 注入动画 const style = document.createElement('style'); style.textContent = '@keyframes fadeInOut{0%{opacity:0;transform:translateX(-50%) translateY(-10px)}15%{opacity:1;transform:translateX(-50%) translateY(0)}85%{opacity:1}100%{opacity:0}}'; document.head.appendChild(style); const d = document.createElement('div'); d.id = '__whut_panel'; d.innerHTML = '' // HTML + '
' + '
📖党校学习
' + '
' + '
' + '' + '
' // 状态块 + '
' + '
待命中
等待操作
' // 视频进度 + '
' + '
0:000:00
' + '
' // 课程进度 + '
课程进度0 / 0
' + '
' // 课程列表 + '
课程状态
' // 日志 + '
运行日志
' // 状态标识 + '
⚡ 自动运行中
'; document.body.appendChild(d); // 绑定引用 logEl = $('#__whut_log'); statusEl = $('#__whut_status'); progressEl = $('#__whut_progress'); courseNameEl = $('#__whut_substatus'); listEl = $('#__whut_courselist'); vidBarEl = $('#__whut_vidBar'); vidTimeEl = $('#__whut_vidCur'); vidTotalEl = $('#__whut_vidTot'); vidEtaEl = $('#__whut_vidEta'); bannerEl = $('#__whut_banner'); // 事件 $('#__whut_hide').addEventListener('click', () => d.classList.toggle('collapsed')); makeDraggable(d, $('#__whut_hd')); } function makeDraggable(el, hd) { let ox, oy, dx, dy, mv, up; hd.addEventListener('mousedown', e => { ox = e.clientX; oy = e.clientY; const r = el.getBoundingClientRect(); dx = r.left; dy = r.top; mv = ev => { el.style.left = (dx + ev.clientX - ox) + 'px'; el.style.top = (dy + ev.clientY - oy) + 'px'; el.style.right = 'auto'; }; up = () => { document.removeEventListener('mousemove', mv); document.removeEventListener('mouseup', up); mv = up = null; }; document.addEventListener('mousemove', mv); document.addEventListener('mouseup', up); // 防止在窗口外松开鼠标导致事件监听泄漏 document.addEventListener('mouseleave', up, { once: true }); }); } // ==================== UI 更新 ==================== function setState(st) { const dot = $('#__whut_dot'); if (dot) dot.className = 'stat-dot ' + st; } function setStatus(s, sub) { if (statusEl) statusEl.textContent = s; if (courseNameEl && sub !== undefined) courseNameEl.textContent = sub; } function setBanner(msg) { if (bannerEl) { bannerEl.textContent = msg; bannerEl.style.display = msg ? 'block' : 'none'; } } function updateCourseProgress() { const actual = (stats.completed|0) + (stats.scriptDone|0); if (progressEl) progressEl.textContent = actual + ' / ' + (stats.total|0); const bar = $('#__whut_crsBar'); if (bar && stats.total > 0) bar.style.width = Math.min(100, (actual / (stats.total|0)) * 100) + '%'; } function updateVidProgress() { const v = $('video'); if (v && v.duration && !v.ended) { currentVidTime = v.currentTime; const total = v.duration; // 使用真实 video.duration,不依赖 API 推算 const blk = $('#__whut_vidBlk'); if (blk) blk.style.display = 'block'; const pct = Math.min(100, (currentVidTime / total) * 100); if (vidBarEl) vidBarEl.style.width = pct + '%'; if (vidTimeEl) vidTimeEl.textContent = fmtTime(currentVidTime); if (vidTotalEl) vidTotalEl.textContent = fmtTime(total); if (vidEtaEl) { const rem = total - currentVidTime; vidEtaEl.textContent = rem > 0 ? '剩余 ' + fmtTime(rem) : ''; } } } function fmtTime(s) { const m = Math.floor(s / 60); const sec = Math.floor(s % 60); return m + ':' + (sec < 10 ? '0' : '') + sec; } function log(msg, type) { const t = new Date().toLocaleTimeString(); const el = document.createElement('div'); el.className = 'l' + (type ? ' ' + type : ''); el.textContent = t + ' ' + msg; if (logEl) { logEl.appendChild(el); logEl.scrollTop = logEl.scrollHeight; } console.log('%c[学习]', 'color:#cba6f7', msg); // 重要事件 toast if (type === 'ok' && (msg.includes('完成') || msg.includes('进入'))) toast(msg, 2); if (type === 'err') toast(msg, 4); } function showXmIdInput() { let box = $('#__whut_xmIdBox'); if (!box) { box = document.createElement('div'); box.id = '__whut_xmIdBox'; box.style.cssText = 'margin-top:8px;display:flex;gap:4px'; box.innerHTML = ''; const body = $('#__whut_body'); if (body) { const acts = body.querySelector('.acts'); if (acts) body.insertBefore(box, acts); else body.appendChild(box); } $('#__whut_xmIdBtn').addEventListener('click', () => { const v = $('#__whut_xmIdInput').value.trim(); if (v) { xmId = v; log('手动ID: ' + v, 'ok'); box.remove(); scanAndStart(); } }); } box.style.display = 'flex'; } // ==================== 视频劫持 ==================== function hackVideo(video) { if (hacked.has(video)) return; hacked.add(video); // 静音策略:先静音起播绕过浏览器 autoplay 限制,再尝试取消静音 // 如果取消静音导致浏览器暂停视频,则放弃取消静音(避免无限循环) video.muted = true; video.volume = 0; video.playbackRate = 1.0; let unmutePhase = 0; // 0=未尝试, 1=等待检测, 2=成功, 3=已放弃 // 智能计算跳播时长:处理 API 返回秒/分钟单位不一致 let seekTo = finishedDuration; let seekDone = false; let seekRetries = 0; const MAX_SEEK_RETRIES = 20; // 10秒内每500ms重试 const doSeek = () => { if (seekDone || seekRetries >= MAX_SEEK_RETRIES) return; if (!video.duration || video.duration <= 0) return; seekRetries++; // 单位修正:如果 finishedDuration 远大于实际时长,可能是重复乘了60 if (seekTo > video.duration * 1.5) { const fixed = seekTo / 60; if (fixed > 0 && fixed < video.duration - 5) { log('单位修正: ' + fmtTime(seekTo) + ' → ' + fmtTime(fixed), 'warn'); seekTo = fixed; } } if (seekTo > 0 && seekTo < video.duration - 5) { seekDone = true; video.currentTime = seekTo; watchdogLastCurrentTime = seekTo; watchdogProgressTime = Date.now(); log('跳过已看 ' + fmtTime(seekTo) + ',继续播放', 'info'); } }; // 事件驱动 + 定时兜底,防 SPA 不触发原生事件 video.addEventListener('loadedmetadata', doSeek); video.addEventListener('canplay', () => { doSeek(); }, { once: false }); if (video.readyState >= 2 && video.duration) doSeek(); let lastMilestone = 0; let playFailCount = 0; const forceTimer = setInterval(() => { if (video.ended) { clearInterval(forceTimer); return; } if (!seekDone) doSeek(); // 定时重试跳播 // 尝试取消静音:仅在从未尝试过时执行一次 if (!video.paused && unmutePhase === 0 && video.muted) { unmutePhase = 1; video.muted = false; video.volume = 1.0; // 800ms 后检测取消静音是否导致浏览器暂停 setTimeout(() => { if (unmutePhase !== 1) return; // 已处理过 if (video.paused) { // 浏览器因取消静音暂停了视频 → 放弃,保持静音 unmutePhase = 3; video.muted = true; video.volume = 0; video.play().catch(() => {}); log('⚠ 取消静音导致暂停,保持静音播放(反作弊通常数分钟才检测)', 'warn'); } else { unmutePhase = 2; log('🔊 取消静音成功', 'ok'); } }, 800); } if (video.paused) { const prom = video.play(); if (prom && prom.catch) { prom.then(() => { playFailCount = 0; }) .catch(() => { playFailCount++; }); } // 失败 6 次(3秒)→ 静音重试(绕过浏览器 autoplay 策略) if (playFailCount === 6) { video.muted = true; video.volume = 0; // 如果之前取消静音成功了但现在又暂停了,不重置 unmutePhase(可能是用户手动暂停或别的原因) video.play().then(() => { log('🔇 静音起播成功' + (unmutePhase >= 2 ? '(重新静音)' : ''), 'warn'); playFailCount = 0; }).catch(() => {}); } // 失败 20 次(10秒)→ 强刷 if (playFailCount > 20) { log('❌ 视频无法自动播放(' + playFailCount + '次失败),强制刷新恢复', 'err'); clearInterval(forceTimer); setTimeout(() => { saveStateForReload(); location.reload(); }, 500); return; } } else { playFailCount = 0; // 看门狗:视频在播放但 currentTime 变了才算有进展 if (Math.abs(video.currentTime - watchdogLastCurrentTime) > 0.2) { watchdogLastCurrentTime = video.currentTime; watchdogProgressTime = Date.now(); } // 每30秒记录一次播放进度里程碑 const milestone = Math.floor(video.currentTime / 30); if (milestone > lastMilestone && video.duration > 0) { lastMilestone = milestone; const pct = Math.round(video.currentTime / video.duration * 100); log('⏱ 播放中 ' + fmtTime(video.currentTime) + ' / ' + fmtTime(video.duration) + ' (' + pct + '%)', 'info'); } } updateVidProgress(); }, 500); video.play().then(() => { log('▶ 视频自动起播成功', 'ok'); // 延迟 1.5s 尝试取消静音(避开浏览器 autoplay 检测窗口) // 但由 forceTimer 统一管理 unmutePhase,这里只标记待处理 if (unmutePhase === 0 && video.muted) { setTimeout(() => { if (!video.ended && !stopped && unmutePhase === 0) { // 交给 forceTimer 的 unmutePhase 逻辑处理 log('🔊 准备取消静音...', 'info'); } }, 1500); } }).catch(() => { log('⚠ 视频自动播放被浏览器拦截,等待用户交互...', 'warn'); }); video.addEventListener('play', () => { log('▶ 视频开始播放', 'info'); }); video.addEventListener('pause', () => { log('⏸ 视频暂停', 'warn'); }); video.addEventListener('seeking', () => { log('⏩ 跳转至 ' + fmtTime(video.currentTime), 'info'); }); video.addEventListener('ended', () => { clearInterval(forceTimer); updateVidProgress(); log('视频播放完毕 ✔', 'ok'); toast('本视频播放完毕,自动进入下一节', 2); onVideoEnded(); }); video.addEventListener('emptied', () => { clearInterval(forceTimer); hacked.delete(video); }); video.addEventListener('abort', () => { clearInterval(forceTimer); hacked.delete(video); }); const vShow = fmtTime(video.duration || videoDuration); log('已接管 · 总长 ' + vShow, 'info'); updateVidProgress(); } function hackAllVideos() { $$('video').forEach(hackVideo); return $$('video').length; } // ==================== 结束处理 ==================== let videoEndedHandled = false; // 防止 ended 事件和循环检测重复触发导航 function onVideoEnded() { if (stopped || videoEndedHandled) return; videoEndedHandled = true; setTimeout(() => { tryGoNextOrBack(); videoEndedHandled = false; }, 1500); } function tryGoNextOrBack() { if (stopped || navigating) return; const kws = ['下一节', '下一章', '下一个', '继续学习', '继续']; for (const kw of kws) { for (const el of $$('button, a, span, div[class*=btn]')) { if (!el.offsetParent) continue; if ((el.textContent || '').trim().includes(kw)) { log('→ ' + kw, 'ok'); navigating = true; el.click(); setTimeout(() => { navigating = false; }, 2500); return; } } } const items = $$('.el-menu-item,[class*=chapter] [class*=item],[class*=lesson],[class*=catalog] li,[role=treeitem]').filter(e => e.offsetParent); for (let i = 0; i < items.length - 1; i++) { if ((items[i].className || '').match(/\b(active|is-active|current)\b/)) { log('→ 目录下一项', 'ok'); navigating = true; items[i + 1].click(); setTimeout(() => { navigating = false; }, 2500); return; } } if (courseList.length > 1) { log('本节完成 → 返回列表重扫', 'ok'); stats.scriptDone = (stats.scriptDone | 0) + 1; updateCourseProgress(); goBackAndContinue(); } else { log('全部完成 → 返回列表', 'ok'); stats.scriptDone = 0; stats.completed = stats.total | 0; updateCourseProgress(); goBackAndContinue(); } } function goBackAndContinue() { cancelCourseRecovery(); sessionStorage.setItem('whut_goto_detail', xmId); saveStateForReload(); location.reload(); } // ==================== 扫描 ==================== // 静默拉取课程列表和进度(不导航,只填充数据供课程页初始化用) async function fetchCourseStats() { try { const resp = await API.getXm(); if (resp.code !== 0 || !resp.data) return; const list = resp.data.xmCourseSetting.chooseCourseList; setBanner(resp.data.name); courseList = list .filter(c => { const mode = c.studyMode || ''; if (mode !== 'WLXX' && mode !== '') return false; if (!c.resourceId) return false; const prog = c.progress != null ? parseFloat(c.progress) : 0; return prog < 1; }) .sort((a, b) => (a.sort || 0) - (b.sort || 0)); // 过滤掉当前正在播放的课程,避免重复进入 const resId = getResourceId(); if (resId) courseList = courseList.filter(c => String(c.resourceId) !== resId); const totalOnline = list.filter(c => c.resourceId).length; stats.total = totalOnline | 0; stats.completed = (totalOnline - list.filter(c => { const mode = c.studyMode || ''; if (mode !== 'WLXX' && mode !== '') return false; const prog = c.progress != null ? parseFloat(c.progress) : 0; return prog < 1; }).length) | 0; updateCourseProgress(); // 渲染课程列表 renderCourseListItems(list); } catch(e) {} } // 静默扫描:只拉API渲染课程状态,不启动自动学习 async function scanAndRender() { try { const resp = await API.getXm(); if (resp.code !== 0 || !resp.data) return; const list = resp.data.xmCourseSetting.chooseCourseList; sessionStorage.setItem('whut_fullList', JSON.stringify(list)); setBanner(resp.data.name); // 渲染全部课程状态 renderCourseListItems(list); // 填充 courseList 和 stats courseList = list .filter(c => { const mode = c.studyMode || ''; if (mode !== 'WLXX' && mode !== '') return false; if (!c.resourceId) return false; return (c.progress != null ? parseFloat(c.progress) : 0) < 1; }) .sort((a, b) => (a.sort || 0) - (b.sort || 0)); const resId = getResourceId(); if (resId) courseList = courseList.filter(c => String(c.resourceId) !== resId); stats.total = list.filter(c => c.resourceId).length | 0; stats.completed = (stats.total - courseList.length + (resId ? 1 : 0)) | 0; updateCourseProgress(); } catch(e) {} } let scanRetryCount = 0; async function scanAndStart() { xmId = getXmId(); if (!xmId) { // 可能页面尚未渲染完毕,等待重试 scanRetryCount++; if (scanRetryCount <= 5) { log('等待页面渲染...(' + scanRetryCount + '/5)', 'info'); setTimeout(() => { if (!stopped && isDetailPage()) scanAndStart(); }, 1000); return; } scanRetryCount = 0; setStatus('请先进入培训班详情页', '路径: 我的培训 → 选择培训班 → 点击进入'); setState('err'); log('未检测到培训班ID,请进入培训班详情页', 'err'); log('当前页面: ' + location.href, 'warn'); showXmIdInput(); return; } scanRetryCount = 0; setStatus('正在获取课程列表...', '请稍候'); setState('scan'); setBanner(''); let data; try { const resp = await API.getXm(); if (resp.code !== 0) throw new Error('服务器返回 code=' + resp.code); data = resp.data; } catch (e) { setStatus('获取失败', e.message); setState('err'); log('API 请求失败: ' + e.message, 'err'); showXmIdInput(); return; } setBanner(data.name); log('培训班: ' + data.name, 'ok'); const list = data.xmCourseSetting.chooseCourseList; sessionStorage.setItem('whut_fullList', JSON.stringify(list)); // 筛选 WLXX 未完成 courseList = list .filter(c => { const mode = c.studyMode || ''; if (mode !== 'WLXX' && mode !== '') return false; if (!c.resourceId) return false; const prog = c.progress != null ? parseFloat(c.progress) : 0; return prog < 1; }) .sort((a, b) => (a.sort || 0) - (b.sort || 0)); // 渲染课程列表(带颜色) renderCourseListItems(list); const totalOnline = list.filter(c => c.resourceId).length; stats.total = totalOnline | 0; stats.completed = (totalOnline - courseList.length) | 0; updateCourseProgress(); if (courseList.length === 0) { setStatus('全部完成!', totalOnline + ' 门课程已全部通过'); setState('done'); toast('所有课程已完成!', 3); navigating = false; return; } setStatus('准备开始', '共 ' + totalOnline + ' 门,剩 ' + courseList.length + ' 门待完成'); setState('play'); toast('开始自动学习 · 剩余 ' + courseList.length + ' 门', 2); enterNextCourse(); } async function enterNextCourse() { if (stopped || courseList.length === 0) { setStatus('全部完成!', stats.total + ' 门课程已全部通过'); setState('done'); toast('全部课程已完成!', 3); navigating = false; return; } navigating = true; currentCourse = courseList.shift(); const name = currentCourse.courseName || currentCourse.name; const remaining = courseList.length; const done = (stats.completed|0) + (stats.scriptDone|0); log('📚 第' + (done + 1) + '门: ' + name + ' | 剩余' + remaining + '门', 'ok'); setStatus('进入课程', name); setState('play'); updateCourseProgress(); videoDuration = 0; finishedDuration = 0; try { const resp = await API.getCourse(currentCourse.resourceId); if (resp.code === 0 && resp.data) { videoDuration = (resp.data.duration || 0) * 60; const fd = resp.data.finishDruation != null ? resp.data.finishDruation : (resp.data.finishedDuration != null ? resp.data.finishedDuration : 0); finishedDuration = (fd || 0) * 60; if (finishedDuration > 0) { log('已看 ' + fmtTime(finishedDuration) + ',将跳过', 'info'); } } } catch (e) { /* 静默 */ } // 真刷新进课:保存已完成进度 + 跳播位置 + 目标课程 saveStateForReload(); sessionStorage.setItem('whut_finishedDuration', finishedDuration); sessionStorage.setItem('whut_goto_course', JSON.stringify({ resourceId: currentCourse.resourceId, xmId: xmId })); location.reload(); // 如果 reload 被浏览器拦截未生效,恢复课程到列表头部防止丢课 courseList.unshift(currentCourse); navigating = false; } // ==================== 重载恢复 ==================== function scheduleCourseRecovery() { cancelCourseRecovery(); courseRecoveryTimer = setTimeout(() => { if (!stopped && isCoursePage() && $$('video').length === 0 && !navigating) { log('超时无视频,强制刷新恢复...', 'warn'); saveStateForReload(); location.reload(); } }, 45000); } function cancelCourseRecovery() { if (courseRecoveryTimer) { clearTimeout(courseRecoveryTimer); courseRecoveryTimer = null; } } // ==================== 课程循环 ==================== function startCourseLoop() { log('🔄 课程循环已启动', 'ok'); stats.startTime = Date.now(); navigating = false; videoEndedHandled = false; watchdogProgressTime = Date.now(); watchdogLastCurrentTime = -1; let watchdogStuckStart = 0; const timer = setInterval(() => { if (stopped) { clearInterval(timer); return; } if (!isCoursePage()) { clearInterval(timer); return; } const vids = $$('video'); if (vids.length > 0) { cancelCourseRecovery(); } if (vids.length === 0) { window.scrollTo(0, document.body.scrollHeight); if (Date.now() - stats.startTime > CFG.coursePageTimeout * 1000) { log('超时无视频,返回列表', 'warn'); clearInterval(timer); goBackAndContinue(); } } else { hackAllVideos(); // 🐕 看门狗:3分钟无播放进展则强刷 if (Date.now() - watchdogProgressTime > WATCHDOG_STUCK_LIMIT) { log('看门狗:视频卡死超过3分钟,强制刷新恢复', 'err'); setTimeout(() => { saveStateForReload(); location.reload(); }, 500); clearInterval(timer); return; } // 🐕 子检测:连续卡在0:00超过30秒则强刷(以首次检测到卡死时间为准,避免慢网误判) const allPaused = vids.every(v => v.paused && !v.ended); const stuckAtZero = allPaused && vids.some(v => v.currentTime < 0.5); if (stuckAtZero) { if (!watchdogStuckStart) watchdogStuckStart = Date.now(); if (Date.now() - watchdogStuckStart > 30000) { log('视频卡死在0:00持续30秒,强制刷新恢复', 'err'); setTimeout(() => { saveStateForReload(); location.reload(); }, 500); clearInterval(timer); return; } } else { watchdogStuckStart = 0; } const allDone = vids.every(v => v.ended || (isFinite(v.duration) && v.duration > 0 && v.currentTime >= v.duration - 1)); if (allDone && !navigating) { clearInterval(timer); setTimeout(() => tryGoNextOrBack(), 1500); } // 🐕 进度看门狗:每30秒检测服务端进度是否跟随视频增长 progressWatchdog(vids); } }, CFG.checkInterval); timers.push(timer); // 进度看门狗状态 let lastProgressWatchdogCheck = Date.now(); function progressWatchdog(vids) { if (Date.now() - lastProgressWatchdogCheck < 30000) return; lastProgressWatchdogCheck = Date.now(); const v = vids[0]; if (!v || v.ended || v.duration <= 0) return; // 视频播放了至少1分钟才算 if (v.currentTime < 60) return; // 如果70秒内服务端进度没有任何更新 const timeSinceUpdate = Date.now() - lastProgressUpdateTime; if (timeSinceUpdate > 70000) { progressStuckCount++; const curPct = lastProgressPct >= 0 ? lastProgressPct.toFixed(1) + '%' : '未知'; if (progressStuckCount === 1) { log('⚠ 进度卡死: 服务端' + curPct + ',视频已到' + fmtTime(v.currentTime) + ',70秒未更新,强制上报...', 'warn'); forceProgressSave(); } else if (progressStuckCount === 2) { log('⚠ 进度仍卡死,再次强制上报 + 暂停重播...', 'warn'); forceProgressSave(); // 暂停一下再播放,触发网站自身的事件处理 v.pause(); setTimeout(() => { if (!stopped) v.play().catch(() => {}); }, 1500); } else { log('❌ 进度持续卡死(' + progressStuckCount + '次),强制刷新恢复', 'err'); setTimeout(() => { saveStateForReload(); location.reload(); }, 500); timers.forEach(clearInterval); timers = []; } } } } // 强制调用进度保存 API(双保险:save + getCourse 同步) function forceProgressSave() { if (!currentCourse || !xmId) return; const body = 'id=' + encodeURIComponent(currentCourse.resourceId) + '&flag=1&xmId=' + encodeURIComponent(xmId); // 方案1: 直接调用进度保存接口 fetch('/api/student/study/progress/save', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' }, body: body }).then(r => r.text()).then(t => { try { const j = JSON.parse(t); if (j.code === 0) log('✅ 强制上报响应: ' + j.data + '%', 'ok'); else log('⚠ 强制上报异常: ' + t.substring(0, 80), 'warn'); } catch(e) { log('⚠ 强制上报解析失败', 'warn'); } }).catch(e => log('❌ 强制上报网络错误: ' + e.message, 'err')); // 方案2: 同步调用 getCourse 获取服务端最新进度 fetch('/api/student/recommendCourse/getById?id=' + encodeURIComponent(currentCourse.resourceId) + '&xmId=' + encodeURIComponent(xmId)) .then(r => r.json()).then(j => { if (j.code === 0 && j.data) { const fd = j.data.finishDruation != null ? j.data.finishDruation : (j.data.finishedDuration != null ? j.data.finishedDuration : 0); const svrPct = j.data.progress != null ? parseFloat(j.data.progress) : 0; if (fd > 0 || svrPct > 0) { log('📡 服务端同步: 已看' + fmtTime(fd * 60) + ', 进度' + (svrPct * 100).toFixed(1) + '%', 'info'); // 如果服务端进度比本地记录新,同步更新 if (svrPct > 0 && svrPct * 100 > lastProgressPct) { lastProgressPct = svrPct * 100; lastProgressUpdateTime = Date.now(); progressStuckCount = 0; } } } }).catch(() => {}); } // ==================== 真人模拟(增强版) ==================== let humanSimTimer = null; let simActionCount = 0; let lastSimLog = ''; function startHumanSimulator() { if (humanSimTimer) return; log('👤 真人模拟启动 · 间隔8~35s', 'ok'); scheduleNextSim(); } function stopHumanSimulator() { if (humanSimTimer) { clearTimeout(humanSimTimer); humanSimTimer = null; } } function scheduleNextSim() { if (stopped) return; const delay = 8000 + Math.random() * 27000; // 8-35秒 humanSimTimer = setTimeout(() => { if (stopped) return; performRandomAction(); scheduleNextSim(); }, delay); } function performRandomAction() { const actions = [ simMouseMove, simClick, simScroll, simHoverVideo, simDblClick, simRightClick, simDrag, simTextSelect, simTouchTap, simMouseMoveToVideo ]; const count = 1 + Math.floor(Math.random() * 3); // 1-3个动作 const shuffled = actions.sort(() => Math.random() - 0.5); const done = []; for (let i = 0; i < count; i++) { try { const name = shuffled[i](); if (name) done.push(name); } catch(e) {} } simActionCount++; const msg = '🎭 #' + simActionCount + ' ' + done.join(' → '); if (msg !== lastSimLog) { lastSimLog = msg; log(msg, 'sim'); } } // --- 10 种动作 --- function simMouseMove() { const x = 100 + Math.random() * (window.innerWidth - 200); const y = 100 + Math.random() * (window.innerHeight - 200); const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window }; document.dispatchEvent(new MouseEvent('mousemove', o)); document.dispatchEvent(new PointerEvent('pointermove', o)); return '鼠标移动(' + Math.round(x) + ',' + Math.round(y) + ')'; } function simClick() { const v = document.querySelector('video'); let x, y; if (v) { const r = v.getBoundingClientRect(); x = r.left + r.width * (0.3 + Math.random() * 0.4); y = r.top + r.height * (0.3 + Math.random() * 0.4); } else { x = 200 + Math.random() * (window.innerWidth - 400); y = 200 + Math.random() * (window.innerHeight - 400); } const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window, button: 0 }; document.dispatchEvent(new MouseEvent('mousedown', o)); document.dispatchEvent(new MouseEvent('mouseup', o)); document.dispatchEvent(new MouseEvent('click', o)); return '左键点击'; } function simDblClick() { const v = document.querySelector('video'); let x, y; if (v) { const r = v.getBoundingClientRect(); x = r.left + r.width * 0.5; y = r.top + r.height * 0.5; } else { x = window.innerWidth / 2; y = window.innerHeight / 2; } const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window, button: 0, detail: 2 }; document.dispatchEvent(new MouseEvent('dblclick', o)); return '双击'; } function simRightClick() { const x = 150 + Math.random() * (window.innerWidth - 300); const y = 150 + Math.random() * (window.innerHeight - 300); const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window, button: 2 }; document.dispatchEvent(new MouseEvent('mousedown', o)); document.dispatchEvent(new MouseEvent('mouseup', o)); document.dispatchEvent(new MouseEvent('contextmenu', o)); return '右键菜单'; } function simDrag() { const v = document.querySelector('video'); let sx, sy, ex, ey; if (v) { const r = v.getBoundingClientRect(); sx = r.left + r.width * 0.2; sy = r.top + r.height * 0.5; ex = r.left + r.width * 0.7; ey = r.top + r.height * 0.5; } else { sx = 100 + Math.random() * 200; sy = window.innerHeight / 2; ex = sx + 50 + Math.random() * 150; ey = sy + (Math.random() - 0.5) * 50; } const steps = 4 + Math.floor(Math.random() * 4); const stepMs = 30 + Math.random() * 60; const mousedownO = { clientX: sx, clientY: sy, bubbles: true, cancelable: true, view: window, button: 0 }; document.dispatchEvent(new MouseEvent('mousedown', mousedownO)); for (let i = 1; i <= steps; i++) { const t = i / steps; const mx = sx + (ex - sx) * t; const my = sy + (ey - sy) * t; const mmO = { clientX: mx, clientY: my, bubbles: true, cancelable: true, view: window, button: 0 }; setTimeout(() => { document.dispatchEvent(new MouseEvent('mousemove', mmO)); if (i === steps) { document.dispatchEvent(new MouseEvent('mouseup', Object.assign({}, mmO))); } }, i * stepMs); } return '拖拽(' + Math.round(sx) + '→' + Math.round(ex) + ')'; } function simScroll() { const deltaY = (Math.random() - 0.4) * 250; window.scrollBy({ top: deltaY, behavior: 'smooth' }); document.dispatchEvent(new WheelEvent('wheel', { deltaY: deltaY, deltaX: (Math.random() - 0.5) * 20, clientX: window.innerWidth / 2, clientY: window.innerHeight / 2, bubbles: true, cancelable: true, view: window })); setTimeout(() => { if (stopped) return; window.scrollBy({ top: -deltaY * (0.5 + Math.random() * 0.4), behavior: 'smooth' }); }, 400 + Math.random() * 600); return '滚动(' + Math.round(deltaY) + 'px' + (deltaY > 0 ? '↓' : '↑') + ')'; } function simHoverVideo() { const v = document.querySelector('video'); if (!v) return simMouseMove(); const r = v.getBoundingClientRect(); const x = r.left + r.width * (0.15 + Math.random() * 0.7); const y = r.top + r.height * (0.7 + Math.random() * 0.25); const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window }; document.dispatchEvent(new MouseEvent('mousemove', o)); document.dispatchEvent(new PointerEvent('pointermove', o)); return '悬停视频进度条'; } function simMouseMoveToVideo() { const v = document.querySelector('video'); if (!v) return simMouseMove(); const r = v.getBoundingClientRect(); // 从随机位置逐步移到视频区域 const sx = 50 + Math.random() * (window.innerWidth - 100); const sy = 50 + Math.random() * (window.innerHeight - 100); const ex = r.left + r.width * (0.3 + Math.random() * 0.4); const ey = r.top + r.height * (0.3 + Math.random() * 0.4); const steps = 3 + Math.floor(Math.random() * 3); for (let i = 1; i <= steps; i++) { const t = i / steps; const mx = sx + (ex - sx) * t; const my = sy + (ey - sy) * t; const o = { clientX: mx, clientY: my, bubbles: true, cancelable: true, view: window }; setTimeout(() => { document.dispatchEvent(new MouseEvent('mousemove', o)); document.dispatchEvent(new PointerEvent('pointermove', o)); }, i * 40); } return '移至视频区域'; } function simTextSelect() { // 选中页面中一段文字 const sel = window.getSelection(); if (!sel) return null; const textNodes = []; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); let node; while ((node = walker.nextNode())) { if (node.textContent.trim().length > 10) textNodes.push(node); } if (textNodes.length === 0) return null; const tn = textNodes[Math.floor(Math.random() * textNodes.length)]; const len = tn.textContent.length; const start = Math.floor(Math.random() * Math.max(1, len - 5)); const end = Math.min(len, start + 3 + Math.floor(Math.random() * 8)); try { const range = document.createRange(); range.setStart(tn, start); range.setEnd(tn, end); sel.removeAllRanges(); sel.addRange(range); // 500ms 后取消选中 setTimeout(() => { try { sel.removeAllRanges(); } catch(e) {} }, 300 + Math.random() * 400); return '选中文字("' + tn.textContent.substring(start, end).trim() + '")'; } catch(e) { return null; } } function simTouchTap() { const x = 100 + Math.random() * (window.innerWidth - 200); const y = 150 + Math.random() * (window.innerHeight - 250); const o = { clientX: x, clientY: y, screenX: x, screenY: y, bubbles: true, cancelable: true, view: window }; document.dispatchEvent(new TouchEvent('touchstart', Object.assign({ touches: [o], targetTouches: [o], changedTouches: [o] }, { bubbles: true, cancelable: true }))); setTimeout(() => { document.dispatchEvent(new TouchEvent('touchend', Object.assign({ touches: [], targetTouches: [], changedTouches: [o] }, { bubbles: true, cancelable: true }))); }, 80 + Math.random() * 120); return '触屏点击'; } // ==================== 进度保存 API 监控 ==================== let progressMonitorInstalled = false; function installProgressMonitor() { if (progressMonitorInstalled) return; progressMonitorInstalled = true; const PROGRESS_URL = '/api/student/study/progress/save'; // 拦截 fetch const origFetch = window.fetch; window.fetch = function(url, opts) { const urlStr = typeof url === 'string' ? url : (url.url || ''); if (urlStr.includes(PROGRESS_URL)) { const body = opts && opts.body ? String(opts.body) : ''; const idMatch = body.match(/id=([^&]+)/); const rid = idMatch ? idMatch[1].substring(0, 8) : '?'; return origFetch.apply(this, arguments).then(r => { const cloned = r.clone(); cloned.text().then(t => { try { const j = JSON.parse(t); if (j.code === 0 && j.data != null) { const pct = parseFloat(j.data); lastProgressPct = pct; lastProgressUpdateTime = Date.now(); progressStuckCount = 0; const v = document.querySelector('video'); if (v) videoTimeAtLastProgress = v.currentTime; log('📡 进度上报: ' + pct.toFixed(1) + '% [课程:' + rid + ']', pct >= 90 ? 'ok' : 'info'); } } catch(e) {} }).catch(() => {}); return r; }); } return origFetch.apply(this, arguments); }; // 拦截 XMLHttpRequest const origOpen = XMLHttpRequest.prototype.open; const origSend = XMLHttpRequest.prototype.send; XMLHttpRequest.prototype.open = function(method, url) { this.__whut_url = url; return origOpen.apply(this, arguments); }; XMLHttpRequest.prototype.send = function(body) { if (this.__whut_url && this.__whut_url.includes(PROGRESS_URL)) { const bodyStr = body ? String(body) : ''; const idMatch = bodyStr.match(/id=([^&]+)/); const rid = idMatch ? idMatch[1].substring(0, 8) : '?'; this.addEventListener('load', function() { try { const j = JSON.parse(this.responseText); if (j.code === 0 && j.data != null) { const pct = parseFloat(j.data); lastProgressPct = pct; lastProgressUpdateTime = Date.now(); progressStuckCount = 0; const v = document.querySelector('video'); if (v) videoTimeAtLastProgress = v.currentTime; log('📡 进度上报: ' + pct.toFixed(1) + '% [课程:' + rid + ']', pct >= 90 ? 'ok' : 'info'); } } catch(e) {} }); } return origSend.apply(this, arguments); }; log('📡 进度上报监控已就绪', 'info'); } // ==================== URL 监听 ==================== let lastUrl = location.href; let urlWatcher = setInterval(() => { if (location.href !== lastUrl) { const prevUrl = lastUrl; lastUrl = location.href; log('📍 页面切换: ' + (isCoursePage() ? '课程页' : isDetailPage() ? '详情页' : '其他'), 'info'); if (!stopped) { startHumanSimulator(); // 始终保活真人模拟 if (isCoursePage()) { navigating = false; log('📖 进入课程页面,等待视频加载...', 'info'); // 从 loadState 恢复的数据渲染课程状态(不调 API,避免覆盖 scriptDone) renderCourseList(); updateCourseProgress(); setStatus('播放中...', currentCourse ? (currentCourse.courseName || currentCourse.name) : ''); setState('play'); let waited = 0; const w = setInterval(() => { waited++; if ($$('video').length > 0 || waited > 15) { clearInterval(w); startCourseLoop(); hackAllVideos(); } }, 1000); } else if (isDetailPage()) { navigating = false; log('📋 进入详情页,自动开始扫描...', 'info'); scanRetryCount = 0; setTimeout(() => { if (!stopped && isDetailPage()) scanAndStart(); }, 1500); } } } }, 1000); // ==================== 视频进度轮询 ==================== let vidProgressTimer = setInterval(() => { if (!stopped && isCoursePage()) updateVidProgress(); }, 2000); // ==================== 初始化 ==================== let videoObserver = null; function init() { log('🚀 脚本初始化 · 版本' + SCRIPT_VERSION + ' · ' + (isCoursePage() ? '课程页' : isDetailPage() ? '详情页' : '其他页面'), 'ok'); createPanel(); installProgressMonitor(); videoObserver = new MutationObserver(mutations => { for (const m of mutations) for (const node of m.addedNodes) { if (node.nodeName === 'VIDEO') hackVideo(node); if (node.querySelectorAll) node.querySelectorAll('video').forEach(hackVideo); } }).observe(document.body || document.documentElement, { childList: true, subtree: true }); // 从课程页回详情页的真刷新标记 const gotoDetail = sessionStorage.getItem('whut_goto_detail'); if (gotoDetail) { sessionStorage.removeItem('whut_goto_detail'); stopped = false; xmId = gotoDetail; loadState(); // 恢复进度/课程列表等 startHumanSimulator(); location.href = '#/myTrain/detail?id=' + encodeURIComponent(xmId); return; } const gotoCourse = sessionStorage.getItem('whut_goto_course'); if (gotoCourse) { sessionStorage.removeItem('whut_goto_course'); try { const c = JSON.parse(gotoCourse); stopped = false; xmId = c.xmId; loadState(); // 恢复 finishedDuration、进度、currentCourse、courseList startHumanSimulator(); location.href = '#/myTrain/course?id=' + encodeURIComponent(c.resourceId) + '&xmId=' + encodeURIComponent(c.xmId); } catch(e) {} return; } // 尝试恢复上次状态 const restored = loadState(); if (restored === 'reload_recovery') { log('后台重载恢复 · 培训班: ' + xmId, 'ok'); log('恢复进度: ' + stats.completed + '/' + stats.total, 'info'); if (courseList.length > 0) log('待完成: ' + courseList.length + ' 门', 'info'); stopped = false; startHumanSimulator(); } else if (restored === 'session_restore') { log('已恢复上次会话 · 培训班ID: ' + xmId, 'ok'); log('上次进度: ' + stats.completed + '/' + stats.total, 'info'); startHumanSimulator(); } if (isCoursePage() && !stopped) { xmId = getXmId() || xmId; if (xmId) { const v = $('video'); if (v && v.currentTime > 0) finishedDuration = v.currentTime; // 如果是重载恢复且已有 courseList,直接接管;否则异步拉取 if (restored !== 'reload_recovery' || courseList.length === 0) { fetchCourseStats(); } else { updateCourseProgress(); } log('已检测到课程页面,自动接管', 'ok'); setStatus('后台运行中', currentCourse ? (currentCourse.courseName || currentCourse.name) : ''); setState('play'); startCourseLoop(); } else { // xmId 缺失时延迟重试(页面可能尚未渲染完毕) log('课程页 xmId 缺失,1.5s后重试...', 'warn'); setTimeout(() => { if (!stopped && isCoursePage()) { xmId = getXmId() || GM_getValue('whut_xmId', ''); if (xmId) { log('xmId 重试成功,自动接管', 'ok'); fetchCourseStats(); setStatus('后台运行中', currentCourse ? (currentCourse.courseName || currentCourse.name) : ''); setState('play'); startCourseLoop(); } else { log('课程页 xmId 重试失败,等待URL变化触发', 'warn'); } } }, 1500); } } else if (isDetailPage()) { xmId = getXmId() || xmId; if (!xmId) { setStatus('请进入培训班', '点击左侧「我的培训」→ 选择培训班'); setState('idle'); return; } // 始终拉取并渲染全部课程状态 scanAndRender(); if (restored === 'reload_recovery' && !stopped && courseList.length > 0) { log('重载恢复:自动进入下一门课...', 'ok'); setTimeout(() => { navigating = true; enterNextCourse(); }, 800); } else if (restored === 'session_restore' && !stopped) { log('检测到未完成任务,自动恢复...', 'ok'); setTimeout(() => { if (isDetailPage()) scanAndStart(); }, 1000); } else { // 始终自动开始,延迟确保 SPA 渲染完成 log('自动开始学习...', 'ok'); const tryStart = (delay, max) => { setTimeout(() => { if (!isDetailPage() || stopped) return; xmId = getXmId(); if (xmId) { scanAndStart(); return; } if (delay < max) tryStart(delay * 2, max); else log('等待超时,页面可能未正确加载', 'warn'); }, delay); }; tryStart(1000, 8000); } } else { setStatus('请进入培训班', '点击左侧「我的培训」→ 选择培训班'); setState('idle'); log('请进入培训班详情页后使用', 'warn'); } } if (document.readyState === 'complete') init(); else window.addEventListener('load', init); })();