utkarshpathak48 commited on
Commit
6cdec4b
·
verified ·
1 Parent(s): 9b5fcbf

Restore main.py: fix test-telegram probe + 30s timeout

Browse files
Files changed (1) hide show
  1. main.py +451 -1
main.py CHANGED
@@ -1 +1,451 @@
1
- IiIiVHJhZGUgQ29waWxvdCDigJQgRmFzdEFQSSBzZXJ2ZXIuCgpFbmRwb2ludHM6CiAgR0VUIC8gICAgICAgICAgICDihpIgZGFzaGJvYXJkIEhUTUwgKHNlcnZlZCBmcm9tIHN0YXRpYy9pbmRleC5odG1sKQogIEdFVCAvYXBpL3NjYW4gICAg4oaSIHJhbmtlZCBzaG9ydGxpc3QgSlNPTgogIEdFVCAvYXBpL2NhcmQve3N5bWJvbH0g4oaSIGZ1bGwgdHJhZGUgY2FyZCBmb3Igb25lIHN5bWJvbAogIEdFVCAvYXBpL3N0YXR1cyAg4oaSIGhlYWx0aCArIGFjdGl2ZSBzb3VyY2UKCkFsbCBjb21wdXRhdGlvbiBpcyBkZXRlcm1pbmlzdGljIOKAlCBubyBBSSBpbiB0aGUgZGF0YSBwYXRoLgoiIiIKZnJvbSBfX2Z1dHVyZV9fIGltcG9ydCBhbm5vdGF0aW9ucwppbXBvcnQgbWF0aCwgc3RhdGlzdGljcywgdGltZSwgb3MKZnJvbSBwYXRobGliIGltcG9ydCBQYXRoCmZyb20gZnVuY3Rvb2xzIGltcG9ydCBscnVfY2FjaGUKZnJvbSBkYXRldGltZSBpbXBvcnQgZGF0ZXRpbWUsIHRpbWV6b25lCgppbXBvcnQgYXN5bmNpbwppbXBvcnQgbG9nZ2luZwpmcm9tIGNvbnRleHRsaWIgaW1wb3J0IGFzeW5jY29udGV4dG1hbmFnZXIKCmZyb20gZmFzdGFwaSBpbXBvcnQgRmFzdEFQSSwgSFRUUEV4Y2VwdGlvbiwgUXVlcnkKZnJvbSBmYXN0YXBpLnN0YXRpY2ZpbGVzIGltcG9ydCBTdGF0aWNGaWxlcwpmcm9tIGZhc3RhcGkucmVzcG9uc2VzIGltcG9ydCBKU09OUmVzcG9uc2UsIEZpbGVSZXNwb25zZQoKZnJvbSBleGNoYW5nZSBpbXBvcnQgcGlja19zb3VyY2UsIFNPVVJDRVMsIEJpbmdYLCBCaW5hbmNlCmZyb20gc2NvcmVyIGltcG9ydCBzY29yZV9zeW1ib2wKZnJvbSBjYXRhbHlzdCBpbXBvcnQgZmV0Y2hfZmVhcl9ncmVlZCwgZmV0Y2hfY29pbl9uZXdzLCBzY29yZV9jYXRhbHlzdCwgZmV0Y2hfY3J5cHRvX2hlYWRsaW5lcwpmcm9tIHNpZ25hbHMgaW1wb3J0IHNjYW5fdW5pdmVyc2UsIGJ1aWxkX3NpZ25hbF91bml2ZXJzZQpmcm9tIGJhY2t0ZXN0IGltcG9ydCBiYWNrdGVzdF9zeW1ib2wKZnJvbSBzaWduYWxfbG9nIGltcG9ydCBsb2dfc2lnbmFsLCByZXNvbHZlX3NpZ25hbHMsIGdldF9hY2N1cmFjeSwgZ2V0X3N5bWJvbF9hY2N1cmFjeQoKIyBDcnlwdG9QYW5pYyB0b2tlbiDigJQgc2V0IGluIEhGIFNwYWNlcyBTZWNyZXRzIGFzIENSWVBUT1BBTklDX1RPS0VOCiMgTmV2ZXIgaGFyZGNvZGUgb3IgbG9nIHRoaXMgdmFsdWUKQ1JZUFRPUEFOSUNfVE9LRU4gPSBvcy5lbnZpcm9uLmdldCgiQ1JZUFRPUEFOSUNfVE9LRU4iLCAiIikKCmxvZ2dlciA9IGxvZ2dpbmcuZ2V0TG9nZ2VyKCJtYWluIikKCkFMRVJUX0lOVEVSVkFMID0gMzAwICAjIDUgbWludXRlcwoKYXN5bmMgZGVmIF9hbGVydF9sb29wKCk6CiAgICAiIiJCYWNrZ3JvdW5kIHRhc2s6IHNjYW4gZXZlcnkgNSBtaW4gYW5kIGZpcmUgVGVsZWdyYW0gYWxlcnRzIGZvciBoaWdoLWNvbmZpZGVuY2UgY2FyZHMuIiIiCiAgICBmcm9tIHRlbGVncmFtX2FsZXJ0IGltcG9ydCBzZW5kX2FsZXJ0CiAgICAjIFJlYWQgYWNjb3VudCBzaXplIGZyb20gSEYgU2VjcmV0IOKAlCBkZWZhdWx0cyB0byAxMCBpZiBub3Qgc2V0CiAgICB0cnk6CiAgICAgICAgYWxlcnRfYWNjb3VudCA9IGZsb2F0KG9zLmVudmlyb24uZ2V0KCJBTEVSVF9BQ0NPVU5UIiwgIjEwIikpCiAgICBleGNlcHQgVmFsdWVFcnJvcjoKICAgICAgICBhbGVydF9hY2NvdW50ID0gMTAuMAogICAgbG9nZ2VyLmluZm8oIkFsZXJ0IGxvb3Agc3RhcnRpbmcg4oCUIGFjY291bnQ9JCUuMmYiLCBhbGVydF9hY2NvdW50KQoKICAgIGF3YWl0IGFzeW5jaW8uc2xlZXAoNjApICAjIGdpdmUgYXBwIHRpbWUgdG8gZnVsbHkgYm9vdCBiZWZvcmUgZmlyc3Qgc2NhbgogICAgd2hpbGUgVHJ1ZToKICAgICAgICB0cnk6CiAgICAgICAgICAgIHNyYyA9IGdldF9zb3VyY2UoKQogICAgICAgICAgICBzZWNvbmRhcnkgPSBnZXRfc2Vjb25kYXJ5KCkKICAgICAgICAgICAgY2FuZGlkYXRlcyA9IHJhbmtfdW5pdmVyc2Uoc3JjLCBzcmMubWluX3ZvbCwgMzApCiAgICAgICAgICAgIGZvciBjIGluIGNhbmRpZGF0ZXM6CiAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgY2FyZCA9IHNjb3JlX3N5bWJvbChzcmMsIGNbInN5bWJvbCJdLCBhbGVydF9hY2NvdW50LCAxLjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWNvbmRhcnk9c2Vjb25kYXJ5LCB1c2VyX2xldmVyYWdlPTYuMCkKICAgICAgICAgICAgICAgICAgICBjb25mICAgPSBjYXJkLmdldCgiY29uZmlkZW5jZSIsIDApCiAgICAgICAgICAgICAgICAgICAgdmV0b2VkID0gY2FyZC5nZXQoInZldG9lZCIsIEZhbHNlKQogICAgICAgICAgICAgICAgICAgIGlmIGNvbmYgPj0gOC44IGFuZCBub3QgdmV0b2VkOgogICAgICAgICAgICAgICAgICAgICAgICB0dl9zeW0gPSBzcmMudHZfc3ltYm9sKGNhcmRbInN5bWJvbCJdKQogICAgICAgICAgICAgICAgICAgICAgICBhd2FpdCBzZW5kX2FsZXJ0KGNhcmQsIHR2X3N5bSkKICAgICAgICAgICAgICAgICAgICAgICAgYXdhaXQgYXN5bmNpby5zbGVlcCgxKSAgICMgc21hbGwgZ2FwIGJldHdlZW4gc2VuZHMKICAgICAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgICAgICAgICBsb2dnZXIuZGVidWcoIkFsZXJ0IHNjYW4gZXJyb3IgZm9yICVzOiAlcyIsIGMuZ2V0KCJzeW1ib2wiKSwgZSkKICAgICAgICAgICAgICAgIGF3YWl0IGFzeW5jaW8uc2xlZXAoMC41KSAgIyBnZW50bGUgcmF0ZSBsaW1pdCBiZXR3ZWVuIHN5bWJvbHMKICAgICAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgICAgIGxvZ2dlci5lcnJvcigiQWxlcnQgbG9vcCBlcnJvcjogJXMiLCBlKQogICAgICAgIGF3YWl0IGFzeW5jaW8uc2xlZXAoQUxFUlRfSU5URVJWQUwpCgoKIyBFdmVudCBsb29wIHJlZmVyZW5jZSDigJQgY2FwdHVyZWQgYXQgc3RhcnR1cCBzbyBzeW5jIGVuZHBvaW50cyBjYW4gc2NoZWR1bGUgY29yb3V0aW5lcwpfZXZlbnRfbG9vcDogYXN5bmNpby5BYnN0cmFjdEV2ZW50TG9vcCB8IE5vbmUgPSBOb25lCgoKQGFzeW5jY29udGV4dG1hbmFnZXIKYXN5bmMgZGVmIGxpZmVzcGFuKGFwcCk6CiAgICBnbG9iYWwgX2V2ZW50X2xvb3AKICAgIF9ldmVudF9sb29wID0gYXN5bmNpby5nZXRfcnVubmluZ19sb29wKCkKICAgIHRhc2sgPSBhc3luY2lvLmNyZWF0ZV90YXNrKF9hbGVydF9sb29wKCkpCiAgICB0cnk6CiAgICAgICAgeWllbGQKICAgIGZpbmFsbHk6CiAgICAgICAgdGFzay5jYW5jZWwoKQogICAgICAgIHRyeToKICAgICAgICAgICAgYXdhaXQgdGFzawogICAgICAgIGV4Y2VwdCBhc3luY2lvLkNhbmNlbGxlZEVycm9yOgogICAgICAgICAgICBwYXNzCgoKYXBwID0gRmFzdEFQSSh0aXRsZT0iVHJhZGUgQ29waWxvdCIsIHZlcnNpb249IjEuMCIsIGxpZmVzcGFuPWxpZmVzcGFuKQoKIyBDYWNoZSB0aGUgYWN0aXZlIHNvdXJjZSBmb3IgNSBtaW4gKGF2b2lkcyByZS1waW5naW5nIG9uIGV2ZXJ5IHJlcXVlc3QpCl9zb3VyY2VfY2FjaGU6IGRpY3QgPSB7fQpfc291cmNlX3RzOiBmbG9hdCA9IDAKU09VUkNFX1RUTCA9IDMwMAoKIyBTZWNvbmRhcnkgc291cmNlIGZvciBjcm9zcy12YWxpZGF0aW9uIChCaW5hbmNlIOKAlCBkZWVwZXIgT0kvZnVuZGluZyBkYXRhKQpfYmluYW5jZTogQmluYW5jZSB8IE5vbmUgPSBOb25lCl9iaW5hbmNlX29rOiBib29sID0gRmFsc2UKX2JpbmFuY2VfdHM6IGZsb2F0ID0gMApCSU5BTkNFX1RUTCA9IDMwMAoKCmRlZiBnZXRfc291cmNlKCk6CiAgICBnbG9iYWwgX3NvdXJjZV9jYWNoZSwgX3NvdXJjZV90cwogICAgaWYgdGltZS50aW1lKCkgLSBfc291cmNlX3RzID4gU09VUkNFX1RUTCBvciBub3QgX3NvdXJjZV9jYWNoZS5nZXQoInNyYyIpOgogICAgICAgIHNyYyA9IHBpY2tfc291cmNlKCkKICAgICAgICBfc291cmNlX2NhY2hlID0geyJzcmMiOiBzcmN9CiAgICAgICAgX3NvdXJjZV90cyA9IHRpbWUudGltZSgpCiAgICByZXR1cm4gX3NvdXJjZV9jYWNoZVsic3JjIl0KCgpkZWYgZ2V0X3NlY29uZGFyeSgpOgogICAgIiIiUmV0dXJucyBCaW5hbmNlIGluc3RhbmNlIGlmIHJlYWNoYWJsZSwgZWxzZSBOb25lLiBVc2VkIGZvciBjcm9zcy12YWxpZGF0aW9uLiIiIgogICAgZ2xvYmFsIF9iaW5hbmNlLCBfYmluYW5jZV9vaywgX2JpbmFuY2VfdHMKICAgIGlmIHRpbWUudGltZSgpIC0gX2JpbmFuY2VfdHMgPCBCSU5BTkNFX1RUTDoKICAgICAgICByZXR1cm4gX2JpbmFuY2UgaWYgX2JpbmFuY2Vfb2sgZWxzZSBOb25lCiAgICB0cnk6CiAgICAgICAgYiA9IEJpbmFuY2UoKQogICAgICAgIGIucGluZygpCiAgICAgICAgX2JpbmFuY2UgPSBiCiAgICAgICAgX2JpbmFuY2Vfb2sgPSBUcnVlCiAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgIF9iaW5hbmNlID0gTm9uZQogICAgICAgIF9iaW5hbmNlX29rID0gRmFsc2UKICAgIF9iaW5hbmNlX3RzID0gdGltZS50aW1lKCkKICAgIHJldHVybiBfYmluYW5jZSBpZiBfYmluYW5jZV9vayBlbHNlIE5vbmUKCgpkZWYgcmFua191bml2ZXJzZShzcmMsIG1pbl92b2x1bWU6IGZsb2F0LCB0b3BfbjogaW50KSAtPiBsaXN0W2RpY3RdOgogICAgcGVycHMgPSBzcmMudW5pdmVyc2UoKQogICAgcm93cyA9IFtdCiAgICBmb3IgdCBpbiBzcmMudGlja2VycygpOgogICAgICAgIGlmIHRbInN5bWJvbCJdIG5vdCBpbiBwZXJwczoKICAgICAgICAgICAgY29udGludWUKICAgICAgICBxdiA9IHRbInF1b3RlX3ZvbHVtZV91c2QiXQogICAgICAgIGxvLCBoaSA9IHQuZ2V0KCJsb3ciLCAwKSwgdC5nZXQoImhpZ2giLCAwKQogICAgICAgIGlmIHF2IDwgbWluX3ZvbHVtZSBvciBsbyA8PSAwOgogICAgICAgICAgICBjb250aW51ZQogICAgICAgIHRbInJhbmdlXzI0aF9wY3QiXSA9IChoaSAtIGxvKSAvIGxvICogMTAwCiAgICAgICAgcm93cy5hcHBlbmQodCkKICAgIGlmIGxlbihyb3dzKSA8IDM6CiAgICAgICAgcmV0dXJuIFt7InN5bWJvbCI6IHN9IGZvciBzIGluIHNyYy5hbmNob3JzXQoKICAgIGxvZ192b2xzID0gW21hdGgubG9nKHJbInF1b3RlX3ZvbHVtZV91c2QiXSkgZm9yIHIgaW4gcm93c10KICAgIHJhbmdlcyA9IFtyWyJyYW5nZV8yNGhfcGN0Il0gZm9yIHIgaW4gcm93c10KICAgIG12LCBzdiA9IHN0YXRpc3RpY3MubWVhbihsb2dfdm9scyksIHN0YXRpc3RpY3Muc3RkZXYobG9nX3ZvbHMpCiAgICBtciwgc3IgPSBzdGF0aXN0aWNzLm1lYW4ocmFuZ2VzKSwgc3RhdGlzdGljcy5zdGRldihyYW5nZXMpCiAgICBmb3IgciwgbHYsIHJnIGluIHppcChyb3dzLCBsb2dfdm9scywgcmFuZ2VzKToKICAgICAgICByWyJzY2FuX3Njb3JlIl0gPSByb3VuZCgwLjUgKiAobHYgLSBtdikgLyBzdiArIDAuNSAqIChyZyAtIG1yKSAvIHNyLCAzKQoKICAgIHJvd3Muc29ydChrZXk9bGFtYmRhIHI6IHJbInNjYW5fc2NvcmUiXSwgcmV2ZXJzZT1UcnVlKQogICAgcGlja2VkID0gcm93c1s6dG9wX25dCiAgICBwaWNrZWRfc3ltcyA9IHtyWyJzeW1ib2wiXSBmb3IgciBpbiBwaWNrZWR9CiAgICBmb3Igc3ltIGluIHNyYy5hbmNob3JzOgogICAgICAgIGlmIHN5bSBub3QgaW4gcGlja2VkX3N5bXM6CiAgICAgICAgICAgIGV4dHJhID0gbmV4dCgociBmb3IgciBpbiByb3dzIGlmIHJbInN5bWJvbCJdID09IHN5bSksIE5vbmUpCiAgICAgICAgICAgIGlmIGV4dHJhOgogICAgICAgICAgICAgICAgZXh0cmFbImFuY2hvciJdID0gVHJ1ZQogICAgICAgICAgICAgICAgcGlja2VkLmFwcGVuZChleHRyYSkKICAgIHJldHVybiBwaWNrZWQKCgojIOKUgOKUgCBBUEkgcm91dGVzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKQGFwcC5nZXQoIi9hcGkvc3RhdHVzIikKZGVmIHN0YXR1cygpOgogICAgdHJ5OgogICAgICAgIHNyYyA9IGdldF9zb3VyY2UoKQogICAgICAgIHJldHVybiB7Im9rIjogVHJ1ZSwgInNvdXJjZSI6IHNyYy5uYW1lLCAibWFya2V0Ijogc3JjLm1hcmtldF9ub3RlLAogICAgICAgICAgICAgICAgInRzIjogZGF0ZXRpbWUubm93KHRpbWV6b25lLnV0YykuaXNvZm9ybWF0KCl9CiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcmV0dXJuIEpTT05SZXNwb25zZSh7Im9rIjogRmFsc2UsICJlcnJvciI6IHN0cihlKX0sIHN0YXR1c19jb2RlPTUwMykKCgpAYXBwLmdldCgiL2FwaS9zY2FuIikKZGVmIHNjYW4odG9wOiBpbnQgPSA4LCBtaW5fdm9sdW1lOiBmbG9hdCA9IE5vbmUsIGFjY291bnQ6IGZsb2F0ID0gMTAwMDAwLAogICAgICAgICByaXNrX3BjdDogZmxvYXQgPSAxLjAsCiAgICAgICAgIHVzZXJfbGV2ZXJhZ2U6IGZsb2F0ID0gUXVlcnkoZGVmYXVsdD02LjAsIGdlPTEuMCwgbGU9MTAwLjApKToKICAgIHRyeToKICAgICAgICBzcmMgPSBnZXRfc291cmNlKCkKICAgICAgICBmbG9vciA9IG1pbl92b2x1bWUgb3Igc3JjLm1pbl92b2wKICAgICAgICBjYW5kaWRhdGVzID0gcmFua191bml2ZXJzZShzcmMsIGZsb29yLCB0b3ApCiAgICAgICAgc2Vjb25kYXJ5ID0gZ2V0X3NlY29uZGFyeSgpCiAgICAgICAgY2FyZHMgPSBbXQogICAgICAgIGZvciBjIGluIGNhbmRpZGF0ZXM6CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIGNhcmQgPSBzY29yZV9zeW1ib2woc3JjLCBjWyJzeW1ib2wiXSwgYWNjb3VudCwgcmlza19wY3QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlY29uZGFyeT1zZWNvbmRhcnksIHVzZXJfbGV2ZXJhZ2U9dXNlcl9sZXZlcmFnZSkKICAgICAgICAgICAgICAgIGNhcmRbInJhbmtfaW5mbyJdID0ge2s6IGMuZ2V0KGspIGZvciBrIGluCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoInNjYW5fc2NvcmUiLCJjaGFuZ2VfMjRoX3BjdCIsInJhbmdlXzI0aF9wY3QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJxdW90ZV92b2x1bWVfdXNkIiwiYW5jaG9yIil9CiAgICAgICAgICAgICAgICBjYXJkcy5hcHBlbmQoY2FyZCkKICAgICAgICAgICAgICAgICMgQXV0by1sb2cgZXZlcnkgbm9uLXZldG9lZCBzaWduYWwgZm9yIGxpdmUgYWNjdXJhY3kgdHJhY2tpbmcKICAgICAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgICAgICBsb2dfc2lnbmFsKGNhcmQpCiAgICAgICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAgICAgICAgIHBhc3MKICAgICAgICAgICAgICAgICMgRmlyZSBUZWxlZ3JhbSBhbGVydCBpZiBjb25maWRlbmNlID49IDkuMAogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIGNvbmYgICA9IGNhcmQuZ2V0KCJjb25maWRlbmNlIiwgMCkKICAgICAgICAgICAgICAgICAgICB2ZXRvZWQgPSBjYXJkLmdldCgidmV0b2VkIiwgRmFsc2UpCiAgICAgICAgICAgICAgICAgICAgaWYgY29uZiA+PSA4LjggYW5kIG5vdCB2ZXRvZWQgYW5kIF9ldmVudF9sb29wIGlzIG5vdCBOb25lOgogICAgICAgICAgICAgICAgICAgICAgICBmcm9tIHRlbGVncmFtX2FsZXJ0IGltcG9ydCBzZW5kX2FsZXJ0CiAgICAgICAgICAgICAgICAgICAgICAgIHR2X3N5bSA9IHNyYy50dl9zeW1ib2woY2FyZFsic3ltYm9sIl0pCiAgICAgICAgICAgICAgICAgICAgICAgIGFzeW5jaW8ucnVuX2Nvcm91dGluZV90aHJlYWRzYWZlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VuZF9hbGVydChjYXJkLCB0dl9zeW0sIG1hbnVhbD1UcnVlKSwgX2V2ZW50X2xvb3AKICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgICAgICAgICB0aW1lLnNsZWVwKDAuMDUpCiAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICAgICAgICAgIGNhcmRzLmFwcGVuZCh7InN5bWJvbCI6IGNbInN5bWJvbCJdLCAiZXJyb3IiOiBzdHIoZSlbOjEyMF19KQogICAgICAgIGNhcmRzLnNvcnQoa2V5PWxhbWJkYSBjOiBjLmdldCgiY29uZmlkZW5jZSIsIDApLCByZXZlcnNlPVRydWUpCiAgICAgICAgcmV0dXJuIHsiZmV0Y2hlZF9hdCI6IGRhdGV0aW1lLm5vdyh0aW1lem9uZS51dGMpLmlzb2Zvcm1hdCgpLAogICAgICAgICAgICAgICAgInNvdXJjZSI6IHNyYy5uYW1lLCAibWFya2V0Ijogc3JjLm1hcmtldF9ub3RlLCAiY2FyZHMiOiBjYXJkc30KICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKDUwMywgc3RyKGUpKQoKCkBhcHAuZ2V0KCIvYXBpL3ByaWNlcyIpCmRlZiBwcmljZXMoc3ltYm9sczogc3RyID0gIiIpOgogICAgIiIiTGlnaHR3ZWlnaHQgcHJpY2UgdGlja2VyIOKAlCByZXR1cm5zIGN1cnJlbnQgcHJpY2UgKyAyNGggY2hhbmdlIGZvciBhCiAgICBjb21tYS1zZXBhcmF0ZWQgbGlzdCBvZiBzeW1ib2xzLiBVc2VkIGZvciByZWFsLXRpbWUgY2FyZCBwcmljZSB1cGRhdGVzLiIiIgogICAgdHJ5OgogICAgICAgIHNyYyA9IGdldF9zb3VyY2UoKQogICAgICAgIHJlcXVlc3RlZCA9IHtzLnN0cmlwKCkgZm9yIHMgaW4gc3ltYm9scy5zcGxpdCgiLCIpIGlmIHMuc3RyaXAoKX0KICAgICAgICB0aWNrZXJzID0gc3JjLnRpY2tlcnMoKQogICAgICAgIHJlc3VsdCA9IHt9CiAgICAgICAgZm9yIHQgaW4gdGlja2VyczoKICAgICAgICAgICAgc3ltID0gdC5nZXQoInN5bWJvbCIsICIiKQogICAgICAgICAgICBpZiBub3QgcmVxdWVzdGVkIG9yIHN5bSBpbiByZXF1ZXN0ZWQ6CiAgICAgICAgICAgICAgICByZXN1bHRbc3ltXSA9IHsKICAgICAgICAgICAgICAgICAgICAicHJpY2UiOiB0LmdldCgibGFzdF9wcmljZSIpLAogICAgICAgICAgICAgICAgICAgICJjaGFuZ2VfMjRoX3BjdCI6IHQuZ2V0KCJjaGFuZ2VfMjRoX3BjdCIpLAogICAgICAgICAgICAgICAgICAgICJoaWdoIjogdC5nZXQoImhpZ2giKSwKICAgICAgICAgICAgICAgICAgICAibG93IjogdC5nZXQoImxvdyIpLAogICAgICAgICAgICAgICAgICAgICJxdW90ZV92b2x1bWVfdXNkIjogdC5nZXQoInF1b3RlX3ZvbHVtZV91c2QiKSwKICAgICAgICAgICAgICAgIH0KICAgICAgICByZXR1cm4geyJ0cyI6IGRhdGV0aW1lLm5vdyh0aW1lem9uZS51dGMpLmlzb2Zvcm1hdCgpLCAicHJpY2VzIjogcmVzdWx0fQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oNTAzLCBzdHIoZSkpCgoKQGFwcC5nZXQoIi9hcGkvY2FyZC97c3ltYm9sfSIpCmRlZiBjYXJkKHN5bWJvbDogc3RyLCBhY2NvdW50OiBmbG9hdCA9IDEwMDAwMCwgcmlza19wY3Q6IGZsb2F0ID0gMS4wLAogICAgICAgICB1c2VyX2xldmVyYWdlOiBmbG9hdCA9IFF1ZXJ5KGRlZmF1bHQ9Ni4wLCBnZT0xLjAsIGxlPTEwMC4wKSk6CiAgICB0cnk6CiAgICAgICAgc3JjID0gZ2V0X3NvdXJjZSgpCiAgICAgICAgc2Vjb25kYXJ5ID0gZ2V0X3NlY29uZGFyeSgpCiAgICAgICAgcmVzdWx0ID0gc2NvcmVfc3ltYm9sKHNyYywgc3ltYm9sLnVwcGVyKCksIGFjY291bnQsIHJpc2tfcGN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWNvbmRhcnk9c2Vjb25kYXJ5LCB1c2VyX2xldmVyYWdlPXVzZXJfbGV2ZXJhZ2UpCiAgICAgICAgIyBMb2cgdGhlIHNpZ25hbCBmb3IgbGl2ZSBhY2N1cmFjeSB0cmFja2luZwogICAgICAgIHRyeToKICAgICAgICAgICAgbG9nX3NpZ25hbChyZXN1bHQpCiAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgcGFzcwogICAgICAgIHJldHVybiByZXN1bHQKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKDUwMywgc3RyKGUpKQoKCkBhcHAuZ2V0KCIvYXBpL3NpZ25hbHMiKQpkZWYgc2lnbmFscygpOgogICAgIiIiU2NhbiBtaWQtY2FwIHBlcnBzICh2b2x1bWUgcmFua3MgNi01MCkgZm9yIGV2ZW50LWRyaXZlbiBzaWduYWxzLgoKICAgIFVzZXMgNSBpbmRlcGVuZGVudCBkZXRlY3RvcnM6IFJTSSBleHRyZW1lIGNyb3NzLCB2b2x1bWUgc3VyZ2UsCiAgICBCQiBzcXVlZXplIHJlbGVhc2UsIGZ1bmRpbmcgcmF0ZSBleHRyZW1lLCBhbmQgMjRoIHByaWNlIHN1cmdlLgogICAgRWFjaCBmaXJlcyBpbmRlcGVuZGVudGx5IOKAlCBubyBjb21iaW5lZCBnYXRlIHJlcXVpcmVkLgogICAgQlRDIGFuZCBFVEggYWx3YXlzIGFwcGVuZGVkIGFzIG1hcmtldC13aWRlIHNlbnRpbWVudCBhbmNob3JzLgoKICAgIFJlc3VsdHMgY2FjaGVkIDUgbWluIHBlciBzeW1ib2wgaW5zaWRlIHNpZ25hbHMucHkuCiAgICAiIiIKICAgIHRyeToKICAgICAgICBzcmMgPSBnZXRfc291cmNlKCkKICAgICAgICBzZWNvbmRhcnkgPSBnZXRfc2Vjb25kYXJ5KCkKCiAgICAgICAgIyBCdWlsZCB1bml2ZXJzZTogbWlkLWNhcCByYW5rcyA2LTUwIGJ5IFVTRCB2b2x1bWUgKyBCVEMvRVRIIGFuY2hvcnMKICAgICAgICB0cnk6CiAgICAgICAgICAgIHN5bWJvbHMsIHRpY2tlcl9tYXAgPSBidWlsZF9zaWduYWxfdW5pdmVyc2Uoc3JjKQogICAgICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgICAgIHN5bWJvbHMgPSBnZXRhdHRyKHNyYywgImFuY2hvcnMiLCBbXSlbOjIwXQogICAgICAgICAgICB0aWNrZXJfbWFwID0ge30KCiAgICAgICAgYWxlcnRzID0gc2Nhbl91bml2ZXJzZShzcmMsIHN5bWJvbHMsIHNlY29uZGFyeT1zZWNvbmRhcnksIHRpY2tlcl9tYXA9dGlja2VyX21hcCkKICAgICAgICByZXR1cm4gewogICAgICAgICAgICAiZmV0Y2hlZF9hdCI6IGRhdGV0aW1lLm5vdyh0aW1lem9uZS51dGMpLmlzb2Zvcm1hdCgpLAogICAgICAgICAgICAic291cmNlIjogc3JjLm5hbWUsCiAgICAgICAgICAgICJzY2FubmVkIjogbGVuKHN5bWJvbHMpLAogICAgICAgICAgICAiYWxlcnRzIjogYWxlcnRzLAogICAgICAgIH0KICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKDUwMywgc3RyKGUpKQoKCkBhcHAuZ2V0KCIvYXBpL25ld3MiKQpkZWYgbmV3cygpOgogICAgIiIiRnJlZSBjcnlwdG8gbmV3cyDigJQgQ29pbkdlY2tvIHRyZW5kaW5nICsgQ29pbkRlc2sgKyBDb2ludGVsZWdyYXBoIFJTUy4KCiAgICBObyBBUEkga2V5IHJlcXVpcmVkLiBSZXR1cm5zOgogICAgICB0cmVuZGluZ19jb2luczogdG9wIDcgY29pbnMgYnkgQ29pbkdlY2tvIHNlYXJjaCB2b2x1bWUgaW4gbGFzdCAyNGgKICAgICAgaGVhZGxpbmVzOiAgICAgIGxhdGVzdCBoZWFkbGluZXMgZnJvbSBDb2luRGVzayArIENvaW50ZWxlZ3JhcGggUlNTCiAgICAgICAgICAgICAgICAgICAgICBlYWNoIHRhZ2dlZCB3aXRoIGNvaW5zX21lbnRpb25lZFtdCiAgICBDYWNoZWQgMTAgbWluLgogICAgIiIiCiAgICB0cnk6CiAgICAgICAgcmVzdWx0ID0gZmV0Y2hfY3J5cHRvX2hlYWRsaW5lcygpCiAgICAgICAgcmV0dXJuIHJlc3VsdAogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oNTAzLCBzdHIoZSkpCgoKQGFwcC5nZXQoIi9hcGkvc2lnbmFsLWFjY3VyYWN5IikKZGVmIHNpZ25hbF9hY2N1cmFjeShzeW1ib2w6IHN0ciA9ICIiKToKICAgICIiIkxpdmUgc2lnbmFsIGFjY3VyYWN5IOKAlCByZWFsIHdpbiByYXRlIGZyb20gbG9nZ2VkICsgcmVzb2x2ZWQgc2lnbmFscy4KCiAgICBUaGlzIGlzIGdyb3VuZCB0cnV0aDogZXZlcnkgY2FyZCB0aGUgc2Nhbm5lciBnZW5lcmF0ZXMgZ2V0cyBsb2dnZWQsCiAgICB0aGVuIGNoZWNrZWQgNDhoIGxhdGVyIHRvIHNlZSBpZiBUUDEgb3IgU0wgd2FzIGhpdCBmaXJzdC4KCiAgICA/c3ltYm9sPUJUQy1VU0RUICAob3B0aW9uYWwg4oCUIHJldHVybnMgcGVyLXN5bWJvbCBicmVha2Rvd24pCiAgICAiIiIKICAgIHRyeToKICAgICAgICBvdmVyYWxsID0gZ2V0X2FjY3VyYWN5KCkKICAgICAgICBpZiBzeW1ib2w6CiAgICAgICAgICAgIG92ZXJhbGxbInN5bWJvbF9icmVha2Rvd24iXSA9IGdldF9zeW1ib2xfYWNjdXJhY3koc3ltYm9sLnVwcGVyKCkpCiAgICAgICAgcmV0dXJuIG92ZXJhbGwKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKDUwMywgc3RyKGUpKQoKCkBhcHAucG9zdCgiL2FwaS9yZXNvbHZlLXNpZ25hbHMiKQpkZWYgcmVzb2x2ZV9zaWduYWxzX2VuZHBvaW50KCk6CiAgICAiIiJUcmlnZ2VyIHJlc29sdXRpb24gb2YgYWxsIHBlbmRpbmcgc2lnbmFscyB0aGF0IGFyZSBwYXN0IHRoZWlyIDQ4aCB3aW5kb3cuCgogICAgQ2FsbGVkIG1hbnVhbGx5IG9yIGJ5IGEgc2NoZWR1bGVyLiBDaGVja3MgcHJpY2UgcmFuZ2Ugb24gZXhjaGFuZ2UKICAgIHRvIGRldGVybWluZSBpZiBUUDEgb3IgU0wgd2FzIHRvdWNoZWQgZHVyaW5nIHRoZSBob2xkIHBlcmlvZC4KICAgICIiIgogICAgdHJ5OgogICAgICAgIHNyYyA9IGdldF9zb3VyY2UoKQogICAgICAgIHJlc3VsdCA9IHJlc29sdmVfc2lnbmFscyhzcmMpCiAgICAgICAgcmV0dXJuIHJlc3VsdAogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHJhaXNlIEhUVFBFeGNlcHRpb24oNTAzLCBzdHIoZSkpCgoKQGFwcC5nZXQoIi9hcGkvY2F0YWx5c3QiKQpkZWYgY2F0YWx5c3Qoc3ltYm9sOiBzdHIgPSAiIik6CiAgICAiIiJEZWJ1ZyBlbmRwb2ludCDigJQgcmV0dXJucyByYXcgRmVhciAmIEdyZWVkICsgQ3J5cHRvUGFuaWMgZGF0YS4KICAgID9zeW1ib2w9QlRDLVVTRFQgIChvcHRpb25hbDsgcmV0dXJucyBjb2luIG5ld3MgaWYgcHJvdmlkZWQpCiAgICAiIiIKICAgIHRyeToKICAgICAgICBmbmcgPSBmZXRjaF9mZWFyX2dyZWVkKCkKICAgICAgICByZXN1bHQ6IGRpY3QgPSB7CiAgICAgICAgICAgICJmZWFyX2dyZWVkIjogewogICAgICAgICAgICAgICAgInZhbHVlIjogZm5nLmdldCgidmFsdWUiKSwKICAgICAgICAgICAgICAgICJsYWJlbCI6IGZuZy5nZXQoImxhYmVsIiksCiAgICAgICAgICAgICAgICAic2NvcmVfbW9kIjogZm5nLmdldCgic2NvcmVfbW9kIiksCiAgICAgICAgICAgICAgICAiZXJyb3IiOiBmbmcuZ2V0KCJlcnJvciIpLAogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGlmIHN5bWJvbDoKICAgICAgICAgICAgbmV3cyA9IGZldGNoX2NvaW5fbmV3cyhzeW1ib2wudXBwZXIoKSkKICAgICAgICAgICAgY29tYmluZWRfc2NvcmUsIG5vdGVzLCBfcmF3ID0gc2NvcmVfY2F0YWx5c3Qoc3ltYm9sLnVwcGVyKCkpCiAgICAgICAgICAgIHJlc3VsdFsic3ltYm9sIl0gPSBzeW1ib2wudXBwZXIoKQogICAgICAgICAgICByZXN1bHRbIm5ld3MiXSA9IHsKICAgICAgICAgICAgICAgICJzY29yZSI6IG5ld3MuZ2V0KCJzY29yZSIpLAogICAgICAgICAgICAgICAgImxhYmVsIjogbmV3cy5nZXQoImxhYmVsIiksCiAgICAgICAgICAgICAgICAic291cmNlIjogbmV3cy5nZXQoInNvdXJjZSIpLAogICAgICAgICAgICAgICAgIml0ZW1zIjogbmV3cy5nZXQoIml0ZW1zIiwgW10pLAogICAgICAgICAgICAgICAgImVycm9yIjogbmV3cy5nZXQoImVycm9yIiksCiAgICAgICAgICAgIH0KICAgICAgICAgICAgcmVzdWx0WyJjYXRhbHlzdF9zY29yZSJdID0gY29tYmluZWRfc2NvcmUKICAgICAgICAgICAgcmVzdWx0WyJub3RlcyJdID0gbm90ZXMKICAgICAgICByZXN1bHRbImNyeXB0b3BhbmljX2tleV9zZXQiXSA9IGJvb2woQ1JZUFRPUEFOSUNfVE9LRU4pCiAgICAgICAgcmVzdWx0WyJ0cyJdID0gZGF0ZXRpbWUubm93KHRpbWV6b25lLnV0YykuaXNvZm9ybWF0KCkKICAgICAgICByZXR1cm4gcmVzdWx0CiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcmFpc2UgSFRUUEV4Y2VwdGlvbig1MDMsIHN0cihlKSkKCgpAYXBwLmdldCgiL2FwaS90ZXN0LXRlbGVncmFtIikKZGVmIHRlc3RfdGVsZWdyYW0oKToKICAgICIiIkRpYWdub3NlIFRlbGVncmFtIHJlbGF5OiBwcm9iZSBXb3JrZXIgcmVhY2hhYmlsaXR5IHRoZW4gc2VuZCBtZXNzYWdlLiIiIgogICAgaW1wb3J0IHJlcXVlc3RzIGFzIHJlcQogICAgd29ya2VyX3VybCA9IG9zLmVudmlyb24uZ2V0KCJDTE9VREZMQVJFX1dPUktFUl9VUkwiLCAiIikKICAgIGlmIG5vdCB3b3JrZXJfdXJsOgogICAgICAgIHJldHVybiBKU09OUmVzcG9uc2UoeyJvayI6IEZhbHNlLCAiZXJyb3IiOiAiQ0xPVURGTEFSRV9XT1JLRVJfVVJMIG5vdCBzZXQgaW4gSEYgU2VjcmV0cyJ9LCBzdGF0dXNfY29kZT01MDApCgogICAgIyBQcm9iZTogR0VUIHRoZSB3b3JrZXIgKGV4cGVjdCA0MDUgLSBwcm92ZXMgSEYgY2FuIHJlYWNoIHdvcmtlcnMuZGV2KQogICAgcHJvYmUgPSB7fQogICAgdHJ5OgogICAgICAgIHIgPSByZXEuZ2V0KHdvcmtlcl91cmwsIHRpbWVvdXQ9OCkKICAgICAgICBwcm9iZVsid29ya2VyX2dldCJdID0geyJzdGF0dXMiOiByLnN0YXR1c19jb2RlLCAiYm9keSI6IHIudGV4dFs6ODBdfQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByb2JlWyJ3b3JrZXJfZ2V0Il0gPSB7ImVycm9yIjogdHlwZShlKS5fX25hbWVfXyArICI6ICIgKyBzdHIoZSlbOjEyMF19CgogICAgIyBTZW5kIHRoZSBUZWxlZ3JhbSBtZXNzYWdlIHdpdGggbG9uZ2VyIHRpbWVvdXQKICAgIG1zZyA9ICJUZXN0OiBUcmFkZSBDb3BpbG90IHJlbGF5IGNoZWNrIHZpYSBDbG91ZGZsYXJlIFdvcmtlci4iCiAgICB0cnk6CiAgICAgICAgcmVzcCA9IHJlcS5wb3N0KHdvcmtlcl91cmwsIGpzb249eyJ0ZXh0IjogbXNnLCAicGFyc2VfbW9kZSI6ICJIVE1MIn0sIHRpbWVvdXQ9MzApCiAgICAgICAgYm9keSA9IHJlc3AuanNvbigpCiAgICAgICAgaWYgcmVzcC5zdGF0dXNfY29kZSA9PSAyMDA6CiAgICAgICAgICAgIHJldHVybiB7Im9rIjogVHJ1ZSwgIm1lc3NhZ2UiOiAiVGVzdCBtZXNzYWdlIHNlbnQgdmlhIENGIFdvcmtlciIsICJwcm9iZSI6IHByb2JlfQogICAgICAgIHJldHVybiBKU09OUmVzcG9uc2UoeyJvayI6IEZhbHNlLCAic3RhdHVzIjogcmVzcC5zdGF0dXNfY29kZSwgImVycm9yIjogYm9keSwgInByb2JlIjogcHJvYmV9LCBzdGF0dXNfY29kZT01MDApCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcmV0dXJuIEpTT05SZXNwb25zZSh7Im9rIjogRmFsc2UsICJlcnJvciI6IHR5cGUoZSkuX19uYW1lX18gKyAiOiAiICsgc3RyKGUpWzoyMDBdLCAicHJvYmUiOiBwcm9iZX0sIHN0YXR1c19jb2RlPTUwMCkKCgpAYXBwLmdldCgiL2FwaS9iYWNrdGVzdC97c3ltYm9sfSIpCmRlZiBiYWNrdGVzdChzeW1ib2w6IHN0cik6CiAgICAiIiJXYWxrLWZvcndhcmQgYmFja3Rlc3QgZm9yIG9uZSBzeW1ib2wgb3ZlciB+NiBtb250aHMgb2YgMWggY2FuZGxlcy4KCiAgICBXaW4gY29uZGl0aW9uOiBUUDIgaGl0IGJlZm9yZSBTTC4KICAgIFJlc3VsdHMgY2FjaGVkIDZoIOKAlCBmaXJzdCBjYWxsIGlzIHNsb3cgKH41LTE1cyksIHN1YnNlcXVlbnQgY2FsbHMgaW5zdGFudC4KCiAgICBSZXR1cm5zOgogICAgICB0b3RhbF90cmFkZXMsIHdpbl9yYXRlLCBhdmdfciwgbWF4X2RyYXdkb3duX3IsCiAgICAgIGJ5X3N0YXRlIHtCVUxML0JFQVIvUkFOR0lORzoge3RyYWRlcywgd2lucywgd2luX3JhdGUsIGF2Z19yfX0sCiAgICAgIGJ5X2RpcmVjdGlvbiB7bG9uZy9zaG9ydDogLi4ufSwKICAgICAgZXF1aXR5X2N1cnZlIFtmbG9hdCBsaXN0XSwKICAgICAgcmVjZW50X3RyYWRlcyBbbGFzdCA1MF0KICAgICIiIgogICAgdHJ5OgogICAgICAgIHNyYyA9IGdldF9zb3VyY2UoKQogICAgICAgIHJlc3VsdCA9IGJhY2t0ZXN0X3N5bWJvbChzcmMsIHN5bWJvbC51cHBlcigpKQogICAgICAgIHJldHVybiByZXN1bHQKICAgIGV4Y2VwdCBFeGNlcHRpb24gYXMgZToKICAgICAgICByYWlzZSBIVFRQRXhjZXB0aW9uKDUwMywgc3RyKGUpKQoKCiMg4pSA4pSAIFN0YXRpYyBkYXNoYm9hcmQg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpzdGF0aWNfZGlyID0gUGF0aChfX2ZpbGVfXykucGFyZW50IC8gInN0YXRpYyIKaWYgc3RhdGljX2Rpci5leGlzdHMoKToKICAgIGFwcC5tb3VudCgiL3N0YXRpYyIsIFN0YXRpY0ZpbGVzKGRpcmVjdG9yeT1zdHIoc3RhdGljX2RpcikpLCBuYW1lPSJzdGF0aWMiKQoKQGFwcC5nZXQoIi8iKQpkZWYgcm9vdCgpOgogICAgaW5kZXggPSBzdGF0aWNfZGlyIC8gImluZGV4Lmh0bWwiCiAgICBpZiBpbmRleC5leGlzdHMoKToKICAgICAgICByZXR1cm4gRmlsZVJlc3BvbnNlKHN0cihpbmRleCkpCiAgICByZXR1cm4geyJtZXNzYWdlIjogIlRyYWRlIENvcGlsb3QgQVBJIHJ1bm5pbmcuIFVzZSAvYXBpL3NjYW4gb3IgL2FwaS9zdGF0dXMuIn0KCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgaW1wb3J0IHV2aWNvcm4KICAgIHBvcnQgPSBpbnQob3MuZW52aXJvbi5nZXQoIlBPUlQiLCA3ODYwKSkKICAgIHV2aWNvcm4ucnVuKCJtYWluOmFwcCIsIGhvc3Q9IjAuMC4wLjAiLCBwb3J0PXBvcnQpCg==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Trade Copilot — FastAPI server.
2
+
3
+ Endpoints:
4
+ GET / → dashboard HTML (served from static/index.html)
5
+ GET /api/scan → ranked shortlist JSON
6
+ GET /api/card/{symbol} → full trade card for one symbol
7
+ GET /api/status → health + active source
8
+
9
+ All computation is deterministic — no AI in the data path.
10
+ """
11
+ from __future__ import annotations
12
+ import math, statistics, time, os
13
+ from pathlib import Path
14
+ from functools import lru_cache
15
+ from datetime import datetime, timezone
16
+
17
+ import asyncio
18
+ import logging
19
+ from contextlib import asynccontextmanager
20
+
21
+ from fastapi import FastAPI, HTTPException, Query
22
+ from fastapi.staticfiles import StaticFiles
23
+ from fastapi.responses import JSONResponse, FileResponse
24
+
25
+ from exchange import pick_source, SOURCES, BingX, Binance
26
+ from scorer import score_symbol
27
+ from catalyst import fetch_fear_greed, fetch_coin_news, score_catalyst, fetch_crypto_headlines
28
+ from signals import scan_universe, build_signal_universe
29
+ from backtest import backtest_symbol
30
+ from signal_log import log_signal, resolve_signals, get_accuracy, get_symbol_accuracy
31
+
32
+ # CryptoPanic token — set in HF Spaces Secrets as CRYPTOPANIC_TOKEN
33
+ # Never hardcode or log this value
34
+ CRYPTOPANIC_TOKEN = os.environ.get("CRYPTOPANIC_TOKEN", "")
35
+
36
+ logger = logging.getLogger("main")
37
+
38
+ ALERT_INTERVAL = 300 # 5 minutes
39
+
40
+ async def _alert_loop():
41
+ """Background task: scan every 5 min and fire Telegram alerts for high-confidence cards."""
42
+ from telegram_alert import send_alert
43
+ # Read account size from HF Secret — defaults to 10 if not set
44
+ try:
45
+ alert_account = float(os.environ.get("ALERT_ACCOUNT", "10"))
46
+ except ValueError:
47
+ alert_account = 10.0
48
+ logger.info("Alert loop starting — account=$%.2f", alert_account)
49
+
50
+ await asyncio.sleep(60) # give app time to fully boot before first scan
51
+ while True:
52
+ try:
53
+ src = get_source()
54
+ secondary = get_secondary()
55
+ candidates = rank_universe(src, src.min_vol, 30)
56
+ for c in candidates:
57
+ try:
58
+ card = score_symbol(src, c["symbol"], alert_account, 1.0,
59
+ secondary=secondary, user_leverage=6.0)
60
+ conf = card.get("confidence", 0)
61
+ vetoed = card.get("vetoed", False)
62
+ if conf >= 8.8 and not vetoed:
63
+ tv_sym = src.tv_symbol(card["symbol"])
64
+ await send_alert(card, tv_sym)
65
+ await asyncio.sleep(1) # small gap between sends
66
+ except Exception as e:
67
+ logger.debug("Alert scan error for %s: %s", c.get("symbol"), e)
68
+ await asyncio.sleep(0.5) # gentle rate limit between symbols
69
+ except Exception as e:
70
+ logger.error("Alert loop error: %s", e)
71
+ await asyncio.sleep(ALERT_INTERVAL)
72
+
73
+
74
+ # Event loop reference — captured at startup so sync endpoints can schedule coroutines
75
+ _event_loop: asyncio.AbstractEventLoop | None = None
76
+
77
+
78
+ @asynccontextmanager
79
+ async def lifespan(app):
80
+ global _event_loop
81
+ _event_loop = asyncio.get_running_loop()
82
+ task = asyncio.create_task(_alert_loop())
83
+ try:
84
+ yield
85
+ finally:
86
+ task.cancel()
87
+ try:
88
+ await task
89
+ except asyncio.CancelledError:
90
+ pass
91
+
92
+
93
+ app = FastAPI(title="Trade Copilot", version="1.0", lifespan=lifespan)
94
+
95
+ # Cache the active source for 5 min (avoids re-pinging on every request)
96
+ _source_cache: dict = {}
97
+ _source_ts: float = 0
98
+ SOURCE_TTL = 300
99
+
100
+ # Secondary source for cross-validation (Binance — deeper OI/funding data)
101
+ _binance: Binance | None = None
102
+ _binance_ok: bool = False
103
+ _binance_ts: float = 0
104
+ BINANCE_TTL = 300
105
+
106
+
107
+ def get_source():
108
+ global _source_cache, _source_ts
109
+ if time.time() - _source_ts > SOURCE_TTL or not _source_cache.get("src"):
110
+ src = pick_source()
111
+ _source_cache = {"src": src}
112
+ _source_ts = time.time()
113
+ return _source_cache["src"]
114
+
115
+
116
+ def get_secondary():
117
+ """Returns Binance instance if reachable, else None. Used for cross-validation."""
118
+ global _binance, _binance_ok, _binance_ts
119
+ if time.time() - _binance_ts < BINANCE_TTL:
120
+ return _binance if _binance_ok else None
121
+ try:
122
+ b = Binance()
123
+ b.ping()
124
+ _binance = b
125
+ _binance_ok = True
126
+ except Exception:
127
+ _binance = None
128
+ _binance_ok = False
129
+ _binance_ts = time.time()
130
+ return _binance if _binance_ok else None
131
+
132
+
133
+ def rank_universe(src, min_volume: float, top_n: int) -> list[dict]:
134
+ perps = src.universe()
135
+ rows = []
136
+ for t in src.tickers():
137
+ if t["symbol"] not in perps:
138
+ continue
139
+ qv = t["quote_volume_usd"]
140
+ lo, hi = t.get("low", 0), t.get("high", 0)
141
+ if qv < min_volume or lo <= 0:
142
+ continue
143
+ t["range_24h_pct"] = (hi - lo) / lo * 100
144
+ rows.append(t)
145
+ if len(rows) < 3:
146
+ return [{"symbol": s} for s in src.anchors]
147
+
148
+ log_vols = [math.log(r["quote_volume_usd"]) for r in rows]
149
+ ranges = [r["range_24h_pct"] for r in rows]
150
+ mv, sv = statistics.mean(log_vols), statistics.stdev(log_vols)
151
+ mr, sr = statistics.mean(ranges), statistics.stdev(ranges)
152
+ for r, lv, rg in zip(rows, log_vols, ranges):
153
+ r["scan_score"] = round(0.5 * (lv - mv) / sv + 0.5 * (rg - mr) / sr, 3)
154
+
155
+ rows.sort(key=lambda r: r["scan_score"], reverse=True)
156
+ picked = rows[:top_n]
157
+ picked_syms = {r["symbol"] for r in picked}
158
+ for sym in src.anchors:
159
+ if sym not in picked_syms:
160
+ extra = next((r for r in rows if r["symbol"] == sym), None)
161
+ if extra:
162
+ extra["anchor"] = True
163
+ picked.append(extra)
164
+ return picked
165
+
166
+
167
+ # ── API routes ─────────────────────────────────────────────────────────────
168
+
169
+ @app.get("/api/status")
170
+ def status():
171
+ try:
172
+ src = get_source()
173
+ return {"ok": True, "source": src.name, "market": src.market_note,
174
+ "ts": datetime.now(timezone.utc).isoformat()}
175
+ except Exception as e:
176
+ return JSONResponse({"ok": False, "error": str(e)}, status_code=503)
177
+
178
+
179
+ @app.get("/api/scan")
180
+ def scan(top: int = 8, min_volume: float = None, account: float = 100000,
181
+ risk_pct: float = 1.0,
182
+ user_leverage: float = Query(default=6.0, ge=1.0, le=100.0)):
183
+ try:
184
+ src = get_source()
185
+ floor = min_volume or src.min_vol
186
+ candidates = rank_universe(src, floor, top)
187
+ secondary = get_secondary()
188
+ cards = []
189
+ for c in candidates:
190
+ try:
191
+ card = score_symbol(src, c["symbol"], account, risk_pct,
192
+ secondary=secondary, user_leverage=user_leverage)
193
+ card["rank_info"] = {k: c.get(k) for k in
194
+ ("scan_score","change_24h_pct","range_24h_pct",
195
+ "quote_volume_usd","anchor")}
196
+ cards.append(card)
197
+ # Auto-log every non-vetoed signal for live accuracy tracking
198
+ try:
199
+ log_signal(card)
200
+ except Exception:
201
+ pass
202
+ # Fire Telegram alert if confidence >= 9.0
203
+ try:
204
+ conf = card.get("confidence", 0)
205
+ vetoed = card.get("vetoed", False)
206
+ if conf >= 8.8 and not vetoed and _event_loop is not None:
207
+ from telegram_alert import send_alert
208
+ tv_sym = src.tv_symbol(card["symbol"])
209
+ asyncio.run_coroutine_threadsafe(
210
+ send_alert(card, tv_sym, manual=True), _event_loop
211
+ )
212
+ except Exception:
213
+ pass
214
+ time.sleep(0.05)
215
+ except Exception as e:
216
+ cards.append({"symbol": c["symbol"], "error": str(e)[:120]})
217
+ cards.sort(key=lambda c: c.get("confidence", 0), reverse=True)
218
+ return {"fetched_at": datetime.now(timezone.utc).isoformat(),
219
+ "source": src.name, "market": src.market_note, "cards": cards}
220
+ except Exception as e:
221
+ raise HTTPException(503, str(e))
222
+
223
+
224
+ @app.get("/api/prices")
225
+ def prices(symbols: str = ""):
226
+ """Lightweight price ticker — returns current price + 24h change for a
227
+ comma-separated list of symbols. Used for real-time card price updates."""
228
+ try:
229
+ src = get_source()
230
+ requested = {s.strip() for s in symbols.split(",") if s.strip()}
231
+ tickers = src.tickers()
232
+ result = {}
233
+ for t in tickers:
234
+ sym = t.get("symbol", "")
235
+ if not requested or sym in requested:
236
+ result[sym] = {
237
+ "price": t.get("last_price"),
238
+ "change_24h_pct": t.get("change_24h_pct"),
239
+ "high": t.get("high"),
240
+ "low": t.get("low"),
241
+ "quote_volume_usd": t.get("quote_volume_usd"),
242
+ }
243
+ return {"ts": datetime.now(timezone.utc).isoformat(), "prices": result}
244
+ except Exception as e:
245
+ raise HTTPException(503, str(e))
246
+
247
+
248
+ @app.get("/api/card/{symbol}")
249
+ def card(symbol: str, account: float = 100000, risk_pct: float = 1.0,
250
+ user_leverage: float = Query(default=6.0, ge=1.0, le=100.0)):
251
+ try:
252
+ src = get_source()
253
+ secondary = get_secondary()
254
+ result = score_symbol(src, symbol.upper(), account, risk_pct,
255
+ secondary=secondary, user_leverage=user_leverage)
256
+ # Log the signal for live accuracy tracking
257
+ try:
258
+ log_signal(result)
259
+ except Exception:
260
+ pass
261
+ return result
262
+ except Exception as e:
263
+ raise HTTPException(503, str(e))
264
+
265
+
266
+ @app.get("/api/signals")
267
+ def signals():
268
+ """Scan mid-cap perps (volume ranks 6-50) for event-driven signals.
269
+
270
+ Uses 5 independent detectors: RSI extreme cross, volume surge,
271
+ BB squeeze release, funding rate extreme, and 24h price surge.
272
+ Each fires independently — no combined gate required.
273
+ BTC and ETH always appended as market-wide sentiment anchors.
274
+
275
+ Results cached 5 min per symbol inside signals.py.
276
+ """
277
+ try:
278
+ src = get_source()
279
+ secondary = get_secondary()
280
+
281
+ # Build universe: mid-cap ranks 6-50 by USD volume + BTC/ETH anchors
282
+ try:
283
+ symbols, ticker_map = build_signal_universe(src)
284
+ except Exception:
285
+ symbols = getattr(src, "anchors", [])[:20]
286
+ ticker_map = {}
287
+
288
+ alerts = scan_universe(src, symbols, secondary=secondary, ticker_map=ticker_map)
289
+ return {
290
+ "fetched_at": datetime.now(timezone.utc).isoformat(),
291
+ "source": src.name,
292
+ "scanned": len(symbols),
293
+ "alerts": alerts,
294
+ }
295
+ except Exception as e:
296
+ raise HTTPException(503, str(e))
297
+
298
+
299
+ @app.get("/api/news")
300
+ def news():
301
+ """Free crypto news — CoinGecko trending + CoinDesk + Cointelegraph RSS.
302
+
303
+ No API key required. Returns:
304
+ trending_coins: top 7 coins by CoinGecko search volume in last 24h
305
+ headlines: latest headlines from CoinDesk + Cointelegraph RSS
306
+ each tagged with coins_mentioned[]
307
+ Cached 10 min.
308
+ """
309
+ try:
310
+ result = fetch_crypto_headlines()
311
+ return result
312
+ except Exception as e:
313
+ raise HTTPException(503, str(e))
314
+
315
+
316
+ @app.get("/api/signal-accuracy")
317
+ def signal_accuracy(symbol: str = ""):
318
+ """Live signal accuracy — real win rate from logged + resolved signals.
319
+
320
+ This is ground truth: every card the scanner generates gets logged,
321
+ then checked 48h later to see if TP1 or SL was hit first.
322
+
323
+ ?symbol=BTC-USDT (optional — returns per-symbol breakdown)
324
+ """
325
+ try:
326
+ overall = get_accuracy()
327
+ if symbol:
328
+ overall["symbol_breakdown"] = get_symbol_accuracy(symbol.upper())
329
+ return overall
330
+ except Exception as e:
331
+ raise HTTPException(503, str(e))
332
+
333
+
334
+ @app.post("/api/resolve-signals")
335
+ def resolve_signals_endpoint():
336
+ """Trigger resolution of all pending signals that are past their 48h window.
337
+
338
+ Called manually or by a scheduler. Checks price range on exchange
339
+ to determine if TP1 or SL was touched during the hold period.
340
+ """
341
+ try:
342
+ src = get_source()
343
+ result = resolve_signals(src)
344
+ return result
345
+ except Exception as e:
346
+ raise HTTPException(503, str(e))
347
+
348
+
349
+ @app.get("/api/catalyst")
350
+ def catalyst(symbol: str = ""):
351
+ """Debug endpoint — returns raw Fear & Greed + CryptoPanic data.
352
+ ?symbol=BTC-USDT (optional; returns coin news if provided)
353
+ """
354
+ try:
355
+ fng = fetch_fear_greed()
356
+ result: dict = {
357
+ "fear_greed": {
358
+ "value": fng.get("value"),
359
+ "label": fng.get("label"),
360
+ "score_mod": fng.get("score_mod"),
361
+ "error": fng.get("error"),
362
+ }
363
+ }
364
+ if symbol:
365
+ news = fetch_coin_news(symbol.upper())
366
+ combined_score, notes, _raw = score_catalyst(symbol.upper())
367
+ result["symbol"] = symbol.upper()
368
+ result["news"] = {
369
+ "score": news.get("score"),
370
+ "label": news.get("label"),
371
+ "source": news.get("source"),
372
+ "items": news.get("items", []),
373
+ "error": news.get("error"),
374
+ }
375
+ result["catalyst_score"] = combined_score
376
+ result["notes"] = notes
377
+ result["cryptopanic_key_set"] = bool(CRYPTOPANIC_TOKEN)
378
+ result["ts"] = datetime.now(timezone.utc).isoformat()
379
+ return result
380
+ except Exception as e:
381
+ raise HTTPException(503, str(e))
382
+
383
+
384
+ @app.get("/api/test-telegram")
385
+ def test_telegram():
386
+ """Diagnose Telegram relay: probe Worker reachability then send message."""
387
+ import requests as req
388
+ worker_url = os.environ.get("CLOUDFLARE_WORKER_URL", "")
389
+ if not worker_url:
390
+ return JSONResponse({"ok": False, "error": "CLOUDFLARE_WORKER_URL not set in HF Secrets"}, status_code=500)
391
+
392
+ # Probe: GET the worker (expect 405 - proves HF can reach workers.dev)
393
+ probe = {}
394
+ try:
395
+ r = req.get(worker_url, timeout=8)
396
+ probe["worker_get"] = {"status": r.status_code, "body": r.text[:80]}
397
+ except Exception as e:
398
+ probe["worker_get"] = {"error": type(e).__name__ + ": " + str(e)[:120]}
399
+
400
+ # Send the Telegram message with longer timeout
401
+ msg = "Test: Trade Copilot relay check via Cloudflare Worker."
402
+ try:
403
+ resp = req.post(worker_url, json={"text": msg, "parse_mode": "HTML"}, timeout=30)
404
+ body = resp.json()
405
+ if resp.status_code == 200:
406
+ return {"ok": True, "message": "Test message sent via CF Worker", "probe": probe}
407
+ return JSONResponse({"ok": False, "status": resp.status_code, "error": body, "probe": probe}, status_code=500)
408
+ except Exception as e:
409
+ return JSONResponse({"ok": False, "error": type(e).__name__ + ": " + str(e)[:200], "probe": probe}, status_code=500)
410
+
411
+
412
+ @app.get("/api/backtest/{symbol}")
413
+ def backtest(symbol: str):
414
+ """Walk-forward backtest for one symbol over ~6 months of 1h candles.
415
+
416
+ Win condition: TP2 hit before SL.
417
+ Results cached 6h — first call is slow (~5-15s), subsequent calls instant.
418
+
419
+ Returns:
420
+ total_trades, win_rate, avg_r, max_drawdown_r,
421
+ by_state {BULL/BEAR/RANGING: {trades, wins, win_rate, avg_r}},
422
+ by_direction {long/short: ...},
423
+ equity_curve [float list],
424
+ recent_trades [last 50]
425
+ """
426
+ try:
427
+ src = get_source()
428
+ result = backtest_symbol(src, symbol.upper())
429
+ return result
430
+ except Exception as e:
431
+ raise HTTPException(503, str(e))
432
+
433
+
434
+ # ── Static dashboard ───────────────────────────────────────────────────────
435
+
436
+ static_dir = Path(__file__).parent / "static"
437
+ if static_dir.exists():
438
+ app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
439
+
440
+ @app.get("/")
441
+ def root():
442
+ index = static_dir / "index.html"
443
+ if index.exists():
444
+ return FileResponse(str(index))
445
+ return {"message": "Trade Copilot API running. Use /api/scan or /api/status."}
446
+
447
+
448
+ if __name__ == "__main__":
449
+ import uvicorn
450
+ port = int(os.environ.get("PORT", 7860))
451
+ uvicorn.run("main:app", host="0.0.0.0", port=port)